github.com/pdmccormick/importable-docker-buildx@v0.0.0-20240426161518-e47091289030/util/dockerutil/client.go (about) 1 package dockerutil 2 3 import ( 4 "context" 5 "io" 6 "sync" 7 8 "github.com/docker/buildx/util/progress" 9 "github.com/docker/cli/cli/command" 10 "github.com/docker/docker/client" 11 ) 12 13 // Client represents an active docker object. 14 type Client struct { 15 cli command.Cli 16 17 featuresOnce sync.Once 18 featuresCache map[Feature]bool 19 } 20 21 // NewClient initializes a new docker client. 22 func NewClient(cli command.Cli) *Client { 23 return &Client{cli: cli} 24 } 25 26 // API returns a new docker API client. 27 func (c *Client) API(name string) (client.APIClient, error) { 28 if name == "" { 29 name = c.cli.CurrentContext() 30 } 31 return NewClientAPI(c.cli, name) 32 } 33 34 // LoadImage imports an image to docker. 35 func (c *Client) LoadImage(ctx context.Context, name string, status progress.Writer) (io.WriteCloser, func(), error) { 36 dapi, err := c.API(name) 37 if err != nil { 38 return nil, nil, err 39 } 40 41 pr, pw := io.Pipe() 42 done := make(chan struct{}) 43 44 ctx, cancel := context.WithCancel(ctx) 45 var w *waitingWriter 46 w = &waitingWriter{ 47 PipeWriter: pw, 48 f: func() { 49 handleErr := func(err error) { 50 pr.CloseWithError(err) 51 w.mu.Lock() 52 w.err = err 53 w.mu.Unlock() 54 } 55 56 resp, err := dapi.ImageLoad(ctx, pr, false) 57 defer close(done) 58 if err != nil { 59 handleErr(err) 60 return 61 } 62 63 status = progress.ResetTime(status) 64 if err := progress.Wrap("importing to docker", status.Write, func(l progress.SubLogger) error { 65 return fromReader(l, resp.Body) 66 }); err != nil { 67 handleErr(err) 68 } 69 }, 70 done: done, 71 cancel: cancel, 72 } 73 return w, func() { 74 pr.Close() 75 }, nil 76 } 77 78 func (c *Client) Features(ctx context.Context, name string) map[Feature]bool { 79 c.featuresOnce.Do(func() { 80 c.featuresCache = c.features(ctx, name) 81 }) 82 return c.featuresCache 83 } 84 85 func (c *Client) features(ctx context.Context, name string) map[Feature]bool { 86 features := make(map[Feature]bool) 87 if dapi, err := c.API(name); err == nil { 88 if info, err := dapi.Info(ctx); err == nil { 89 for _, v := range info.DriverStatus { 90 switch v[0] { 91 case "driver-type": 92 if v[1] == "io.containerd.snapshotter.v1" { 93 features[OCIImporter] = true 94 } 95 } 96 } 97 } 98 } 99 return features 100 } 101 102 type waitingWriter struct { 103 *io.PipeWriter 104 f func() 105 once sync.Once 106 mu sync.Mutex 107 err error 108 done chan struct{} 109 cancel func() 110 } 111 112 func (w *waitingWriter) Write(dt []byte) (int, error) { 113 w.once.Do(func() { 114 go w.f() 115 }) 116 return w.PipeWriter.Write(dt) 117 } 118 119 func (w *waitingWriter) Close() error { 120 err := w.PipeWriter.Close() 121 <-w.done 122 if err == nil { 123 w.mu.Lock() 124 defer w.mu.Unlock() 125 return w.err 126 } 127 return err 128 }