github.com/containerd/Containerd@v1.4.13/client.go (about)

     1  /*
     2     Copyright The containerd Authors.
     3  
     4     Licensed under the Apache License, Version 2.0 (the "License");
     5     you may not use this file except in compliance with the License.
     6     You may obtain a copy of the License at
     7  
     8         http://www.apache.org/licenses/LICENSE-2.0
     9  
    10     Unless required by applicable law or agreed to in writing, software
    11     distributed under the License is distributed on an "AS IS" BASIS,
    12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13     See the License for the specific language governing permissions and
    14     limitations under the License.
    15  */
    16  
    17  package containerd
    18  
    19  import (
    20  	"bytes"
    21  	"context"
    22  	"encoding/json"
    23  	"fmt"
    24  	"net/http"
    25  	"runtime"
    26  	"strconv"
    27  	"strings"
    28  	"sync"
    29  	"time"
    30  
    31  	containersapi "github.com/containerd/containerd/api/services/containers/v1"
    32  	contentapi "github.com/containerd/containerd/api/services/content/v1"
    33  	diffapi "github.com/containerd/containerd/api/services/diff/v1"
    34  	eventsapi "github.com/containerd/containerd/api/services/events/v1"
    35  	imagesapi "github.com/containerd/containerd/api/services/images/v1"
    36  	introspectionapi "github.com/containerd/containerd/api/services/introspection/v1"
    37  	leasesapi "github.com/containerd/containerd/api/services/leases/v1"
    38  	namespacesapi "github.com/containerd/containerd/api/services/namespaces/v1"
    39  	snapshotsapi "github.com/containerd/containerd/api/services/snapshots/v1"
    40  	"github.com/containerd/containerd/api/services/tasks/v1"
    41  	versionservice "github.com/containerd/containerd/api/services/version/v1"
    42  	"github.com/containerd/containerd/containers"
    43  	"github.com/containerd/containerd/content"
    44  	contentproxy "github.com/containerd/containerd/content/proxy"
    45  	"github.com/containerd/containerd/defaults"
    46  	"github.com/containerd/containerd/errdefs"
    47  	"github.com/containerd/containerd/events"
    48  	"github.com/containerd/containerd/images"
    49  	"github.com/containerd/containerd/leases"
    50  	leasesproxy "github.com/containerd/containerd/leases/proxy"
    51  	"github.com/containerd/containerd/namespaces"
    52  	"github.com/containerd/containerd/pkg/dialer"
    53  	"github.com/containerd/containerd/platforms"
    54  	"github.com/containerd/containerd/plugin"
    55  	"github.com/containerd/containerd/remotes"
    56  	"github.com/containerd/containerd/remotes/docker"
    57  	"github.com/containerd/containerd/services/introspection"
    58  	"github.com/containerd/containerd/snapshots"
    59  	snproxy "github.com/containerd/containerd/snapshots/proxy"
    60  	"github.com/containerd/typeurl"
    61  	ptypes "github.com/gogo/protobuf/types"
    62  	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
    63  	specs "github.com/opencontainers/runtime-spec/specs-go"
    64  	"github.com/pkg/errors"
    65  	"google.golang.org/grpc"
    66  	"google.golang.org/grpc/backoff"
    67  	"google.golang.org/grpc/health/grpc_health_v1"
    68  )
    69  
    70  func init() {
    71  	const prefix = "types.containerd.io"
    72  	// register TypeUrls for commonly marshaled external types
    73  	major := strconv.Itoa(specs.VersionMajor)
    74  	typeurl.Register(&specs.Spec{}, prefix, "opencontainers/runtime-spec", major, "Spec")
    75  	typeurl.Register(&specs.Process{}, prefix, "opencontainers/runtime-spec", major, "Process")
    76  	typeurl.Register(&specs.LinuxResources{}, prefix, "opencontainers/runtime-spec", major, "LinuxResources")
    77  	typeurl.Register(&specs.WindowsResources{}, prefix, "opencontainers/runtime-spec", major, "WindowsResources")
    78  }
    79  
    80  // New returns a new containerd client that is connected to the containerd
    81  // instance provided by address
    82  func New(address string, opts ...ClientOpt) (*Client, error) {
    83  	var copts clientOpts
    84  	for _, o := range opts {
    85  		if err := o(&copts); err != nil {
    86  			return nil, err
    87  		}
    88  	}
    89  	if copts.timeout == 0 {
    90  		copts.timeout = 10 * time.Second
    91  	}
    92  
    93  	c := &Client{
    94  		defaultns: copts.defaultns,
    95  	}
    96  
    97  	if copts.defaultRuntime != "" {
    98  		c.runtime = copts.defaultRuntime
    99  	} else {
   100  		c.runtime = defaults.DefaultRuntime
   101  	}
   102  
   103  	if copts.defaultPlatform != nil {
   104  		c.platform = copts.defaultPlatform
   105  	} else {
   106  		c.platform = platforms.Default()
   107  	}
   108  
   109  	if copts.services != nil {
   110  		c.services = *copts.services
   111  	}
   112  	if address != "" {
   113  		backoffConfig := backoff.DefaultConfig
   114  		backoffConfig.MaxDelay = 3 * time.Second
   115  		connParams := grpc.ConnectParams{
   116  			Backoff: backoffConfig,
   117  		}
   118  		gopts := []grpc.DialOption{
   119  			grpc.WithBlock(),
   120  			grpc.WithInsecure(),
   121  			grpc.FailOnNonTempDialError(true),
   122  			grpc.WithConnectParams(connParams),
   123  			grpc.WithContextDialer(dialer.ContextDialer),
   124  
   125  			// TODO(stevvooe): We may need to allow configuration of this on the client.
   126  			grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(defaults.DefaultMaxRecvMsgSize)),
   127  			grpc.WithDefaultCallOptions(grpc.MaxCallSendMsgSize(defaults.DefaultMaxSendMsgSize)),
   128  		}
   129  		if len(copts.dialOptions) > 0 {
   130  			gopts = copts.dialOptions
   131  		}
   132  		if copts.defaultns != "" {
   133  			unary, stream := newNSInterceptors(copts.defaultns)
   134  			gopts = append(gopts,
   135  				grpc.WithUnaryInterceptor(unary),
   136  				grpc.WithStreamInterceptor(stream),
   137  			)
   138  		}
   139  		connector := func() (*grpc.ClientConn, error) {
   140  			ctx, cancel := context.WithTimeout(context.Background(), copts.timeout)
   141  			defer cancel()
   142  			conn, err := grpc.DialContext(ctx, dialer.DialAddress(address), gopts...)
   143  			if err != nil {
   144  				return nil, errors.Wrapf(err, "failed to dial %q", address)
   145  			}
   146  			return conn, nil
   147  		}
   148  		conn, err := connector()
   149  		if err != nil {
   150  			return nil, err
   151  		}
   152  		c.conn, c.connector = conn, connector
   153  	}
   154  	if copts.services == nil && c.conn == nil {
   155  		return nil, errors.Wrap(errdefs.ErrUnavailable, "no grpc connection or services is available")
   156  	}
   157  
   158  	// check namespace labels for default runtime
   159  	if copts.defaultRuntime == "" && c.defaultns != "" {
   160  		if label, err := c.GetLabel(context.Background(), defaults.DefaultRuntimeNSLabel); err != nil {
   161  			return nil, err
   162  		} else if label != "" {
   163  			c.runtime = label
   164  		}
   165  	}
   166  
   167  	return c, nil
   168  }
   169  
   170  // NewWithConn returns a new containerd client that is connected to the containerd
   171  // instance provided by the connection
   172  func NewWithConn(conn *grpc.ClientConn, opts ...ClientOpt) (*Client, error) {
   173  	var copts clientOpts
   174  	for _, o := range opts {
   175  		if err := o(&copts); err != nil {
   176  			return nil, err
   177  		}
   178  	}
   179  	c := &Client{
   180  		defaultns: copts.defaultns,
   181  		conn:      conn,
   182  		runtime:   fmt.Sprintf("%s.%s", plugin.RuntimePlugin, runtime.GOOS),
   183  	}
   184  
   185  	// check namespace labels for default runtime
   186  	if copts.defaultRuntime == "" && c.defaultns != "" {
   187  		if label, err := c.GetLabel(context.Background(), defaults.DefaultRuntimeNSLabel); err != nil {
   188  			return nil, err
   189  		} else if label != "" {
   190  			c.runtime = label
   191  		}
   192  	}
   193  
   194  	if copts.services != nil {
   195  		c.services = *copts.services
   196  	}
   197  	return c, nil
   198  }
   199  
   200  // Client is the client to interact with containerd and its various services
   201  // using a uniform interface
   202  type Client struct {
   203  	services
   204  	connMu    sync.Mutex
   205  	conn      *grpc.ClientConn
   206  	runtime   string
   207  	defaultns string
   208  	platform  platforms.MatchComparer
   209  	connector func() (*grpc.ClientConn, error)
   210  }
   211  
   212  // Reconnect re-establishes the GRPC connection to the containerd daemon
   213  func (c *Client) Reconnect() error {
   214  	if c.connector == nil {
   215  		return errors.Wrap(errdefs.ErrUnavailable, "unable to reconnect to containerd, no connector available")
   216  	}
   217  	c.connMu.Lock()
   218  	defer c.connMu.Unlock()
   219  	c.conn.Close()
   220  	conn, err := c.connector()
   221  	if err != nil {
   222  		return err
   223  	}
   224  	c.conn = conn
   225  	return nil
   226  }
   227  
   228  // IsServing returns true if the client can successfully connect to the
   229  // containerd daemon and the healthcheck service returns the SERVING
   230  // response.
   231  // This call will block if a transient error is encountered during
   232  // connection. A timeout can be set in the context to ensure it returns
   233  // early.
   234  func (c *Client) IsServing(ctx context.Context) (bool, error) {
   235  	c.connMu.Lock()
   236  	if c.conn == nil {
   237  		c.connMu.Unlock()
   238  		return false, errors.Wrap(errdefs.ErrUnavailable, "no grpc connection available")
   239  	}
   240  	c.connMu.Unlock()
   241  	r, err := c.HealthService().Check(ctx, &grpc_health_v1.HealthCheckRequest{}, grpc.WaitForReady(true))
   242  	if err != nil {
   243  		return false, err
   244  	}
   245  	return r.Status == grpc_health_v1.HealthCheckResponse_SERVING, nil
   246  }
   247  
   248  // Containers returns all containers created in containerd
   249  func (c *Client) Containers(ctx context.Context, filters ...string) ([]Container, error) {
   250  	r, err := c.ContainerService().List(ctx, filters...)
   251  	if err != nil {
   252  		return nil, err
   253  	}
   254  	var out []Container
   255  	for _, container := range r {
   256  		out = append(out, containerFromRecord(c, container))
   257  	}
   258  	return out, nil
   259  }
   260  
   261  // NewContainer will create a new container in container with the provided id
   262  // the id must be unique within the namespace
   263  func (c *Client) NewContainer(ctx context.Context, id string, opts ...NewContainerOpts) (Container, error) {
   264  	ctx, done, err := c.WithLease(ctx)
   265  	if err != nil {
   266  		return nil, err
   267  	}
   268  	defer done(ctx)
   269  
   270  	container := containers.Container{
   271  		ID: id,
   272  		Runtime: containers.RuntimeInfo{
   273  			Name: c.runtime,
   274  		},
   275  	}
   276  	for _, o := range opts {
   277  		if err := o(ctx, c, &container); err != nil {
   278  			return nil, err
   279  		}
   280  	}
   281  	r, err := c.ContainerService().Create(ctx, container)
   282  	if err != nil {
   283  		return nil, err
   284  	}
   285  	return containerFromRecord(c, r), nil
   286  }
   287  
   288  // LoadContainer loads an existing container from metadata
   289  func (c *Client) LoadContainer(ctx context.Context, id string) (Container, error) {
   290  	r, err := c.ContainerService().Get(ctx, id)
   291  	if err != nil {
   292  		return nil, err
   293  	}
   294  	return containerFromRecord(c, r), nil
   295  }
   296  
   297  // RemoteContext is used to configure object resolutions and transfers with
   298  // remote content stores and image providers.
   299  type RemoteContext struct {
   300  	// Resolver is used to resolve names to objects, fetchers, and pushers.
   301  	// If no resolver is provided, defaults to Docker registry resolver.
   302  	Resolver remotes.Resolver
   303  
   304  	// PlatformMatcher is used to match the platforms for an image
   305  	// operation and define the preference when a single match is required
   306  	// from multiple platforms.
   307  	PlatformMatcher platforms.MatchComparer
   308  
   309  	// Unpack is done after an image is pulled to extract into a snapshotter.
   310  	// It is done simultaneously for schema 2 images when they are pulled.
   311  	// If an image is not unpacked on pull, it can be unpacked any time
   312  	// afterwards. Unpacking is required to run an image.
   313  	Unpack bool
   314  
   315  	// UnpackOpts handles options to the unpack call.
   316  	UnpackOpts []UnpackOpt
   317  
   318  	// Snapshotter used for unpacking
   319  	Snapshotter string
   320  
   321  	// SnapshotterOpts are additional options to be passed to a snapshotter during pull
   322  	SnapshotterOpts []snapshots.Opt
   323  
   324  	// Labels to be applied to the created image
   325  	Labels map[string]string
   326  
   327  	// BaseHandlers are a set of handlers which get are called on dispatch.
   328  	// These handlers always get called before any operation specific
   329  	// handlers.
   330  	BaseHandlers []images.Handler
   331  
   332  	// HandlerWrapper wraps the handler which gets sent to dispatch.
   333  	// Unlike BaseHandlers, this can run before and after the built
   334  	// in handlers, allowing operations to run on the descriptor
   335  	// after it has completed transferring.
   336  	HandlerWrapper func(images.Handler) images.Handler
   337  
   338  	// ConvertSchema1 is whether to convert Docker registry schema 1
   339  	// manifests. If this option is false then any image which resolves
   340  	// to schema 1 will return an error since schema 1 is not supported.
   341  	ConvertSchema1 bool
   342  
   343  	// Platforms defines which platforms to handle when doing the image operation.
   344  	// Platforms is ignored when a PlatformMatcher is set, otherwise the
   345  	// platforms will be used to create a PlatformMatcher with no ordering
   346  	// preference.
   347  	Platforms []string
   348  
   349  	// MaxConcurrentDownloads is the max concurrent content downloads for each pull.
   350  	MaxConcurrentDownloads int
   351  
   352  	// AllMetadata downloads all manifests and known-configuration files
   353  	AllMetadata bool
   354  
   355  	// ChildLabelMap sets the labels used to reference child objects in the content
   356  	// store. By default, all GC reference labels will be set for all fetched content.
   357  	ChildLabelMap func(ocispec.Descriptor) []string
   358  }
   359  
   360  func defaultRemoteContext() *RemoteContext {
   361  	return &RemoteContext{
   362  		Resolver: docker.NewResolver(docker.ResolverOptions{
   363  			Client: http.DefaultClient,
   364  		}),
   365  	}
   366  }
   367  
   368  // Fetch downloads the provided content into containerd's content store
   369  // and returns a non-platform specific image reference
   370  func (c *Client) Fetch(ctx context.Context, ref string, opts ...RemoteOpt) (images.Image, error) {
   371  	fetchCtx := defaultRemoteContext()
   372  	for _, o := range opts {
   373  		if err := o(c, fetchCtx); err != nil {
   374  			return images.Image{}, err
   375  		}
   376  	}
   377  
   378  	if fetchCtx.Unpack {
   379  		return images.Image{}, errors.Wrap(errdefs.ErrNotImplemented, "unpack on fetch not supported, try pull")
   380  	}
   381  
   382  	if fetchCtx.PlatformMatcher == nil {
   383  		if len(fetchCtx.Platforms) == 0 {
   384  			fetchCtx.PlatformMatcher = platforms.All
   385  		} else {
   386  			var ps []ocispec.Platform
   387  			for _, s := range fetchCtx.Platforms {
   388  				p, err := platforms.Parse(s)
   389  				if err != nil {
   390  					return images.Image{}, errors.Wrapf(err, "invalid platform %s", s)
   391  				}
   392  				ps = append(ps, p)
   393  			}
   394  
   395  			fetchCtx.PlatformMatcher = platforms.Any(ps...)
   396  		}
   397  	}
   398  
   399  	ctx, done, err := c.WithLease(ctx)
   400  	if err != nil {
   401  		return images.Image{}, err
   402  	}
   403  	defer done(ctx)
   404  
   405  	img, err := c.fetch(ctx, fetchCtx, ref, 0)
   406  	if err != nil {
   407  		return images.Image{}, err
   408  	}
   409  	return c.createNewImage(ctx, img)
   410  }
   411  
   412  // Push uploads the provided content to a remote resource
   413  func (c *Client) Push(ctx context.Context, ref string, desc ocispec.Descriptor, opts ...RemoteOpt) error {
   414  	pushCtx := defaultRemoteContext()
   415  	for _, o := range opts {
   416  		if err := o(c, pushCtx); err != nil {
   417  			return err
   418  		}
   419  	}
   420  	if pushCtx.PlatformMatcher == nil {
   421  		if len(pushCtx.Platforms) > 0 {
   422  			var ps []ocispec.Platform
   423  			for _, platform := range pushCtx.Platforms {
   424  				p, err := platforms.Parse(platform)
   425  				if err != nil {
   426  					return errors.Wrapf(err, "invalid platform %s", platform)
   427  				}
   428  				ps = append(ps, p)
   429  			}
   430  			pushCtx.PlatformMatcher = platforms.Any(ps...)
   431  		} else {
   432  			pushCtx.PlatformMatcher = platforms.All
   433  		}
   434  	}
   435  
   436  	// Annotate ref with digest to push only push tag for single digest
   437  	if !strings.Contains(ref, "@") {
   438  		ref = ref + "@" + desc.Digest.String()
   439  	}
   440  
   441  	pusher, err := pushCtx.Resolver.Pusher(ctx, ref)
   442  	if err != nil {
   443  		return err
   444  	}
   445  
   446  	var wrapper func(images.Handler) images.Handler
   447  
   448  	if len(pushCtx.BaseHandlers) > 0 {
   449  		wrapper = func(h images.Handler) images.Handler {
   450  			h = images.Handlers(append(pushCtx.BaseHandlers, h)...)
   451  			if pushCtx.HandlerWrapper != nil {
   452  				h = pushCtx.HandlerWrapper(h)
   453  			}
   454  			return h
   455  		}
   456  	} else if pushCtx.HandlerWrapper != nil {
   457  		wrapper = pushCtx.HandlerWrapper
   458  	}
   459  
   460  	return remotes.PushContent(ctx, pusher, desc, c.ContentStore(), pushCtx.PlatformMatcher, wrapper)
   461  }
   462  
   463  // GetImage returns an existing image
   464  func (c *Client) GetImage(ctx context.Context, ref string) (Image, error) {
   465  	i, err := c.ImageService().Get(ctx, ref)
   466  	if err != nil {
   467  		return nil, err
   468  	}
   469  	return NewImage(c, i), nil
   470  }
   471  
   472  // ListImages returns all existing images
   473  func (c *Client) ListImages(ctx context.Context, filters ...string) ([]Image, error) {
   474  	imgs, err := c.ImageService().List(ctx, filters...)
   475  	if err != nil {
   476  		return nil, err
   477  	}
   478  	images := make([]Image, len(imgs))
   479  	for i, img := range imgs {
   480  		images[i] = NewImage(c, img)
   481  	}
   482  	return images, nil
   483  }
   484  
   485  // Restore restores a container from a checkpoint
   486  func (c *Client) Restore(ctx context.Context, id string, checkpoint Image, opts ...RestoreOpts) (Container, error) {
   487  	store := c.ContentStore()
   488  	index, err := decodeIndex(ctx, store, checkpoint.Target())
   489  	if err != nil {
   490  		return nil, err
   491  	}
   492  
   493  	ctx, done, err := c.WithLease(ctx)
   494  	if err != nil {
   495  		return nil, err
   496  	}
   497  	defer done(ctx)
   498  
   499  	copts := []NewContainerOpts{}
   500  	for _, o := range opts {
   501  		copts = append(copts, o(ctx, id, c, checkpoint, index))
   502  	}
   503  
   504  	ctr, err := c.NewContainer(ctx, id, copts...)
   505  	if err != nil {
   506  		return nil, err
   507  	}
   508  
   509  	return ctr, nil
   510  }
   511  
   512  func writeIndex(ctx context.Context, index *ocispec.Index, client *Client, ref string) (d ocispec.Descriptor, err error) {
   513  	labels := map[string]string{}
   514  	for i, m := range index.Manifests {
   515  		labels[fmt.Sprintf("containerd.io/gc.ref.content.%d", i)] = m.Digest.String()
   516  	}
   517  	data, err := json.Marshal(index)
   518  	if err != nil {
   519  		return ocispec.Descriptor{}, err
   520  	}
   521  	return writeContent(ctx, client.ContentStore(), ocispec.MediaTypeImageIndex, ref, bytes.NewReader(data), content.WithLabels(labels))
   522  }
   523  
   524  // GetLabel gets a label value from namespace store
   525  // If there is no default label, an empty string returned with nil error
   526  func (c *Client) GetLabel(ctx context.Context, label string) (string, error) {
   527  	ns, err := namespaces.NamespaceRequired(ctx)
   528  	if err != nil {
   529  		if c.defaultns == "" {
   530  			return "", err
   531  		}
   532  		ns = c.defaultns
   533  	}
   534  
   535  	srv := c.NamespaceService()
   536  	labels, err := srv.Labels(ctx, ns)
   537  	if err != nil {
   538  		return "", err
   539  	}
   540  
   541  	value := labels[label]
   542  	return value, nil
   543  }
   544  
   545  // Subscribe to events that match one or more of the provided filters.
   546  //
   547  // Callers should listen on both the envelope and errs channels. If the errs
   548  // channel returns nil or an error, the subscriber should terminate.
   549  //
   550  // The subscriber can stop receiving events by canceling the provided context.
   551  // The errs channel will be closed and return a nil error.
   552  func (c *Client) Subscribe(ctx context.Context, filters ...string) (ch <-chan *events.Envelope, errs <-chan error) {
   553  	return c.EventService().Subscribe(ctx, filters...)
   554  }
   555  
   556  // Close closes the clients connection to containerd
   557  func (c *Client) Close() error {
   558  	c.connMu.Lock()
   559  	defer c.connMu.Unlock()
   560  	if c.conn != nil {
   561  		return c.conn.Close()
   562  	}
   563  	return nil
   564  }
   565  
   566  // NamespaceService returns the underlying Namespaces Store
   567  func (c *Client) NamespaceService() namespaces.Store {
   568  	if c.namespaceStore != nil {
   569  		return c.namespaceStore
   570  	}
   571  	c.connMu.Lock()
   572  	defer c.connMu.Unlock()
   573  	return NewNamespaceStoreFromClient(namespacesapi.NewNamespacesClient(c.conn))
   574  }
   575  
   576  // ContainerService returns the underlying container Store
   577  func (c *Client) ContainerService() containers.Store {
   578  	if c.containerStore != nil {
   579  		return c.containerStore
   580  	}
   581  	c.connMu.Lock()
   582  	defer c.connMu.Unlock()
   583  	return NewRemoteContainerStore(containersapi.NewContainersClient(c.conn))
   584  }
   585  
   586  // ContentStore returns the underlying content Store
   587  func (c *Client) ContentStore() content.Store {
   588  	if c.contentStore != nil {
   589  		return c.contentStore
   590  	}
   591  	c.connMu.Lock()
   592  	defer c.connMu.Unlock()
   593  	return contentproxy.NewContentStore(contentapi.NewContentClient(c.conn))
   594  }
   595  
   596  // SnapshotService returns the underlying snapshotter for the provided snapshotter name
   597  func (c *Client) SnapshotService(snapshotterName string) snapshots.Snapshotter {
   598  	snapshotterName, err := c.resolveSnapshotterName(context.Background(), snapshotterName)
   599  	if err != nil {
   600  		snapshotterName = DefaultSnapshotter
   601  	}
   602  	if c.snapshotters != nil {
   603  		return c.snapshotters[snapshotterName]
   604  	}
   605  	c.connMu.Lock()
   606  	defer c.connMu.Unlock()
   607  	return snproxy.NewSnapshotter(snapshotsapi.NewSnapshotsClient(c.conn), snapshotterName)
   608  }
   609  
   610  // TaskService returns the underlying TasksClient
   611  func (c *Client) TaskService() tasks.TasksClient {
   612  	if c.taskService != nil {
   613  		return c.taskService
   614  	}
   615  	c.connMu.Lock()
   616  	defer c.connMu.Unlock()
   617  	return tasks.NewTasksClient(c.conn)
   618  }
   619  
   620  // ImageService returns the underlying image Store
   621  func (c *Client) ImageService() images.Store {
   622  	if c.imageStore != nil {
   623  		return c.imageStore
   624  	}
   625  	c.connMu.Lock()
   626  	defer c.connMu.Unlock()
   627  	return NewImageStoreFromClient(imagesapi.NewImagesClient(c.conn))
   628  }
   629  
   630  // DiffService returns the underlying Differ
   631  func (c *Client) DiffService() DiffService {
   632  	if c.diffService != nil {
   633  		return c.diffService
   634  	}
   635  	c.connMu.Lock()
   636  	defer c.connMu.Unlock()
   637  	return NewDiffServiceFromClient(diffapi.NewDiffClient(c.conn))
   638  }
   639  
   640  // IntrospectionService returns the underlying Introspection Client
   641  func (c *Client) IntrospectionService() introspection.Service {
   642  	if c.introspectionService != nil {
   643  		return c.introspectionService
   644  	}
   645  	c.connMu.Lock()
   646  	defer c.connMu.Unlock()
   647  	return introspection.NewIntrospectionServiceFromClient(introspectionapi.NewIntrospectionClient(c.conn))
   648  }
   649  
   650  // LeasesService returns the underlying Leases Client
   651  func (c *Client) LeasesService() leases.Manager {
   652  	if c.leasesService != nil {
   653  		return c.leasesService
   654  	}
   655  	c.connMu.Lock()
   656  	defer c.connMu.Unlock()
   657  	return leasesproxy.NewLeaseManager(leasesapi.NewLeasesClient(c.conn))
   658  }
   659  
   660  // HealthService returns the underlying GRPC HealthClient
   661  func (c *Client) HealthService() grpc_health_v1.HealthClient {
   662  	c.connMu.Lock()
   663  	defer c.connMu.Unlock()
   664  	return grpc_health_v1.NewHealthClient(c.conn)
   665  }
   666  
   667  // EventService returns the underlying event service
   668  func (c *Client) EventService() EventService {
   669  	if c.eventService != nil {
   670  		return c.eventService
   671  	}
   672  	c.connMu.Lock()
   673  	defer c.connMu.Unlock()
   674  	return NewEventServiceFromClient(eventsapi.NewEventsClient(c.conn))
   675  }
   676  
   677  // VersionService returns the underlying VersionClient
   678  func (c *Client) VersionService() versionservice.VersionClient {
   679  	c.connMu.Lock()
   680  	defer c.connMu.Unlock()
   681  	return versionservice.NewVersionClient(c.conn)
   682  }
   683  
   684  // Conn returns the underlying GRPC connection object
   685  func (c *Client) Conn() *grpc.ClientConn {
   686  	c.connMu.Lock()
   687  	defer c.connMu.Unlock()
   688  	return c.conn
   689  }
   690  
   691  // Version of containerd
   692  type Version struct {
   693  	// Version number
   694  	Version string
   695  	// Revision from git that was built
   696  	Revision string
   697  }
   698  
   699  // Version returns the version of containerd that the client is connected to
   700  func (c *Client) Version(ctx context.Context) (Version, error) {
   701  	c.connMu.Lock()
   702  	if c.conn == nil {
   703  		c.connMu.Unlock()
   704  		return Version{}, errors.Wrap(errdefs.ErrUnavailable, "no grpc connection available")
   705  	}
   706  	c.connMu.Unlock()
   707  	response, err := c.VersionService().Version(ctx, &ptypes.Empty{})
   708  	if err != nil {
   709  		return Version{}, err
   710  	}
   711  	return Version{
   712  		Version:  response.Version,
   713  		Revision: response.Revision,
   714  	}, nil
   715  }
   716  
   717  type ServerInfo struct {
   718  	UUID string
   719  }
   720  
   721  func (c *Client) Server(ctx context.Context) (ServerInfo, error) {
   722  	c.connMu.Lock()
   723  	if c.conn == nil {
   724  		c.connMu.Unlock()
   725  		return ServerInfo{}, errors.Wrap(errdefs.ErrUnavailable, "no grpc connection available")
   726  	}
   727  	c.connMu.Unlock()
   728  
   729  	response, err := c.IntrospectionService().Server(ctx, &ptypes.Empty{})
   730  	if err != nil {
   731  		return ServerInfo{}, err
   732  	}
   733  	return ServerInfo{
   734  		UUID: response.UUID,
   735  	}, nil
   736  }
   737  
   738  func (c *Client) resolveSnapshotterName(ctx context.Context, name string) (string, error) {
   739  	if name == "" {
   740  		label, err := c.GetLabel(ctx, defaults.DefaultSnapshotterNSLabel)
   741  		if err != nil {
   742  			return "", err
   743  		}
   744  
   745  		if label != "" {
   746  			name = label
   747  		} else {
   748  			name = DefaultSnapshotter
   749  		}
   750  	}
   751  
   752  	return name, nil
   753  }
   754  
   755  func (c *Client) getSnapshotter(ctx context.Context, name string) (snapshots.Snapshotter, error) {
   756  	name, err := c.resolveSnapshotterName(ctx, name)
   757  	if err != nil {
   758  		return nil, err
   759  	}
   760  
   761  	s := c.SnapshotService(name)
   762  	if s == nil {
   763  		return nil, errors.Wrapf(errdefs.ErrNotFound, "snapshotter %s was not found", name)
   764  	}
   765  
   766  	return s, nil
   767  }
   768  
   769  // CheckRuntime returns true if the current runtime matches the expected
   770  // runtime. Providing various parts of the runtime schema will match those
   771  // parts of the expected runtime
   772  func CheckRuntime(current, expected string) bool {
   773  	cp := strings.Split(current, ".")
   774  	l := len(cp)
   775  	for i, p := range strings.Split(expected, ".") {
   776  		if i > l {
   777  			return false
   778  		}
   779  		if p != cp[i] {
   780  			return false
   781  		}
   782  	}
   783  	return true
   784  }