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  }