mirror of
https://github.com/linuxkit/linuxkit.git
synced 2025-07-19 17:26:28 +00:00
add support for pushing and pulling images
Signed-off-by: Avi Deitcher <avi@deitcher.net>
This commit is contained in:
parent
8b9b3f673b
commit
54d9db8650
@ -26,5 +26,7 @@ func cacheCmd() *cobra.Command {
|
|||||||
cmd.AddCommand(cacheLsCmd())
|
cmd.AddCommand(cacheLsCmd())
|
||||||
cmd.AddCommand(cacheExportCmd())
|
cmd.AddCommand(cacheExportCmd())
|
||||||
cmd.AddCommand(cacheImportCmd())
|
cmd.AddCommand(cacheImportCmd())
|
||||||
|
cmd.AddCommand(cachePullCmd())
|
||||||
|
cmd.AddCommand(cachePushCmd())
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
82
src/cmd/linuxkit/cache/pull.go
vendored
82
src/cmd/linuxkit/cache/pull.go
vendored
@ -5,10 +5,18 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/containerd/containerd/reference"
|
"github.com/containerd/containerd/reference"
|
||||||
|
"github.com/google/go-containerregistry/pkg/authn"
|
||||||
|
namepkg "github.com/google/go-containerregistry/pkg/name"
|
||||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||||
|
"github.com/google/go-containerregistry/pkg/v1/layout"
|
||||||
|
"github.com/google/go-containerregistry/pkg/v1/match"
|
||||||
"github.com/google/go-containerregistry/pkg/v1/partial"
|
"github.com/google/go-containerregistry/pkg/v1/partial"
|
||||||
|
"github.com/google/go-containerregistry/pkg/v1/remote"
|
||||||
"github.com/google/go-containerregistry/pkg/v1/validate"
|
"github.com/google/go-containerregistry/pkg/v1/validate"
|
||||||
lktspec "github.com/linuxkit/linuxkit/src/cmd/linuxkit/spec"
|
lktspec "github.com/linuxkit/linuxkit/src/cmd/linuxkit/spec"
|
||||||
|
"github.com/linuxkit/linuxkit/src/cmd/linuxkit/util"
|
||||||
|
imagespec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -118,3 +126,77 @@ func validateManifestContents(index v1.ImageIndex, digest v1.Hash) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pull pull a reference, whether it points to an arch-specific image or to an index.
|
||||||
|
// If an index, optionally, try to pull its individual named references as well.
|
||||||
|
func (p *Provider) Pull(name string, withArchReferences bool) error {
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
fullname := util.ReferenceExpand(name, util.ReferenceWithTag())
|
||||||
|
ref, err := namepkg.ParseReference(fullname)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v1ref, err := reference.Parse(ref.String())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// before we even try to push, let us see if it exists remotely
|
||||||
|
remoteOptions := []remote.Option{remote.WithAuthFromKeychain(authn.DefaultKeychain)}
|
||||||
|
|
||||||
|
desc, err := remote.Get(ref, remoteOptions...)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error getting manifest for trusted image %s: %v", name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// use the original image name in the annotation
|
||||||
|
annotations := map[string]string{
|
||||||
|
imagespec.AnnotationRefName: fullname,
|
||||||
|
}
|
||||||
|
|
||||||
|
// first attempt as an index
|
||||||
|
ii, err := desc.ImageIndex()
|
||||||
|
if err == nil {
|
||||||
|
log.Debugf("ImageWrite retrieved %s is index, saving", fullname)
|
||||||
|
|
||||||
|
if err := p.cache.WriteIndex(ii); err != nil {
|
||||||
|
return fmt.Errorf("unable to write index: %v", err)
|
||||||
|
}
|
||||||
|
if _, err := p.DescriptorWrite(&v1ref, desc.Descriptor); err != nil {
|
||||||
|
return fmt.Errorf("unable to write index descriptor to cache: %v", err)
|
||||||
|
}
|
||||||
|
if withArchReferences {
|
||||||
|
im, err := ii.IndexManifest()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to get IndexManifest: %v", err)
|
||||||
|
}
|
||||||
|
for _, m := range im.Manifests {
|
||||||
|
if m.MediaType.IsImage() && m.Platform != nil && m.Platform.Architecture != unknown && m.Platform.OS != unknown {
|
||||||
|
archSpecific := fmt.Sprintf("%s-%s", ref.String(), m.Platform.Architecture)
|
||||||
|
archRef, err := reference.Parse(archSpecific)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to parse arch-specific reference %s: %v", archSpecific, err)
|
||||||
|
}
|
||||||
|
if _, err := p.DescriptorWrite(&archRef, m); err != nil {
|
||||||
|
return fmt.Errorf("unable to write index descriptor to cache: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var im v1.Image
|
||||||
|
// try an image
|
||||||
|
im, err = desc.Image()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("provided image is neither an image nor an index: %s", name)
|
||||||
|
}
|
||||||
|
log.Debugf("ImageWrite retrieved %s is image, saving", fullname)
|
||||||
|
if err = p.cache.ReplaceImage(im, match.Name(fullname), layout.WithAnnotations(annotations)); err != nil {
|
||||||
|
return fmt.Errorf("unable to save image to cache: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
37
src/cmd/linuxkit/cache_pull.go
Normal file
37
src/cmd/linuxkit/cache_pull.go
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
cachepkg "github.com/linuxkit/linuxkit/src/cmd/linuxkit/cache"
|
||||||
|
"github.com/linuxkit/linuxkit/src/cmd/linuxkit/util"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
func cachePullCmd() *cobra.Command {
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "pull",
|
||||||
|
Short: "pull images to the linuxkit cache from registry",
|
||||||
|
Long: `Pull named images from their registry to the linuxkit cache. Can provide short name, like linuxkit/kernel:6.6.13
|
||||||
|
or nginx, or canonical name, like docker.io/library/nginx:latest. Will be saved into cache as canonical.
|
||||||
|
Will replace in cache if found. Blobs with the same content are not replaced.`,
|
||||||
|
Args: cobra.MinimumNArgs(1),
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
names := args
|
||||||
|
for _, name := range names {
|
||||||
|
fullname := util.ReferenceExpand(name, util.ReferenceWithTag())
|
||||||
|
|
||||||
|
p, err := cachepkg.NewProvider(cacheDir)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("unable to read a local cache: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := p.Pull(fullname, true); err != nil {
|
||||||
|
log.Fatalf("unable to push image named %s: %v", name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
37
src/cmd/linuxkit/cache_push.go
Normal file
37
src/cmd/linuxkit/cache_push.go
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
cachepkg "github.com/linuxkit/linuxkit/src/cmd/linuxkit/cache"
|
||||||
|
"github.com/linuxkit/linuxkit/src/cmd/linuxkit/util"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
func cachePushCmd() *cobra.Command {
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "push",
|
||||||
|
Short: "push images from the linuxkit cache",
|
||||||
|
Long: `Push named images from the linuxkit cache to registry. Can provide short name, like linuxkit/kernel:6.6.13
|
||||||
|
or nginx, or canonical name, like docker.io/library/nginx:latest.
|
||||||
|
It is efficient, as blobs with the same content are not replaced.`,
|
||||||
|
Args: cobra.MinimumNArgs(1),
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
names := args
|
||||||
|
for _, name := range names {
|
||||||
|
fullname := util.ReferenceExpand(name)
|
||||||
|
|
||||||
|
p, err := cachepkg.NewProvider(cacheDir)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("unable to read a local cache: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := p.Push(fullname, true); err != nil {
|
||||||
|
log.Fatalf("unable to push image named %s: %v", name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
@ -2,15 +2,37 @@ package util
|
|||||||
|
|
||||||
import "strings"
|
import "strings"
|
||||||
|
|
||||||
|
type refOpts struct {
|
||||||
|
withTag bool
|
||||||
|
}
|
||||||
|
type ReferenceOption func(r *refOpts)
|
||||||
|
|
||||||
|
// ReferenceWithTag returns a ReferenceOption that ensures a tag is filled. If the tag is not provided,
|
||||||
|
// the default is added
|
||||||
|
func ReferenceWithTag() ReferenceOption {
|
||||||
|
return func(r *refOpts) {
|
||||||
|
r.withTag = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ReferenceExpand expands "redis" to "docker.io/library/redis" so all images have a full domain
|
// ReferenceExpand expands "redis" to "docker.io/library/redis" so all images have a full domain
|
||||||
func ReferenceExpand(ref string) string {
|
func ReferenceExpand(ref string, options ...ReferenceOption) string {
|
||||||
|
var opts refOpts
|
||||||
|
for _, opt := range options {
|
||||||
|
opt(&opts)
|
||||||
|
}
|
||||||
|
var ret string
|
||||||
parts := strings.Split(ref, "/")
|
parts := strings.Split(ref, "/")
|
||||||
switch len(parts) {
|
switch len(parts) {
|
||||||
case 1:
|
case 1:
|
||||||
return "docker.io/library/" + ref
|
ret = "docker.io/library/" + ref
|
||||||
case 2:
|
case 2:
|
||||||
return "docker.io/" + ref
|
ret = "docker.io/" + ref
|
||||||
default:
|
default:
|
||||||
return ref
|
ret = ref
|
||||||
}
|
}
|
||||||
|
if opts.withTag && !strings.Contains(ret, ":") {
|
||||||
|
ret += ":latest"
|
||||||
|
}
|
||||||
|
return ret
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user