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