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 }