mirror of
https://github.com/linuxkit/linuxkit.git
synced 2025-07-20 01:29:07 +00:00
add options to clean only part of the cache
Signed-off-by: Avi Deitcher <avi@deitcher.net>
This commit is contained in:
parent
860163a9c7
commit
2090b2c2b6
@ -32,6 +32,8 @@ func cache(args []string) {
|
|||||||
// Please keep cases in alphabetical order
|
// Please keep cases in alphabetical order
|
||||||
case "clean":
|
case "clean":
|
||||||
cacheClean(args[1:])
|
cacheClean(args[1:])
|
||||||
|
case "rm":
|
||||||
|
cacheRm(args[1:])
|
||||||
case "ls":
|
case "ls":
|
||||||
cacheList(args[1:])
|
cacheList(args[1:])
|
||||||
case "export":
|
case "export":
|
||||||
|
13
src/cmd/linuxkit/cache/image.go
vendored
13
src/cmd/linuxkit/cache/image.go
vendored
@ -1,13 +1,20 @@
|
|||||||
package cache
|
package cache
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/google/go-containerregistry/pkg/v1/layout"
|
|
||||||
imagespec "github.com/opencontainers/image-spec/specs-go/v1"
|
imagespec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ListImages list the named images and their root digests from a layout.Path
|
// ListImages list the named images and their root digests from a layout.Path
|
||||||
func ListImages(p layout.Path) (map[string]string, error) {
|
func ListImages(dir string) (map[string]string, error) {
|
||||||
ii, err := p.ImageIndex()
|
p, err := NewProvider(dir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return p.List()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provider) List() (map[string]string, error) {
|
||||||
|
ii, err := p.cache.ImageIndex()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
91
src/cmd/linuxkit/cache/remove.go
vendored
Normal file
91
src/cmd/linuxkit/cache/remove.go
vendored
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
package cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||||
|
"github.com/google/go-containerregistry/pkg/v1/match"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Remove removes all references pointed to by the provided reference, whether it is an image or an index.
|
||||||
|
// If it is not found, it is a no-op. This should be viewed as "Ensure this reference is not in the cache",
|
||||||
|
// rather than "Remove this reference from the cache".
|
||||||
|
func (p *Provider) Remove(name string) error {
|
||||||
|
root, err := p.FindRoot(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var blobs []v1.Hash
|
||||||
|
// the provided name could be an image or an index, so we need to check both
|
||||||
|
img, err := root.Image()
|
||||||
|
if err == nil {
|
||||||
|
imgBlobs, err := blobsForImage(img)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
blobs = append(blobs, imgBlobs...)
|
||||||
|
imgDigest, err := img.Digest()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
blobs = append(blobs, imgDigest)
|
||||||
|
} else {
|
||||||
|
ii, err := root.ImageIndex()
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// get blobs for each provided image
|
||||||
|
manifests, err := ii.IndexManifest()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to list manifests in index for %s: %v", name, err)
|
||||||
|
}
|
||||||
|
for _, man := range manifests.Manifests {
|
||||||
|
img, err := ii.Image(man.Digest)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to get image for digest %s in index for %s: %v", man.Digest, name, err)
|
||||||
|
}
|
||||||
|
imgBlobs, err := blobsForImage(img)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
blobs = append(blobs, imgBlobs...)
|
||||||
|
blobs = append(blobs, man.Digest)
|
||||||
|
}
|
||||||
|
indexDigest, err := ii.Digest()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
blobs = append(blobs, indexDigest)
|
||||||
|
}
|
||||||
|
// at this point, blobs contains all of the blobs that need to be removed.
|
||||||
|
for _, blob := range blobs {
|
||||||
|
log.Debugf("removing blob %s", blob)
|
||||||
|
if err := p.cache.RemoveBlob(blob); err != nil {
|
||||||
|
log.Warnf("unable to remove blob %s for %s: %v", blob, name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return p.cache.RemoveDescriptors(match.Name(name))
|
||||||
|
}
|
||||||
|
|
||||||
|
func blobsForImage(img v1.Image) ([]v1.Hash, error) {
|
||||||
|
var blobs []v1.Hash
|
||||||
|
layers, err := img.Layers()
|
||||||
|
if err != nil {
|
||||||
|
// if we could not find the layers locally, that is fine;
|
||||||
|
// we are trying to ensure they don't exist in the cache,
|
||||||
|
// and they already don't exist.
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
for _, layer := range layers {
|
||||||
|
dig, err := layer.Digest()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
blobs = append(blobs, dig)
|
||||||
|
}
|
||||||
|
if config, err := img.ConfigName(); err == nil {
|
||||||
|
blobs = append(blobs, config)
|
||||||
|
}
|
||||||
|
return blobs, nil
|
||||||
|
}
|
@ -3,7 +3,7 @@ package cache
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/google/go-containerregistry/pkg/v1"
|
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||||
"github.com/google/go-containerregistry/pkg/v1/match"
|
"github.com/google/go-containerregistry/pkg/v1/match"
|
||||||
"github.com/google/go-containerregistry/pkg/v1/partial"
|
"github.com/google/go-containerregistry/pkg/v1/partial"
|
||||||
)
|
)
|
||||||
@ -17,6 +17,7 @@ import (
|
|||||||
type ResolvableDescriptor interface {
|
type ResolvableDescriptor interface {
|
||||||
Image() (v1.Image, error)
|
Image() (v1.Image, error)
|
||||||
ImageIndex() (v1.ImageIndex, error)
|
ImageIndex() (v1.ImageIndex, error)
|
||||||
|
Digest() (v1.Hash, error)
|
||||||
}
|
}
|
||||||
type layoutImage struct {
|
type layoutImage struct {
|
||||||
img v1.Image
|
img v1.Image
|
||||||
@ -28,6 +29,9 @@ func (l layoutImage) Image() (v1.Image, error) {
|
|||||||
func (l layoutImage) ImageIndex() (v1.ImageIndex, error) {
|
func (l layoutImage) ImageIndex() (v1.ImageIndex, error) {
|
||||||
return nil, fmt.Errorf("not an ImageIndex")
|
return nil, fmt.Errorf("not an ImageIndex")
|
||||||
}
|
}
|
||||||
|
func (l layoutImage) Digest() (v1.Hash, error) {
|
||||||
|
return l.img.Digest()
|
||||||
|
}
|
||||||
|
|
||||||
type layoutIndex struct {
|
type layoutIndex struct {
|
||||||
idx v1.ImageIndex
|
idx v1.ImageIndex
|
||||||
@ -39,6 +43,9 @@ func (l layoutIndex) Image() (v1.Image, error) {
|
|||||||
func (l layoutIndex) ImageIndex() (v1.ImageIndex, error) {
|
func (l layoutIndex) ImageIndex() (v1.ImageIndex, error) {
|
||||||
return l.idx, nil
|
return l.idx, nil
|
||||||
}
|
}
|
||||||
|
func (l layoutIndex) Digest() (v1.Hash, error) {
|
||||||
|
return l.idx.Digest()
|
||||||
|
}
|
||||||
|
|
||||||
// FindRoot find the root ResolvableDescriptor, representing an Image or Index, for
|
// FindRoot find the root ResolvableDescriptor, representing an Image or Index, for
|
||||||
// a given imageName.
|
// a given imageName.
|
||||||
|
63
src/cmd/linuxkit/cache/write.go
vendored
63
src/cmd/linuxkit/cache/write.go
vendored
@ -49,12 +49,12 @@ func (p *Provider) ImagePull(ref *reference.Spec, trustedRef, architecture strin
|
|||||||
if !alwaysPull {
|
if !alwaysPull {
|
||||||
imgSrc, err := p.ValidateImage(ref, architecture)
|
imgSrc, err := p.ValidateImage(ref, architecture)
|
||||||
if err == nil && imgSrc != nil {
|
if err == nil && imgSrc != nil {
|
||||||
log.Printf("Image %s found in local cache, not pulling", image)
|
log.Printf("Image %s arch %s found in local cache, not pulling", image, architecture)
|
||||||
return imgSrc, nil
|
return imgSrc, nil
|
||||||
}
|
}
|
||||||
// there was an error, so try to pull
|
// there was an error, so try to pull
|
||||||
}
|
}
|
||||||
log.Printf("Image %s not found in local cache, pulling", image)
|
log.Printf("Image %s arch %s not found in local cache, pulling", image, architecture)
|
||||||
remoteRef, err := name.ParseReference(pullImageName)
|
remoteRef, err := name.ParseReference(pullImageName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ImageSource{}, fmt.Errorf("invalid image name %s: %v", pullImageName, err)
|
return ImageSource{}, fmt.Errorf("invalid image name %s: %v", pullImageName, err)
|
||||||
@ -78,18 +78,21 @@ func (p *Provider) ImagePull(ref *reference.Spec, trustedRef, architecture strin
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return ImageSource{}, fmt.Errorf("unable to get IndexManifest: %v", err)
|
return ImageSource{}, fmt.Errorf("unable to get IndexManifest: %v", err)
|
||||||
}
|
}
|
||||||
_, err = p.IndexWrite(ref, im.Manifests...)
|
// write the index blob and the descriptor
|
||||||
if err == nil {
|
if err := p.cache.WriteBlob(desc.Digest, io.NopCloser(bytes.NewReader(desc.Manifest))); err != nil {
|
||||||
for _, m := range im.Manifests {
|
return ImageSource{}, fmt.Errorf("unable to write index content to cache: %v", err)
|
||||||
if m.MediaType.IsImage() && (m.Platform == nil || m.Platform.Architecture == architecture) {
|
}
|
||||||
img, err := ii.Image(m.Digest)
|
if _, err := p.DescriptorWrite(ref, desc.Descriptor); err != nil {
|
||||||
if err != nil {
|
return ImageSource{}, fmt.Errorf("unable to write index descriptor to cache: %v", err)
|
||||||
return ImageSource{}, fmt.Errorf("unable to get image: %v", err)
|
}
|
||||||
}
|
for _, m := range im.Manifests {
|
||||||
err = p.cache.WriteImage(img)
|
if m.MediaType.IsImage() && (m.Platform == nil || m.Platform.Architecture == architecture) {
|
||||||
if err != nil {
|
img, err := ii.Image(m.Digest)
|
||||||
return ImageSource{}, fmt.Errorf("unable to write image: %v", err)
|
if err != nil {
|
||||||
}
|
return ImageSource{}, fmt.Errorf("unable to get image: %v", err)
|
||||||
|
}
|
||||||
|
if err := p.cache.WriteImage(img); err != nil {
|
||||||
|
return ImageSource{}, fmt.Errorf("unable to write image: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -355,9 +358,39 @@ func (p *Provider) DescriptorWrite(ref *reference.Spec, desc v1.Descriptor) (lkt
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *Provider) ImageInCache(ref *reference.Spec, trustedRef, architecture string) (bool, error) {
|
func (p *Provider) ImageInCache(ref *reference.Spec, trustedRef, architecture string) (bool, error) {
|
||||||
if _, err := p.findImage(ref.String(), architecture); err != nil {
|
img, err := p.findImage(ref.String(), architecture)
|
||||||
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
// findImage only checks if we had the pointer to it; it does not check if it is complete.
|
||||||
|
// We need to do that next.
|
||||||
|
|
||||||
|
// check that all of the layers exist
|
||||||
|
layers, err := img.Layers()
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("layers not found: %v", err)
|
||||||
|
}
|
||||||
|
for _, layer := range layers {
|
||||||
|
dig, err := layer.Digest()
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("unable to get digest of layer: %v", err)
|
||||||
|
}
|
||||||
|
var rc io.ReadCloser
|
||||||
|
if rc, err = p.cache.Blob(dig); err != nil {
|
||||||
|
return false, fmt.Errorf("layer %s not found: %v", dig, err)
|
||||||
|
}
|
||||||
|
rc.Close()
|
||||||
|
}
|
||||||
|
// check that the config exists
|
||||||
|
config, err := img.ConfigName()
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("unable to get config: %v", err)
|
||||||
|
}
|
||||||
|
var rc io.ReadCloser
|
||||||
|
if rc, err = p.cache.Blob(config); err != nil {
|
||||||
|
return false, fmt.Errorf("config %s not found: %v", config, err)
|
||||||
|
}
|
||||||
|
rc.Close()
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,6 +5,9 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
namepkg "github.com/google/go-containerregistry/pkg/name"
|
||||||
|
"github.com/google/go-containerregistry/pkg/v1/remote"
|
||||||
|
cachepkg "github.com/linuxkit/linuxkit/src/cmd/linuxkit/cache"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -13,13 +16,62 @@ func cacheClean(args []string) {
|
|||||||
|
|
||||||
cacheDir := flagOverEnvVarOverDefaultString{def: defaultLinuxkitCache(), envVar: envVarCacheDir}
|
cacheDir := flagOverEnvVarOverDefaultString{def: defaultLinuxkitCache(), envVar: envVarCacheDir}
|
||||||
flags.Var(&cacheDir, "cache", fmt.Sprintf("Directory for caching and finding cached image, overrides env var %s", envVarCacheDir))
|
flags.Var(&cacheDir, "cache", fmt.Sprintf("Directory for caching and finding cached image, overrides env var %s", envVarCacheDir))
|
||||||
|
publishedOnly := flags.Bool("published-only", false, "Only clean images that linuxkit can confirm at the time of running have been published to the registry")
|
||||||
|
|
||||||
if err := flags.Parse(args); err != nil {
|
if err := flags.Parse(args); err != nil {
|
||||||
log.Fatal("Unable to parse args")
|
log.Fatal("Unable to parse args")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := os.RemoveAll(cacheDir.String()); err != nil {
|
// did we limit to published only?
|
||||||
log.Fatalf("Unable to clean cache %s: %v", cacheDir, err)
|
if !*publishedOnly {
|
||||||
|
if err := os.RemoveAll(cacheDir.String()); err != nil {
|
||||||
|
log.Fatalf("Unable to clean cache %s: %v", cacheDir, err)
|
||||||
|
}
|
||||||
|
log.Infof("Cache emptied: %s", cacheDir)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// list all of the images and content in the cache
|
||||||
|
p, err := cachepkg.NewProvider(cacheDir.String())
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("unable to read a local cache: %v", err)
|
||||||
|
}
|
||||||
|
images, err := p.List()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("error reading image names: %v", err)
|
||||||
|
}
|
||||||
|
removeImagesFromCache(images, p, *publishedOnly)
|
||||||
|
}
|
||||||
|
|
||||||
|
// removeImagesFromCache removes images from the cache.
|
||||||
|
func removeImagesFromCache(images map[string]string, p *cachepkg.Provider, publishedOnly bool) {
|
||||||
|
// check each image in the registry. If it exists, remove it here.
|
||||||
|
for name, hash := range images {
|
||||||
|
if publishedOnly {
|
||||||
|
ref, err := namepkg.ParseReference(name)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
desc, err := remote.Get(ref)
|
||||||
|
if err != nil {
|
||||||
|
log.Debugf("image %s not found in remote registry or error, leaving in cache: %v", name, err)
|
||||||
|
fmt.Fprintf(os.Stderr, "image %s not found in remote registry, leaving in cache", name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if desc == nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "image %s not found in remote registry, leaving in cache", name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if desc.Digest.String() != hash {
|
||||||
|
fmt.Fprintf(os.Stderr, "image %s has mismatched hashes, cache %s vs remote registry %s, leaving in cache", name, hash, desc.Digest.String())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// we have a match, remove it
|
||||||
|
fmt.Fprintf(os.Stderr, "removing image %s from cache", name)
|
||||||
|
if err := p.Remove(name); err != nil {
|
||||||
|
log.Warnf("Unable to remove image %s: %v", name, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
log.Infof("Cache cleaned: %s", cacheDir)
|
|
||||||
}
|
}
|
||||||
|
@ -19,11 +19,7 @@ func cacheList(args []string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// list all of the images and content in the cache
|
// list all of the images and content in the cache
|
||||||
p, err := cachepkg.Get(cacheDir.String())
|
images, err := cachepkg.ListImages(cacheDir.String())
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("unable to read a local cache: %v", err)
|
|
||||||
}
|
|
||||||
images, err := cachepkg.ListImages(p)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("error reading image names: %v", err)
|
log.Fatalf("error reading image names: %v", err)
|
||||||
}
|
}
|
||||||
|
48
src/cmd/linuxkit/cache_rm.go
Normal file
48
src/cmd/linuxkit/cache_rm.go
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
cachepkg "github.com/linuxkit/linuxkit/src/cmd/linuxkit/cache"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
func cacheRm(args []string) {
|
||||||
|
flags := flag.NewFlagSet("rm", flag.ExitOnError)
|
||||||
|
|
||||||
|
cacheDir := flagOverEnvVarOverDefaultString{def: defaultLinuxkitCache(), envVar: envVarCacheDir}
|
||||||
|
flags.Var(&cacheDir, "cache", fmt.Sprintf("Directory for caching and finding cached image, overrides env var %s", envVarCacheDir))
|
||||||
|
publishedOnly := flags.Bool("published-only", false, "Only remove the specified images if linuxkit can confirm at the time of running have been published to the registry")
|
||||||
|
|
||||||
|
if err := flags.Parse(args); err != nil {
|
||||||
|
log.Fatal("Unable to parse args")
|
||||||
|
}
|
||||||
|
|
||||||
|
if flags.NArg() == 0 {
|
||||||
|
log.Fatal("Please specify at least one image to remove")
|
||||||
|
}
|
||||||
|
|
||||||
|
imageNames := flags.Args()
|
||||||
|
|
||||||
|
// did we limit to published only?
|
||||||
|
|
||||||
|
// list all of the images and content in the cache
|
||||||
|
p, err := cachepkg.NewProvider(cacheDir.String())
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("unable to read a local cache: %v", err)
|
||||||
|
}
|
||||||
|
images := map[string]string{}
|
||||||
|
for _, imageName := range imageNames {
|
||||||
|
desc, err := p.FindRoot(imageName)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("error reading image %s: %v", imageName, err)
|
||||||
|
}
|
||||||
|
dig, err := desc.Digest()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("error reading digest for image %s: %v", imageName, err)
|
||||||
|
}
|
||||||
|
images[imageName] = dig.String()
|
||||||
|
}
|
||||||
|
removeImagesFromCache(images, p, *publishedOnly)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user