Merge pull request #1567 from stevvooe/image-check-function
images: support checking status of image content
This commit is contained in:
		| @@ -21,6 +21,7 @@ var imageCommand = cli.Command{ | |||||||
| 	Usage: "manage images", | 	Usage: "manage images", | ||||||
| 	Subcommands: cli.Commands{ | 	Subcommands: cli.Commands{ | ||||||
| 		imagesListCommand, | 		imagesListCommand, | ||||||
|  | 		imagesCheckCommand, | ||||||
| 		imageRemoveCommand, | 		imageRemoveCommand, | ||||||
| 		imagesSetLabelsCommand, | 		imagesSetLabelsCommand, | ||||||
| 		imagesImportCommand, | 		imagesImportCommand, | ||||||
| @@ -176,6 +177,90 @@ var imagesSetLabelsCommand = cli.Command{ | |||||||
| 	}, | 	}, | ||||||
| } | } | ||||||
|  |  | ||||||
|  | var imagesCheckCommand = cli.Command{ | ||||||
|  | 	Name:        "check", | ||||||
|  | 	Usage:       "Check that an image has all content available locally.", | ||||||
|  | 	ArgsUsage:   "[flags] <ref> [<ref>, ...]", | ||||||
|  | 	Description: "Check that an image has all content available locally.", | ||||||
|  | 	Flags:       []cli.Flag{}, | ||||||
|  | 	Action: func(clicontext *cli.Context) error { | ||||||
|  | 		var ( | ||||||
|  | 			exitErr error | ||||||
|  | 		) | ||||||
|  | 		ctx, cancel := appContext(clicontext) | ||||||
|  | 		defer cancel() | ||||||
|  |  | ||||||
|  | 		client, err := newClient(clicontext) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		imageStore := client.ImageService() | ||||||
|  | 		contentStore := client.ContentStore() | ||||||
|  |  | ||||||
|  | 		tw := tabwriter.NewWriter(os.Stdout, 1, 8, 1, ' ', 0) | ||||||
|  | 		fmt.Fprintln(tw, "REF\tTYPE\tDIGEST\tSTATUS\tSIZE\t") | ||||||
|  |  | ||||||
|  | 		args := []string(clicontext.Args()) | ||||||
|  | 		imageList, err := imageStore.List(ctx, args...) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return errors.Wrap(err, "failed listing images") | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		for _, image := range imageList { | ||||||
|  | 			var ( | ||||||
|  | 				status       string = "complete" | ||||||
|  | 				size         string | ||||||
|  | 				requiredSize int64 | ||||||
|  | 				presentSize  int64 | ||||||
|  | 			) | ||||||
|  |  | ||||||
|  | 			available, required, present, missing, err := images.Check(ctx, contentStore, image.Target, platforms.Default()) | ||||||
|  | 			if err != nil { | ||||||
|  | 				if exitErr == nil { | ||||||
|  | 					exitErr = errors.Wrapf(err, "unable to check %v", image.Name) | ||||||
|  | 				} | ||||||
|  | 				log.G(ctx).WithError(err).Errorf("unable to check %v", image.Name) | ||||||
|  | 				status = "error" | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if status != "error" { | ||||||
|  | 				for _, d := range required { | ||||||
|  | 					requiredSize += d.Size | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				for _, d := range present { | ||||||
|  | 					presentSize += d.Size | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				if len(missing) > 0 { | ||||||
|  | 					status = "incomplete" | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				if available { | ||||||
|  | 					status += fmt.Sprintf(" (%v/%v)", len(present), len(required)) | ||||||
|  | 					size = fmt.Sprintf("%v/%v", progress.Bytes(presentSize), progress.Bytes(requiredSize)) | ||||||
|  | 				} else { | ||||||
|  | 					status = fmt.Sprintf("unavailable (%v/?)", len(present)) | ||||||
|  | 					size = fmt.Sprintf("%v/?", progress.Bytes(presentSize)) | ||||||
|  | 				} | ||||||
|  | 			} else { | ||||||
|  | 				size = "-" | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			fmt.Fprintf(tw, "%v\t%v\t%v\t%v\t%v\t\n", | ||||||
|  | 				image.Name, | ||||||
|  | 				image.Target.MediaType, | ||||||
|  | 				image.Target.Digest, | ||||||
|  | 				status, | ||||||
|  | 				size) | ||||||
|  | 		} | ||||||
|  | 		tw.Flush() | ||||||
|  |  | ||||||
|  | 		return exitErr | ||||||
|  | 	}, | ||||||
|  | } | ||||||
|  |  | ||||||
| var imageRemoveCommand = cli.Command{ | var imageRemoveCommand = cli.Command{ | ||||||
| 	Name:        "remove", | 	Name:        "remove", | ||||||
| 	Aliases:     []string{"rm"}, | 	Aliases:     []string{"rm"}, | ||||||
|   | |||||||
| @@ -87,7 +87,12 @@ func (image *Image) Size(ctx context.Context, provider content.Provider, platfor | |||||||
| 	}), ChildrenHandler(provider, platform)), image.Target) | 	}), ChildrenHandler(provider, platform)), image.Target) | ||||||
| } | } | ||||||
|  |  | ||||||
| // Manifest returns the manifest for an image. | // Manifest resolves a manifest from the image for the given platform. | ||||||
|  | // | ||||||
|  | // TODO(stevvooe): This violates the current platform agnostic approach to this | ||||||
|  | // package by returning a specific manifest type. We'll need to refactor this | ||||||
|  | // to return a manifest descriptor or decide that we want to bring the API in | ||||||
|  | // this direction because this abstraction is not needed.` | ||||||
| func Manifest(ctx context.Context, provider content.Provider, image ocispec.Descriptor, platform string) (ocispec.Manifest, error) { | func Manifest(ctx context.Context, provider content.Provider, image ocispec.Descriptor, platform string) (ocispec.Manifest, error) { | ||||||
| 	var ( | 	var ( | ||||||
| 		matcher platforms.Matcher | 		matcher platforms.Matcher | ||||||
| @@ -165,7 +170,7 @@ func Manifest(ctx context.Context, provider content.Provider, image ocispec.Desc | |||||||
| 			return descs, nil | 			return descs, nil | ||||||
|  |  | ||||||
| 		} | 		} | ||||||
| 		return nil, errors.New("could not resolve manifest") | 		return nil, errors.Wrap(errdefs.ErrNotFound, "could not resolve manifest") | ||||||
| 	}), image); err != nil { | 	}), image); err != nil { | ||||||
| 		return ocispec.Manifest{}, err | 		return ocispec.Manifest{}, err | ||||||
| 	} | 	} | ||||||
| @@ -218,6 +223,49 @@ func Platforms(ctx context.Context, provider content.Provider, image ocispec.Des | |||||||
| 	}), ChildrenHandler(provider, "")), image) | 	}), ChildrenHandler(provider, "")), image) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // Check returns nil if the all components of an image are available in the | ||||||
|  | // provider for the specified platform. | ||||||
|  | // | ||||||
|  | // If available is true, the caller can assume that required represents the | ||||||
|  | // complete set of content required for the image. | ||||||
|  | // | ||||||
|  | // missing will have the components that are part of required but not avaiiable | ||||||
|  | // in the provider. | ||||||
|  | // | ||||||
|  | // If there is a problem resolving content, an error will be returned. | ||||||
|  | func Check(ctx context.Context, provider content.Provider, image ocispec.Descriptor, platform string) (available bool, required, present, missing []ocispec.Descriptor, err error) { | ||||||
|  | 	mfst, err := Manifest(ctx, provider, image, platform) | ||||||
|  | 	if err != nil { | ||||||
|  | 		if errdefs.IsNotFound(err) { | ||||||
|  | 			return false, []ocispec.Descriptor{image}, nil, []ocispec.Descriptor{image}, nil | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		return false, nil, nil, nil, errors.Wrap(err, "image check failed") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// TODO(stevvooe): It is possible that referenced conponents could have | ||||||
|  | 	// children, but this is rare. For now, we ignore this and only verify | ||||||
|  | 	// that manfiest components are present. | ||||||
|  | 	required = append([]ocispec.Descriptor{mfst.Config}, mfst.Layers...) | ||||||
|  |  | ||||||
|  | 	for _, desc := range required { | ||||||
|  | 		ra, err := provider.ReaderAt(ctx, desc.Digest) | ||||||
|  | 		if err != nil { | ||||||
|  | 			if errdefs.IsNotFound(err) { | ||||||
|  | 				missing = append(missing, desc) | ||||||
|  | 				continue | ||||||
|  | 			} else { | ||||||
|  | 				return false, nil, nil, nil, err | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		ra.Close() | ||||||
|  | 		present = append(present, desc) | ||||||
|  |  | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return true, required, present, missing, nil | ||||||
|  | } | ||||||
|  |  | ||||||
| // Children returns the immediate children of content described by the descriptor. | // Children returns the immediate children of content described by the descriptor. | ||||||
| func Children(ctx context.Context, provider content.Provider, desc ocispec.Descriptor, platform string) ([]ocispec.Descriptor, error) { | func Children(ctx context.Context, provider content.Provider, desc ocispec.Descriptor, platform string) ([]ocispec.Descriptor, error) { | ||||||
| 	var descs []ocispec.Descriptor | 	var descs []ocispec.Descriptor | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Michael Crosby
					Michael Crosby