github.com/pdmccormick/importable-docker-buildx@v0.0.0-20240426161518-e47091289030/util/progress/printer.go (about) 1 package progress 2 3 import ( 4 "context" 5 "os" 6 "sync" 7 8 "github.com/containerd/console" 9 "github.com/docker/buildx/util/logutil" 10 "github.com/moby/buildkit/client" 11 "github.com/moby/buildkit/util/progress/progressui" 12 "github.com/opencontainers/go-digest" 13 "github.com/sirupsen/logrus" 14 "go.opentelemetry.io/otel/attribute" 15 "go.opentelemetry.io/otel/metric" 16 ) 17 18 type Printer struct { 19 status chan *client.SolveStatus 20 21 ready chan struct{} 22 done chan struct{} 23 paused chan struct{} 24 25 err error 26 warnings []client.VertexWarning 27 logMu sync.Mutex 28 logSourceMap map[digest.Digest]interface{} 29 metrics *metricWriter 30 31 // TODO: remove once we can use result context to pass build ref 32 // see https://github.com/docker/buildx/pull/1861 33 buildRefsMu sync.Mutex 34 buildRefs map[string]string 35 } 36 37 func (p *Printer) Wait() error { 38 close(p.status) 39 <-p.done 40 return p.err 41 } 42 43 func (p *Printer) Pause() error { 44 p.paused = make(chan struct{}) 45 return p.Wait() 46 } 47 48 func (p *Printer) Unpause() { 49 close(p.paused) 50 <-p.ready 51 } 52 53 func (p *Printer) Write(s *client.SolveStatus) { 54 p.status <- s 55 if p.metrics != nil { 56 p.metrics.Write(s) 57 } 58 } 59 60 func (p *Printer) Warnings() []client.VertexWarning { 61 return p.warnings 62 } 63 64 func (p *Printer) ValidateLogSource(dgst digest.Digest, v interface{}) bool { 65 p.logMu.Lock() 66 defer p.logMu.Unlock() 67 src, ok := p.logSourceMap[dgst] 68 if ok { 69 if src == v { 70 return true 71 } 72 } else { 73 p.logSourceMap[dgst] = v 74 return true 75 } 76 return false 77 } 78 79 func (p *Printer) ClearLogSource(v interface{}) { 80 p.logMu.Lock() 81 defer p.logMu.Unlock() 82 for d := range p.logSourceMap { 83 if p.logSourceMap[d] == v { 84 delete(p.logSourceMap, d) 85 } 86 } 87 } 88 89 func NewPrinter(ctx context.Context, out console.File, mode progressui.DisplayMode, opts ...PrinterOpt) (*Printer, error) { 90 opt := &printerOpts{} 91 for _, o := range opts { 92 o(opt) 93 } 94 95 if v := os.Getenv("BUILDKIT_PROGRESS"); v != "" && mode == progressui.AutoMode { 96 mode = progressui.DisplayMode(v) 97 } 98 99 d, err := progressui.NewDisplay(out, mode, opt.displayOpts...) 100 if err != nil { 101 return nil, err 102 } 103 104 pw := &Printer{ 105 ready: make(chan struct{}), 106 metrics: opt.mw, 107 } 108 go func() { 109 for { 110 pw.status = make(chan *client.SolveStatus) 111 pw.done = make(chan struct{}) 112 113 pw.logMu.Lock() 114 pw.logSourceMap = map[digest.Digest]interface{}{} 115 pw.logMu.Unlock() 116 117 resumeLogs := logutil.Pause(logrus.StandardLogger()) 118 close(pw.ready) 119 // not using shared context to not disrupt display but let is finish reporting errors 120 pw.warnings, pw.err = d.UpdateFrom(ctx, pw.status) 121 resumeLogs() 122 close(pw.done) 123 124 if opt.onclose != nil { 125 opt.onclose() 126 } 127 if pw.paused == nil { 128 break 129 } 130 131 pw.ready = make(chan struct{}) 132 <-pw.paused 133 pw.paused = nil 134 135 d, _ = progressui.NewDisplay(out, mode, opt.displayOpts...) 136 } 137 }() 138 <-pw.ready 139 return pw, nil 140 } 141 142 func (p *Printer) WriteBuildRef(target string, ref string) { 143 p.buildRefsMu.Lock() 144 defer p.buildRefsMu.Unlock() 145 if p.buildRefs == nil { 146 p.buildRefs = map[string]string{} 147 } 148 p.buildRefs[target] = ref 149 } 150 151 func (p *Printer) BuildRefs() map[string]string { 152 return p.buildRefs 153 } 154 155 type printerOpts struct { 156 displayOpts []progressui.DisplayOpt 157 mw *metricWriter 158 159 onclose func() 160 } 161 162 type PrinterOpt func(b *printerOpts) 163 164 func WithPhase(phase string) PrinterOpt { 165 return func(opt *printerOpts) { 166 opt.displayOpts = append(opt.displayOpts, progressui.WithPhase(phase)) 167 } 168 } 169 170 func WithDesc(text string, console string) PrinterOpt { 171 return func(opt *printerOpts) { 172 opt.displayOpts = append(opt.displayOpts, progressui.WithDesc(text, console)) 173 } 174 } 175 176 func WithMetrics(mp metric.MeterProvider, attrs attribute.Set) PrinterOpt { 177 return func(opt *printerOpts) { 178 opt.mw = newMetrics(mp, attrs) 179 } 180 } 181 182 func WithOnClose(onclose func()) PrinterOpt { 183 return func(opt *printerOpts) { 184 opt.onclose = onclose 185 } 186 }