Merge pull request #1567 from stevvooe/image-check-function

images: support checking status of image content
This commit is contained in:
Michael Crosby 2017-10-04 11:40:43 -04:00 committed by GitHub
commit 0ae9d8fabf
2 changed files with 135 additions and 2 deletions

View File

@ -21,6 +21,7 @@ var imageCommand = cli.Command{
Usage: "manage images",
Subcommands: cli.Commands{
imagesListCommand,
imagesCheckCommand,
imageRemoveCommand,
imagesSetLabelsCommand,
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{
Name: "remove",
Aliases: []string{"rm"},

View File

@ -87,7 +87,12 @@ func (image *Image) Size(ctx context.Context, provider content.Provider, platfor
}), 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) {
var (
matcher platforms.Matcher
@ -165,7 +170,7 @@ func Manifest(ctx context.Context, provider content.Provider, image ocispec.Desc
return descs, nil
}
return nil, errors.New("could not resolve manifest")
return nil, errors.Wrap(errdefs.ErrNotFound, "could not resolve manifest")
}), image); err != nil {
return ocispec.Manifest{}, err
}
@ -218,6 +223,49 @@ func Platforms(ctx context.Context, provider content.Provider, image ocispec.Des
}), 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.
func Children(ctx context.Context, provider content.Provider, desc ocispec.Descriptor, platform string) ([]ocispec.Descriptor, error) {
var descs []ocispec.Descriptor