Add authfile flag to pull and push
Push and pull can now access any cached registry credentials from the auth file Signed-off-by: umohnani8 <umohnani@redhat.com>
This commit is contained in:
parent
0914a7a667
commit
d855e2c8ad
13 changed files with 428 additions and 210 deletions
|
@ -95,14 +95,19 @@ func loadCmd(c *cli.Context) error {
|
|||
output = os.Stdout
|
||||
}
|
||||
|
||||
options := libpod.CopyOptions{
|
||||
SignaturePolicyPath: c.String("signature-policy"),
|
||||
Writer: output,
|
||||
}
|
||||
|
||||
src := libpod.DockerArchive + ":" + input
|
||||
if err := runtime.PullImage(src, false, c.String("signature-policy"), output); err != nil {
|
||||
if err := runtime.PullImage(src, options); err != nil {
|
||||
src = libpod.OCIArchive + ":" + input
|
||||
// generate full src name with specified image:tag
|
||||
if image != "" {
|
||||
src = src + ":" + image
|
||||
}
|
||||
if err := runtime.PullImage(src, false, "", output); err != nil {
|
||||
if err := runtime.PullImage(src, options); err != nil {
|
||||
return errors.Wrapf(err, "error pulling %q", src)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,6 +38,9 @@ func logoutCmd(c *cli.Context) error {
|
|||
if len(args) > 1 {
|
||||
return errors.Errorf("too many arguments, logout takes only 1 argument")
|
||||
}
|
||||
if len(args) == 0 {
|
||||
return errors.Errorf("registry must be given")
|
||||
}
|
||||
var server string
|
||||
if len(args) == 1 {
|
||||
server = args[0]
|
||||
|
|
152
cmd/kpod/pull.go
152
cmd/kpod/pull.go
|
@ -1,14 +1,14 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"fmt"
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
|
||||
"github.com/containers/image/docker/reference"
|
||||
"github.com/containers/image/pkg/sysregistries"
|
||||
"github.com/containers/image/transports/alltransports"
|
||||
"github.com/containers/image/types"
|
||||
"github.com/kubernetes-incubator/cri-o/libpod"
|
||||
"github.com/kubernetes-incubator/cri-o/libpod/common"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
|
@ -16,16 +16,18 @@ import (
|
|||
|
||||
var (
|
||||
pullFlags = []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
// all-tags is hidden since it has not been implemented yet
|
||||
Name: "all-tags, a",
|
||||
Hidden: true,
|
||||
Usage: "Download all tagged images in the repository",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "signature-policy",
|
||||
Usage: "`pathname` of signature policy file (not usually used)",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "authfile",
|
||||
Usage: "Path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "creds",
|
||||
Usage: "`credentials` (USERNAME:PASSWORD) to use for authenticating to a registry",
|
||||
},
|
||||
}
|
||||
|
||||
pullDescription = "Pulls an image from a registry and stores it locally.\n" +
|
||||
|
@ -41,83 +43,14 @@ var (
|
|||
}
|
||||
)
|
||||
|
||||
// struct for when a user passes a short or incomplete
|
||||
// image name
|
||||
type imagePullStruct struct {
|
||||
imageName string
|
||||
tag string
|
||||
registry string
|
||||
hasRegistry bool
|
||||
transport string
|
||||
}
|
||||
|
||||
func (ips imagePullStruct) returnFQName() string {
|
||||
return fmt.Sprintf("%s%s/%s:%s", ips.transport, ips.registry, ips.imageName, ips.tag)
|
||||
}
|
||||
|
||||
func getRegistriesToTry(image string) ([]string, error) {
|
||||
var registries []string
|
||||
var imageError = fmt.Sprintf("unable to parse '%s'\n", image)
|
||||
imgRef, err := reference.Parse(image)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, imageError)
|
||||
}
|
||||
tagged, isTagged := imgRef.(reference.NamedTagged)
|
||||
tag := "latest"
|
||||
if isTagged {
|
||||
tag = tagged.Tag()
|
||||
}
|
||||
hasDomain := true
|
||||
registry := reference.Domain(imgRef.(reference.Named))
|
||||
if registry == "" {
|
||||
hasDomain = false
|
||||
}
|
||||
imageName := reference.Path(imgRef.(reference.Named))
|
||||
pImage := imagePullStruct{
|
||||
imageName,
|
||||
tag,
|
||||
registry,
|
||||
hasDomain,
|
||||
"docker://",
|
||||
}
|
||||
if pImage.hasRegistry {
|
||||
// If input has a registry, we have to assume they included an image
|
||||
// name but maybe not a tag
|
||||
pullRef, err := alltransports.ParseImageName(pImage.returnFQName())
|
||||
if err != nil {
|
||||
return nil, errors.Errorf(imageError)
|
||||
}
|
||||
registries = append(registries, pullRef.DockerReference().String())
|
||||
} else {
|
||||
// No registry means we check the globals registries configuration file
|
||||
// and assemble a list of candidate sources to try
|
||||
registryConfigPath := ""
|
||||
envOverride := os.Getenv("REGISTRIES_CONFIG_PATH")
|
||||
if len(envOverride) > 0 {
|
||||
registryConfigPath = envOverride
|
||||
}
|
||||
searchRegistries, err := sysregistries.GetRegistries(&types.SystemContext{SystemRegistriesConfPath: registryConfigPath})
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return nil, errors.Errorf("unable to parse the registries.conf file and"+
|
||||
" the image name '%s' is incomplete.", imageName)
|
||||
}
|
||||
for _, searchRegistry := range searchRegistries {
|
||||
pImage.registry = searchRegistry
|
||||
pullRef, err := alltransports.ParseImageName(pImage.returnFQName())
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("unable to parse '%s'", pImage.returnFQName())
|
||||
}
|
||||
registries = append(registries, pullRef.DockerReference().String())
|
||||
}
|
||||
}
|
||||
return registries, nil
|
||||
}
|
||||
|
||||
// pullCmd gets the data from the command line and calls pullImage
|
||||
// to copy an image from a registry to a local machine
|
||||
func pullCmd(c *cli.Context) error {
|
||||
var fqRegistries []string
|
||||
runtime, err := getRuntime(c)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "could not get runtime")
|
||||
}
|
||||
defer runtime.Shutdown(false)
|
||||
|
||||
args := c.Args()
|
||||
if len(args) == 0 {
|
||||
|
@ -132,31 +65,34 @@ func pullCmd(c *cli.Context) error {
|
|||
return err
|
||||
}
|
||||
image := args[0]
|
||||
srcRef, err := alltransports.ParseImageName(image)
|
||||
if err != nil {
|
||||
fqRegistries, err = getRegistriesToTry(image)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
} else {
|
||||
fqRegistries = append(fqRegistries, srcRef.DockerReference().String())
|
||||
}
|
||||
runtime, err := getRuntime(c)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "could not get runtime")
|
||||
}
|
||||
defer runtime.Shutdown(false)
|
||||
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "could not create runtime")
|
||||
}
|
||||
for _, fqname := range fqRegistries {
|
||||
fmt.Printf("Trying to pull %s...", fqname)
|
||||
if err := runtime.PullImage(fqname, c.Bool("all-tags"), c.String("signature-policy"), os.Stdout); err != nil {
|
||||
fmt.Printf(" Failed\n")
|
||||
} else {
|
||||
return nil
|
||||
var registryCreds *types.DockerAuthConfig
|
||||
if c.String("creds") != "" {
|
||||
creds, err := common.ParseRegistryCreds(c.String("creds"))
|
||||
if err != nil {
|
||||
if err == common.ErrNoPassword {
|
||||
fmt.Print("Password: ")
|
||||
password, err := terminal.ReadPassword(0)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "could not read password from terminal")
|
||||
}
|
||||
creds.Password = string(password)
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
registryCreds = creds
|
||||
}
|
||||
return errors.Errorf("error pulling image from %q", image)
|
||||
|
||||
options := libpod.CopyOptions{
|
||||
SignaturePolicyPath: c.String("signature-policy"),
|
||||
AuthFile: c.String("authfile"),
|
||||
DockerRegistryOptions: common.DockerRegistryOptions{
|
||||
DockerRegistryCreds: registryCreds,
|
||||
},
|
||||
Writer: os.Stdout,
|
||||
}
|
||||
|
||||
return runtime.PullImage(image, options)
|
||||
|
||||
}
|
||||
|
|
|
@ -45,6 +45,10 @@ var (
|
|||
Name: "quiet, q",
|
||||
Usage: "don't output progress information when pushing images",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "authfile",
|
||||
Usage: "Path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json",
|
||||
},
|
||||
}
|
||||
pushDescription = fmt.Sprintf(`
|
||||
Pushes an image to a specified location.
|
||||
|
@ -120,7 +124,9 @@ func pushCmd(c *cli.Context) error {
|
|||
RemoveSignatures: removeSignatures,
|
||||
SignBy: signBy,
|
||||
},
|
||||
AuthFile: c.String("authfile"),
|
||||
Writer: writer,
|
||||
}
|
||||
|
||||
return runtime.PushImage(srcName, destName, options, writer)
|
||||
return runtime.PushImage(srcName, destName, options)
|
||||
}
|
||||
|
|
|
@ -83,13 +83,14 @@ func saveCmd(c *cli.Context) error {
|
|||
|
||||
saveOpts := libpod.CopyOptions{
|
||||
SignaturePolicyPath: "",
|
||||
Writer: writer,
|
||||
}
|
||||
|
||||
// only one image is supported for now
|
||||
// future pull requests will fix this
|
||||
for _, image := range args {
|
||||
dest := dst + ":" + image
|
||||
if err := runtime.PushImage(image, dest, saveOpts, writer); err != nil {
|
||||
if err := runtime.PushImage(image, dest, saveOpts); err != nil {
|
||||
return errors.Wrapf(err, "unable to save %q", image)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -170,6 +170,8 @@ _kpod_logs() {
|
|||
|
||||
_kpod_pull() {
|
||||
local options_with_args="
|
||||
--authfile
|
||||
--creds
|
||||
--signature-policy
|
||||
"
|
||||
local boolean_options="
|
||||
|
@ -222,18 +224,20 @@ _kpod_mount() {
|
|||
|
||||
_kpod_push() {
|
||||
local boolean_options="
|
||||
--disable-compression
|
||||
-D
|
||||
--quiet
|
||||
-q
|
||||
--signature-policy
|
||||
--certs
|
||||
--tls-verify
|
||||
--remove-signatures
|
||||
--sign-by
|
||||
--disable-compression
|
||||
-D
|
||||
--quiet
|
||||
-q
|
||||
--remove-signatures
|
||||
--tls-verify
|
||||
"
|
||||
|
||||
local options_with_args="
|
||||
--authfile
|
||||
--cert-dir
|
||||
--creds
|
||||
--sign-by
|
||||
--signature-policy
|
||||
"
|
||||
|
||||
local all_options="$options_with_args $boolean_options"
|
||||
|
|
|
@ -21,10 +21,10 @@ images from archives and local storage using different transports.
|
|||
## imageID
|
||||
Image stored in local container/storage
|
||||
|
||||
## DESTINATION
|
||||
## SOURCE
|
||||
|
||||
The DESTINATION is a location to store container images
|
||||
The Image "DESTINATION" uses a "transport":"details" format.
|
||||
The SOURCE is a location to get container images
|
||||
The Image "SOURCE" uses a "transport":"details" format.
|
||||
|
||||
Multiple transports are supported:
|
||||
|
||||
|
@ -32,7 +32,7 @@ Image stored in local container/storage
|
|||
An existing local directory _path_ storing the manifest, layer tarballs and signatures as individual files. This is a non-standardized format, primarily useful for debugging or noninvasive container inspection.
|
||||
|
||||
**docker://**_docker-reference_
|
||||
An image in a registry implementing the "Docker Registry HTTP API V2". By default, uses the authorization state in `$HOME/.docker/config.json`, which is set e.g. using `(docker login)`.
|
||||
An image in a registry implementing the "Docker Registry HTTP API V2". By default, uses the authorization state in `$XDG_RUNTIME_DIR/containers/auth.json`, which is set e.g. using `(kpod login)`.
|
||||
|
||||
**docker-archive:**_path_[**:**_docker-reference_]
|
||||
An image is stored in the `docker save` formatted file. _docker-reference_ is only used when creating such a file, and it must not contain a digest.
|
||||
|
@ -40,7 +40,7 @@ Image stored in local container/storage
|
|||
**docker-daemon:**_docker-reference_
|
||||
An image _docker-reference_ stored in the docker daemon internal storage. _docker-reference_ must contain either a tag or a digest. Alternatively, when reading images, the format can also be docker-daemon:algo:digest (an image ID).
|
||||
|
||||
**oci:**_path_**:**_tag_
|
||||
**oci-archive:**_path_**:**_tag_
|
||||
An image _tag_ in a directory compliant with "Open Container Image Layout Specification" at _path_.
|
||||
|
||||
**ostree:**_image_[**@**_/absolute/repo/path_]
|
||||
|
@ -54,6 +54,14 @@ Image stored in local container/storage
|
|||
|
||||
## OPTIONS
|
||||
|
||||
**--authfile**
|
||||
|
||||
Path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json
|
||||
|
||||
**--creds**
|
||||
|
||||
Credentials (USERNAME:PASSWORD) to use for authenticating to a registry
|
||||
|
||||
**--signature-policy="PATHNAME"**
|
||||
|
||||
Pathname of a signature policy file to use. It is not recommended that this
|
||||
|
@ -75,8 +83,30 @@ Writing manifest to image destination
|
|||
Storing signatures
|
||||
```
|
||||
|
||||
```
|
||||
# kpod pull --authfile temp-auths/myauths.json docker://docker.io/umohnani/finaltest
|
||||
Trying to pull docker.io/umohnani/finaltest:latest...Getting image source signatures
|
||||
Copying blob sha256:6d987f6f42797d81a318c40d442369ba3dc124883a0964d40b0c8f4f7561d913
|
||||
1.90 MB / 1.90 MB [========================================================] 0s
|
||||
Copying config sha256:ad4686094d8f0186ec8249fc4917b71faa2c1030d7b5a025c29f26e19d95c156
|
||||
1.41 KB / 1.41 KB [========================================================] 0s
|
||||
Writing manifest to image destination
|
||||
Storing signatures
|
||||
```
|
||||
|
||||
```
|
||||
# kpod pull docker.io/umohnani/finaltest
|
||||
Trying to pull docker.io/umohnani/finaltest:latest...Getting image source signatures
|
||||
Copying blob sha256:6d987f6f42797d81a318c40d442369ba3dc124883a0964d40b0c8f4f7561d913
|
||||
1.90 MB / 1.90 MB [========================================================] 0s
|
||||
Copying config sha256:ad4686094d8f0186ec8249fc4917b71faa2c1030d7b5a025c29f26e19d95c156
|
||||
1.41 KB / 1.41 KB [========================================================] 0s
|
||||
Writing manifest to image destination
|
||||
Storing signatures
|
||||
```
|
||||
|
||||
## SEE ALSO
|
||||
kpod(1), crio(8), crio.conf(5)
|
||||
kpod(1), kpod-push(1), crio(8), crio.conf(5)
|
||||
|
||||
## HISTORY
|
||||
July 2017, Originally compiled by Urvashi Mohnani <umohnani@redhat.com>
|
||||
|
|
|
@ -26,7 +26,7 @@ Image stored in local container/storage
|
|||
An existing local directory _path_ storing the manifest, layer tarballs and signatures as individual files. This is a non-standardized format, primarily useful for debugging or noninvasive container inspection.
|
||||
|
||||
**docker://**_docker-reference_
|
||||
An image in a registry implementing the "Docker Registry HTTP API V2". By default, uses the authorization state in `$HOME/.docker/config.json`, which is set e.g. using `(docker login)`.
|
||||
An image in a registry implementing the "Docker Registry HTTP API V2". By default, uses the authorization state in `$XDG_RUNTIME_DIR/containers/auth.json`, which is set e.g. using `(kpod login)`.
|
||||
|
||||
**docker-archive:**_path_[**:**_docker-reference_]
|
||||
An image is stored in the `docker save` formatted file. _docker-reference_ is only used when creating such a file, and it must not contain a digest.
|
||||
|
@ -34,7 +34,7 @@ Image stored in local container/storage
|
|||
**docker-daemon:**_docker-reference_
|
||||
An image _docker-reference_ stored in the docker daemon internal storage. _docker-reference_ must contain either a tag or a digest. Alternatively, when reading images, the format can also be docker-daemon:algo:digest (an image ID).
|
||||
|
||||
**oci:**_path_**:**_tag_
|
||||
**oci-archive:**_path_**:**_tag_
|
||||
An image _tag_ in a directory compliant with "Open Container Image Layout Specification" at _path_.
|
||||
|
||||
**ostree:**_image_[**@**_/absolute/repo/path_]
|
||||
|
@ -42,6 +42,10 @@ Image stored in local container/storage
|
|||
|
||||
## OPTIONS
|
||||
|
||||
**--authfile**
|
||||
|
||||
Path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json
|
||||
|
||||
**--creds="CREDENTIALS"**
|
||||
|
||||
Credentials (USERNAME:PASSWORD) to use for authenticating to a registry
|
||||
|
@ -84,7 +88,7 @@ This example extracts the imageID image to a local directory in docker format.
|
|||
|
||||
This example extracts the imageID image to a local directory in oci format.
|
||||
|
||||
`# kpod push imageID oci:/path/to/layout`
|
||||
`# kpod push imageID oci-archive:/path/to/layout:image:tag`
|
||||
|
||||
This example extracts the imageID image to a container registry named registry.example.com
|
||||
|
||||
|
@ -94,5 +98,19 @@ This example extracts the imageID image and puts into the local docker container
|
|||
|
||||
`# kpod push imageID docker-daemon:image:tag`
|
||||
|
||||
This example pushes the alpine image to umohnani/alpine on dockerhub and reads the creds from
|
||||
the path given to --authfile
|
||||
|
||||
```
|
||||
# kpod push --authfile temp-auths/myauths.json alpine docker://docker.io/umohnani/alpine
|
||||
Getting image source signatures
|
||||
Copying blob sha256:5bef08742407efd622d243692b79ba0055383bbce12900324f75e56f589aedb0
|
||||
4.03 MB / 4.03 MB [========================================================] 1s
|
||||
Copying config sha256:ad4686094d8f0186ec8249fc4917b71faa2c1030d7b5a025c29f26e19d95c156
|
||||
1.41 KB / 1.41 KB [========================================================] 1s
|
||||
Writing manifest to image destination
|
||||
Storing signatures
|
||||
```
|
||||
|
||||
## SEE ALSO
|
||||
kpod(1)
|
||||
kpod(1), kpod-pull(1), crio(8), crio.conf(5)
|
||||
|
|
|
@ -17,15 +17,15 @@ var (
|
|||
)
|
||||
|
||||
// GetCopyOptions constructs a new containers/image/copy.Options{} struct from the given parameters
|
||||
func GetCopyOptions(reportWriter io.Writer, signaturePolicyPath string, srcDockerRegistry, destDockerRegistry *DockerRegistryOptions, signing SigningOptions) *cp.Options {
|
||||
func GetCopyOptions(reportWriter io.Writer, signaturePolicyPath string, srcDockerRegistry, destDockerRegistry *DockerRegistryOptions, signing SigningOptions, authFile string) *cp.Options {
|
||||
if srcDockerRegistry == nil {
|
||||
srcDockerRegistry = &DockerRegistryOptions{}
|
||||
}
|
||||
if destDockerRegistry == nil {
|
||||
destDockerRegistry = &DockerRegistryOptions{}
|
||||
}
|
||||
srcContext := srcDockerRegistry.GetSystemContext(signaturePolicyPath)
|
||||
destContext := destDockerRegistry.GetSystemContext(signaturePolicyPath)
|
||||
srcContext := srcDockerRegistry.GetSystemContext(signaturePolicyPath, authFile)
|
||||
destContext := destDockerRegistry.GetSystemContext(signaturePolicyPath, authFile)
|
||||
return &cp.Options{
|
||||
RemoveSignatures: signing.RemoveSignatures,
|
||||
SignBy: signing.SignBy,
|
||||
|
|
|
@ -22,12 +22,13 @@ type DockerRegistryOptions struct {
|
|||
|
||||
// GetSystemContext constructs a new system context from the given signaturePolicy path and the
|
||||
// values in the DockerRegistryOptions
|
||||
func (o DockerRegistryOptions) GetSystemContext(signaturePolicyPath string) *types.SystemContext {
|
||||
func (o DockerRegistryOptions) GetSystemContext(signaturePolicyPath, authFile string) *types.SystemContext {
|
||||
sc := &types.SystemContext{
|
||||
SignaturePolicyPath: signaturePolicyPath,
|
||||
DockerAuthConfig: o.DockerRegistryCreds,
|
||||
DockerCertPath: o.DockerCertPath,
|
||||
DockerInsecureSkipTLSVerify: o.DockerInsecureSkipTLSVerify,
|
||||
AuthFilePath: authFile,
|
||||
}
|
||||
return sc
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
@ -14,6 +15,7 @@ import (
|
|||
"github.com/containers/image/docker/tarfile"
|
||||
"github.com/containers/image/manifest"
|
||||
ociarchive "github.com/containers/image/oci/archive"
|
||||
"github.com/containers/image/pkg/sysregistries"
|
||||
"github.com/containers/image/signature"
|
||||
is "github.com/containers/image/storage"
|
||||
"github.com/containers/image/transports"
|
||||
|
@ -42,6 +44,9 @@ var (
|
|||
// OCIArchive is the transport we prepend to an image name
|
||||
// when saving to oci-archive
|
||||
OCIArchive = ociarchive.Transport.Name()
|
||||
// DirTransport is the transport for pushing and pulling
|
||||
// images to and from a directory
|
||||
DirTransport = "dir"
|
||||
)
|
||||
|
||||
// CopyOptions contains the options given when pushing or pulling images
|
||||
|
@ -61,6 +66,10 @@ type CopyOptions struct {
|
|||
|
||||
// SigningPolicyPath this points to a alternative signature policy file, used mainly for testing
|
||||
SignaturePolicyPath string
|
||||
// AuthFile is the path of the cached credentials file defined by the user
|
||||
AuthFile string
|
||||
// Writer is the reportWriter for the output
|
||||
Writer io.Writer
|
||||
}
|
||||
|
||||
// Image API
|
||||
|
@ -76,45 +85,124 @@ type ImageFilterParams struct {
|
|||
ImageInput string
|
||||
}
|
||||
|
||||
// struct for when a user passes a short or incomplete
|
||||
// image name
|
||||
type imageDecomposeStruct struct {
|
||||
imageName string
|
||||
tag string
|
||||
registry string
|
||||
hasRegistry bool
|
||||
transport string
|
||||
}
|
||||
|
||||
// ImageFilter is a function to determine whether an image is included in
|
||||
// command output. Images to be outputted are tested using the function. A true
|
||||
// return will include the image, a false return will exclude it.
|
||||
type ImageFilter func(*storage.Image, *types.ImageInspectInfo) bool
|
||||
|
||||
// PullImage pulls an image from configured registries
|
||||
// By default, only the latest tag (or a specific tag if requested) will be
|
||||
// pulled. If allTags is true, all tags for the requested image will be pulled.
|
||||
// Signature validation will be performed if the Runtime has been appropriately
|
||||
// configured
|
||||
func (r *Runtime) PullImage(imgName string, allTags bool, signaturePolicyPath string, reportWriter io.Writer) error {
|
||||
r.lock.Lock()
|
||||
defer r.lock.Unlock()
|
||||
func (ips imageDecomposeStruct) returnFQName() string {
|
||||
return fmt.Sprintf("%s%s/%s:%s", ips.transport, ips.registry, ips.imageName, ips.tag)
|
||||
}
|
||||
|
||||
if !r.valid {
|
||||
return ErrRuntimeStopped
|
||||
}
|
||||
|
||||
// PullImage copies the image from the source to the destination
|
||||
var (
|
||||
images []string
|
||||
)
|
||||
|
||||
if signaturePolicyPath == "" {
|
||||
signaturePolicyPath = r.config.SignaturePolicyPath
|
||||
}
|
||||
|
||||
sc := common.GetSystemContext(signaturePolicyPath, "")
|
||||
|
||||
srcRef, err := alltransports.ParseImageName(imgName)
|
||||
func getRegistriesToTry(image string, store storage.Store) ([]*pullStruct, error) {
|
||||
var pStructs []*pullStruct
|
||||
var imageError = fmt.Sprintf("unable to parse '%s'\n", image)
|
||||
imgRef, err := reference.Parse(image)
|
||||
if err != nil {
|
||||
defaultName := DefaultRegistry + imgName
|
||||
srcRef2, err2 := alltransports.ParseImageName(defaultName)
|
||||
if err2 != nil {
|
||||
return errors.Errorf("error parsing image name %q: %v", defaultName, err2)
|
||||
return nil, errors.Wrapf(err, imageError)
|
||||
}
|
||||
tagged, isTagged := imgRef.(reference.NamedTagged)
|
||||
tag := "latest"
|
||||
if isTagged {
|
||||
tag = tagged.Tag()
|
||||
}
|
||||
hasDomain := true
|
||||
registry := reference.Domain(imgRef.(reference.Named))
|
||||
if registry == "" {
|
||||
hasDomain = false
|
||||
}
|
||||
imageName := reference.Path(imgRef.(reference.Named))
|
||||
pImage := imageDecomposeStruct{
|
||||
imageName,
|
||||
tag,
|
||||
registry,
|
||||
hasDomain,
|
||||
"docker://",
|
||||
}
|
||||
if pImage.hasRegistry {
|
||||
// If input has a registry, we have to assume they included an image
|
||||
// name but maybe not a tag
|
||||
srcRef, err := alltransports.ParseImageName(pImage.returnFQName())
|
||||
if err != nil {
|
||||
return nil, errors.Errorf(imageError)
|
||||
}
|
||||
pStruct := &pullStruct{
|
||||
image: srcRef.DockerReference().String(),
|
||||
srcRef: srcRef,
|
||||
}
|
||||
pStructs = append(pStructs, pStruct)
|
||||
} else {
|
||||
// No registry means we check the globals registries configuration file
|
||||
// and assemble a list of candidate sources to try
|
||||
registryConfigPath := ""
|
||||
envOverride := os.Getenv("REGISTRIES_CONFIG_PATH")
|
||||
if len(envOverride) > 0 {
|
||||
registryConfigPath = envOverride
|
||||
}
|
||||
searchRegistries, err := sysregistries.GetRegistries(&types.SystemContext{SystemRegistriesConfPath: registryConfigPath})
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return nil, errors.Errorf("unable to parse the registries.conf file and"+
|
||||
" the image name '%s' is incomplete.", imageName)
|
||||
}
|
||||
for _, searchRegistry := range searchRegistries {
|
||||
pImage.registry = searchRegistry
|
||||
srcRef, err := alltransports.ParseImageName(pImage.returnFQName())
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("unable to parse '%s'", pImage.returnFQName())
|
||||
}
|
||||
pStruct := &pullStruct{
|
||||
image: srcRef.DockerReference().String(),
|
||||
srcRef: srcRef,
|
||||
}
|
||||
pStructs = append(pStructs, pStruct)
|
||||
}
|
||||
srcRef = srcRef2
|
||||
}
|
||||
|
||||
for _, pStruct := range pStructs {
|
||||
destRef, err := is.Transport.ParseStoreReference(store, pStruct.image)
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("error parsing dest reference name: %v", err)
|
||||
}
|
||||
pStruct.dstRef = destRef
|
||||
}
|
||||
return pStructs, nil
|
||||
}
|
||||
|
||||
type pullStruct struct {
|
||||
image string
|
||||
srcRef types.ImageReference
|
||||
dstRef types.ImageReference
|
||||
}
|
||||
|
||||
func (r *Runtime) getPullStruct(srcRef types.ImageReference, destName string) (*pullStruct, error) {
|
||||
reference := destName
|
||||
if srcRef.DockerReference() != nil {
|
||||
reference = srcRef.DockerReference().String()
|
||||
}
|
||||
destRef, err := is.Transport.ParseStoreReference(r.store, reference)
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("error parsing dest reference name: %v", err)
|
||||
}
|
||||
return &pullStruct{
|
||||
image: destName,
|
||||
srcRef: srcRef,
|
||||
dstRef: destRef,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *Runtime) getPullListFromRef(srcRef types.ImageReference, imgName string, sc *types.SystemContext) ([]*pullStruct, error) {
|
||||
var pullStructs []*pullStruct
|
||||
splitArr := strings.Split(imgName, ":")
|
||||
archFile := splitArr[len(splitArr)-1]
|
||||
|
||||
|
@ -123,41 +211,107 @@ func (r *Runtime) PullImage(imgName string, allTags bool, signaturePolicyPath st
|
|||
tarSource := tarfile.NewSource(archFile)
|
||||
manifest, err := tarSource.LoadTarManifest()
|
||||
if err != nil {
|
||||
return errors.Errorf("error retrieving manifest.json: %v", err)
|
||||
return nil, errors.Errorf("error retrieving manifest.json: %v", err)
|
||||
}
|
||||
// to pull all the images stored in one tar file
|
||||
for i := range manifest {
|
||||
if manifest[i].RepoTags != nil {
|
||||
images = append(images, manifest[i].RepoTags[0])
|
||||
} else {
|
||||
// create an image object and use the hex value of the digest as the image ID
|
||||
// for parsing the store reference
|
||||
newImg, err := srcRef.NewImage(sc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer newImg.Close()
|
||||
digest := newImg.ConfigInfo().Digest
|
||||
if err := digest.Validate(); err == nil {
|
||||
images = append(images, "@"+digest.Hex())
|
||||
} else {
|
||||
return errors.Wrapf(err, "error getting config info")
|
||||
}
|
||||
// to pull the first image stored in the tar file
|
||||
if len(manifest) == 0 {
|
||||
// create an image object and use the hex value of the digest as the image ID
|
||||
// for parsing the store reference
|
||||
newImg, err := srcRef.NewImage(sc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer newImg.Close()
|
||||
digest := newImg.ConfigInfo().Digest
|
||||
if err := digest.Validate(); err == nil {
|
||||
pullInfo, err := r.getPullStruct(srcRef, "@"+digest.Hex())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pullStructs = append(pullStructs, pullInfo)
|
||||
} else {
|
||||
return nil, errors.Wrapf(err, "error getting config info")
|
||||
}
|
||||
} else {
|
||||
pullInfo, err := r.getPullStruct(srcRef, manifest[0].RepoTags[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pullStructs = append(pullStructs, pullInfo)
|
||||
}
|
||||
|
||||
} else if srcRef.Transport().Name() == OCIArchive {
|
||||
// retrieve the manifest from index.json to access the image name
|
||||
manifest, err := ociarchive.LoadManifestDescriptor(srcRef)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error loading manifest for %q", srcRef)
|
||||
return nil, errors.Wrapf(err, "error loading manifest for %q", srcRef)
|
||||
}
|
||||
|
||||
if manifest.Annotations == nil || manifest.Annotations["org.opencontainers.image.ref.name"] == "" {
|
||||
return errors.Errorf("error, archive doesn't have a name annotation. Cannot store image with no name")
|
||||
return nil, errors.Errorf("error, archive doesn't have a name annotation. Cannot store image with no name")
|
||||
}
|
||||
images = append(images, manifest.Annotations["org.opencontainers.image.ref.name"])
|
||||
pullInfo, err := r.getPullStruct(srcRef, manifest.Annotations["org.opencontainers.image.ref.name"])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pullStructs = append(pullStructs, pullInfo)
|
||||
} else if srcRef.Transport().Name() == DirTransport {
|
||||
// supports pull from a directory
|
||||
image := splitArr[1]
|
||||
// remove leading "/"
|
||||
if image[:1] == "/" {
|
||||
image = image[1:]
|
||||
}
|
||||
pullInfo, err := r.getPullStruct(srcRef, image)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pullStructs = append(pullStructs, pullInfo)
|
||||
} else {
|
||||
images = append(images, imgName)
|
||||
pullInfo, err := r.getPullStruct(srcRef, imgName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pullStructs = append(pullStructs, pullInfo)
|
||||
}
|
||||
return pullStructs, nil
|
||||
}
|
||||
|
||||
// PullImage pulls an image from configured registries
|
||||
// By default, only the latest tag (or a specific tag if requested) will be
|
||||
// pulled. If allTags is true, all tags for the requested image will be pulled.
|
||||
// Signature validation will be performed if the Runtime has been appropriately
|
||||
// configured
|
||||
func (r *Runtime) PullImage(imgName string, options CopyOptions) error {
|
||||
r.lock.Lock()
|
||||
defer r.lock.Unlock()
|
||||
|
||||
if !r.valid {
|
||||
return ErrRuntimeStopped
|
||||
}
|
||||
|
||||
// PullImage copies the image from the source to the destination
|
||||
var pullStructs []*pullStruct
|
||||
|
||||
signaturePolicyPath := r.config.SignaturePolicyPath
|
||||
if options.SignaturePolicyPath != "" {
|
||||
signaturePolicyPath = options.SignaturePolicyPath
|
||||
}
|
||||
|
||||
sc := common.GetSystemContext(signaturePolicyPath, options.AuthFile)
|
||||
|
||||
srcRef, err := alltransports.ParseImageName(imgName)
|
||||
if err != nil {
|
||||
// could be trying to pull from registry with short name
|
||||
pullStructs, err = getRegistriesToTry(imgName, r.store)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error getting default registries to try")
|
||||
}
|
||||
} else {
|
||||
pullStructs, err = r.getPullListFromRef(srcRef, imgName, sc)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error getting pullStruct info to pull image %q", imgName)
|
||||
}
|
||||
}
|
||||
|
||||
policy, err := signature.DefaultPolicy(sc)
|
||||
|
@ -171,25 +325,21 @@ func (r *Runtime) PullImage(imgName string, allTags bool, signaturePolicyPath st
|
|||
}
|
||||
defer policyContext.Destroy()
|
||||
|
||||
copyOptions := common.GetCopyOptions(reportWriter, signaturePolicyPath, nil, nil, common.SigningOptions{})
|
||||
for _, image := range images {
|
||||
reference := image
|
||||
if srcRef.DockerReference() != nil {
|
||||
reference = srcRef.DockerReference().String()
|
||||
}
|
||||
destRef, err := is.Transport.ParseStoreReference(r.store, reference)
|
||||
if err != nil {
|
||||
return errors.Errorf("error parsing dest reference name: %v", err)
|
||||
}
|
||||
if err = cp.Image(policyContext, destRef, srcRef, copyOptions); err != nil {
|
||||
return errors.Errorf("error loading image %q: %v", image, err)
|
||||
copyOptions := common.GetCopyOptions(options.Writer, signaturePolicyPath, &options.DockerRegistryOptions, nil, options.SigningOptions, options.AuthFile)
|
||||
|
||||
for _, imageInfo := range pullStructs {
|
||||
fmt.Printf("Trying to pull %s...\n", imageInfo.image)
|
||||
if err = cp.Image(policyContext, imageInfo.dstRef, imageInfo.srcRef, copyOptions); err != nil {
|
||||
fmt.Println("Failed")
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return errors.Wrapf(err, "error pulling image from %q", imgName)
|
||||
}
|
||||
|
||||
// PushImage pushes the given image to a location described by the given path
|
||||
func (r *Runtime) PushImage(source string, destination string, options CopyOptions, reportWriter io.Writer) error {
|
||||
func (r *Runtime) PushImage(source string, destination string, options CopyOptions) error {
|
||||
r.lock.Lock()
|
||||
defer r.lock.Unlock()
|
||||
|
||||
|
@ -214,9 +364,16 @@ func (r *Runtime) PushImage(source string, destination string, options CopyOptio
|
|||
signaturePolicyPath = options.SignaturePolicyPath
|
||||
}
|
||||
|
||||
policyContext, err := common.GetPolicyContext(signaturePolicyPath)
|
||||
sc := common.GetSystemContext(signaturePolicyPath, options.AuthFile)
|
||||
|
||||
policy, err := signature.DefaultPolicy(sc)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "Could not get default policy context for signature policy path %q", signaturePolicyPath)
|
||||
return err
|
||||
}
|
||||
|
||||
policyContext, err := signature.NewPolicyContext(policy)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer policyContext.Destroy()
|
||||
// Look up the image name and its layer, then build the imagePushData from
|
||||
|
@ -239,7 +396,7 @@ func (r *Runtime) PushImage(source string, destination string, options CopyOptio
|
|||
return errors.Wrapf(err, "error copying layers and metadata")
|
||||
}
|
||||
|
||||
copyOptions := common.GetCopyOptions(reportWriter, signaturePolicyPath, nil, &options.DockerRegistryOptions, options.SigningOptions)
|
||||
copyOptions := common.GetCopyOptions(options.Writer, signaturePolicyPath, nil, &options.DockerRegistryOptions, options.SigningOptions, options.AuthFile)
|
||||
|
||||
// Copy the image to the remote destination
|
||||
err = cp.Image(policyContext, dest, src, copyOptions)
|
||||
|
|
|
@ -76,3 +76,63 @@ function teardown() {
|
|||
echo "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
@test "kpod pull from docker-archive" {
|
||||
run ${KPOD_BINARY} ${KPOD_OPTIONS} pull alpine
|
||||
echo "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
run ${KPOD_BINARY} ${KPOD_OPTIONS} save -o alp.tar alpine
|
||||
echo "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
run ${KPOD_BINARY} ${KPOD_OPTIONS} rmi alpine
|
||||
echo "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
run ${KPOD_BINARY} ${KPOD_OPTIONS} pull docker-archive:alp.tar
|
||||
echo "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
run ${KPOD_BINARY} ${KPOD_OPTIONS} rmi alpine
|
||||
echo "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
rm -f alp.tar
|
||||
}
|
||||
|
||||
@test "kpod pull from oci-archive" {
|
||||
run ${KPOD_BINARY} ${KPOD_OPTIONS} pull alpine
|
||||
echo "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
run ${KPOD_BINARY} ${KPOD_OPTIONS} save --format oci-archive -o oci-alp.tar alpine
|
||||
echo "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
run ${KPOD_BINARY} ${KPOD_OPTIONS} rmi alpine
|
||||
echo "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
run ${KPOD_BINARY} ${KPOD_OPTIONS} pull oci-archive:oci-alp.tar
|
||||
echo "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
run ${KPOD_BINARY} ${KPOD_OPTIONS} rmi alpine
|
||||
echo "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
rm -f oci-alp.tar
|
||||
}
|
||||
|
||||
@test "kpod pull from local directory" {
|
||||
run ${KPOD_BINARY} ${KPOD_OPTIONS} pull alpine
|
||||
echo "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
run mkdir test_pull_dir
|
||||
echo "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
run ${KPOD_BINARY} ${KPOD_OPTIONS} push alpine dir:test_pull_dir
|
||||
echo "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
run ${KPOD_BINARY} ${KPOD_OPTIONS} rmi alpine
|
||||
echo "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
run ${KPOD_BINARY} ${KPOD_OPTIONS} pull dir:test_pull_dir
|
||||
echo "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
run ${KPOD_BINARY} ${KPOD_OPTIONS} rmi test_pull_dir
|
||||
echo "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
rm -rf test_pull_dir
|
||||
}
|
||||
|
|
|
@ -49,17 +49,14 @@ function teardown() {
|
|||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
@test "kpod push to oci without compression" {
|
||||
@test "kpod push to oci-archive without compression" {
|
||||
run ${KPOD_BINARY} $KPOD_OPTIONS pull "$IMAGE"
|
||||
echo "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
run mkdir /tmp/oci-busybox
|
||||
run ${KPOD_BINARY} $KPOD_OPTIONS push "$IMAGE" oci-archive:/tmp/oci-busybox.tar:alpine
|
||||
echo "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
run ${KPOD_BINARY} $KPOD_OPTIONS push "$IMAGE" oci:/tmp/oci-busybox:busybox
|
||||
echo "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
rm -rf /tmp/oci-busybox
|
||||
rm -f /tmp/oci-busybox.tar
|
||||
run ${KPOD_BINARY} $KPOD_OPTIONS rmi "$IMAGE"
|
||||
echo "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
|
|
Loading…
Reference in a new issue