Merge pull request #1567 from stevvooe/image-check-function
images: support checking status of image content
This commit is contained in:
commit
0ae9d8fabf
@ -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"},
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user