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",
|
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
|
||||||
|
Loading…
Reference in New Issue
Block a user