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  }