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:
umohnani8 2017-10-13 17:04:57 -04:00
parent 0914a7a667
commit d855e2c8ad
13 changed files with 428 additions and 210 deletions

View file

@ -95,14 +95,19 @@ func loadCmd(c *cli.Context) error {
output = os.Stdout output = os.Stdout
} }
options := libpod.CopyOptions{
SignaturePolicyPath: c.String("signature-policy"),
Writer: output,
}
src := libpod.DockerArchive + ":" + input 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 src = libpod.OCIArchive + ":" + input
// generate full src name with specified image:tag // generate full src name with specified image:tag
if image != "" { if image != "" {
src = src + ":" + 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) return errors.Wrapf(err, "error pulling %q", src)
} }
} }

View file

@ -38,6 +38,9 @@ func logoutCmd(c *cli.Context) error {
if len(args) > 1 { if len(args) > 1 {
return errors.Errorf("too many arguments, logout takes only 1 argument") 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 var server string
if len(args) == 1 { if len(args) == 1 {
server = args[0] server = args[0]

View file

@ -1,14 +1,14 @@
package main package main
import ( import (
"fmt"
"os" "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/containers/image/types"
"github.com/kubernetes-incubator/cri-o/libpod"
"github.com/kubernetes-incubator/cri-o/libpod/common"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli" "github.com/urfave/cli"
@ -16,16 +16,18 @@ import (
var ( var (
pullFlags = []cli.Flag{ 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{ cli.StringFlag{
Name: "signature-policy", Name: "signature-policy",
Usage: "`pathname` of signature policy file (not usually used)", 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" + 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 // pullCmd gets the data from the command line and calls pullImage
// to copy an image from a registry to a local machine // to copy an image from a registry to a local machine
func pullCmd(c *cli.Context) error { 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() args := c.Args()
if len(args) == 0 { if len(args) == 0 {
@ -132,31 +65,34 @@ func pullCmd(c *cli.Context) error {
return err return err
} }
image := args[0] 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 { var registryCreds *types.DockerAuthConfig
return errors.Wrapf(err, "could not create runtime") if c.String("creds") != "" {
} creds, err := common.ParseRegistryCreds(c.String("creds"))
for _, fqname := range fqRegistries { if err != nil {
fmt.Printf("Trying to pull %s...", fqname) if err == common.ErrNoPassword {
if err := runtime.PullImage(fqname, c.Bool("all-tags"), c.String("signature-policy"), os.Stdout); err != nil { fmt.Print("Password: ")
fmt.Printf(" Failed\n") password, err := terminal.ReadPassword(0)
} else { if err != nil {
return 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)
} }

View file

@ -45,6 +45,10 @@ var (
Name: "quiet, q", Name: "quiet, q",
Usage: "don't output progress information when pushing images", 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(` pushDescription = fmt.Sprintf(`
Pushes an image to a specified location. Pushes an image to a specified location.
@ -120,7 +124,9 @@ func pushCmd(c *cli.Context) error {
RemoveSignatures: removeSignatures, RemoveSignatures: removeSignatures,
SignBy: signBy, SignBy: signBy,
}, },
AuthFile: c.String("authfile"),
Writer: writer,
} }
return runtime.PushImage(srcName, destName, options, writer) return runtime.PushImage(srcName, destName, options)
} }

View file

@ -83,13 +83,14 @@ func saveCmd(c *cli.Context) error {
saveOpts := libpod.CopyOptions{ saveOpts := libpod.CopyOptions{
SignaturePolicyPath: "", SignaturePolicyPath: "",
Writer: writer,
} }
// only one image is supported for now // only one image is supported for now
// future pull requests will fix this // future pull requests will fix this
for _, image := range args { for _, image := range args {
dest := dst + ":" + image 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) return errors.Wrapf(err, "unable to save %q", image)
} }
} }

View file

@ -170,6 +170,8 @@ _kpod_logs() {
_kpod_pull() { _kpod_pull() {
local options_with_args=" local options_with_args="
--authfile
--creds
--signature-policy --signature-policy
" "
local boolean_options=" local boolean_options="
@ -222,18 +224,20 @@ _kpod_mount() {
_kpod_push() { _kpod_push() {
local boolean_options=" local boolean_options="
--disable-compression --disable-compression
-D -D
--quiet --quiet
-q -q
--signature-policy --remove-signatures
--certs --tls-verify
--tls-verify
--remove-signatures
--sign-by
" "
local options_with_args=" local options_with_args="
--authfile
--cert-dir
--creds
--sign-by
--signature-policy
" "
local all_options="$options_with_args $boolean_options" local all_options="$options_with_args $boolean_options"

View file

@ -21,10 +21,10 @@ images from archives and local storage using different transports.
## imageID ## imageID
Image stored in local container/storage Image stored in local container/storage
## DESTINATION ## SOURCE
The DESTINATION is a location to store container images The SOURCE is a location to get container images
The Image "DESTINATION" uses a "transport":"details" format. The Image "SOURCE" uses a "transport":"details" format.
Multiple transports are supported: 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. 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_ **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_] **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. 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_ **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). 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_. An image _tag_ in a directory compliant with "Open Container Image Layout Specification" at _path_.
**ostree:**_image_[**@**_/absolute/repo/path_] **ostree:**_image_[**@**_/absolute/repo/path_]
@ -54,6 +54,14 @@ Image stored in local container/storage
## OPTIONS ## 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"** **--signature-policy="PATHNAME"**
Pathname of a signature policy file to use. It is not recommended that this 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 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 ## SEE ALSO
kpod(1), crio(8), crio.conf(5) kpod(1), kpod-push(1), crio(8), crio.conf(5)
## HISTORY ## HISTORY
July 2017, Originally compiled by Urvashi Mohnani <umohnani@redhat.com> July 2017, Originally compiled by Urvashi Mohnani <umohnani@redhat.com>

View file

@ -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. 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_ **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_] **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. 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_ **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). 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_. An image _tag_ in a directory compliant with "Open Container Image Layout Specification" at _path_.
**ostree:**_image_[**@**_/absolute/repo/path_] **ostree:**_image_[**@**_/absolute/repo/path_]
@ -42,6 +42,10 @@ Image stored in local container/storage
## OPTIONS ## OPTIONS
**--authfile**
Path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json
**--creds="CREDENTIALS"** **--creds="CREDENTIALS"**
Credentials (USERNAME:PASSWORD) to use for authenticating to a registry 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. 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 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` `# 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 ## SEE ALSO
kpod(1) kpod(1), kpod-pull(1), crio(8), crio.conf(5)

View file

@ -17,15 +17,15 @@ var (
) )
// GetCopyOptions constructs a new containers/image/copy.Options{} struct from the given parameters // 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 { if srcDockerRegistry == nil {
srcDockerRegistry = &DockerRegistryOptions{} srcDockerRegistry = &DockerRegistryOptions{}
} }
if destDockerRegistry == nil { if destDockerRegistry == nil {
destDockerRegistry = &DockerRegistryOptions{} destDockerRegistry = &DockerRegistryOptions{}
} }
srcContext := srcDockerRegistry.GetSystemContext(signaturePolicyPath) srcContext := srcDockerRegistry.GetSystemContext(signaturePolicyPath, authFile)
destContext := destDockerRegistry.GetSystemContext(signaturePolicyPath) destContext := destDockerRegistry.GetSystemContext(signaturePolicyPath, authFile)
return &cp.Options{ return &cp.Options{
RemoveSignatures: signing.RemoveSignatures, RemoveSignatures: signing.RemoveSignatures,
SignBy: signing.SignBy, SignBy: signing.SignBy,

View file

@ -22,12 +22,13 @@ type DockerRegistryOptions struct {
// GetSystemContext constructs a new system context from the given signaturePolicy path and the // GetSystemContext constructs a new system context from the given signaturePolicy path and the
// values in the DockerRegistryOptions // values in the DockerRegistryOptions
func (o DockerRegistryOptions) GetSystemContext(signaturePolicyPath string) *types.SystemContext { func (o DockerRegistryOptions) GetSystemContext(signaturePolicyPath, authFile string) *types.SystemContext {
sc := &types.SystemContext{ sc := &types.SystemContext{
SignaturePolicyPath: signaturePolicyPath, SignaturePolicyPath: signaturePolicyPath,
DockerAuthConfig: o.DockerRegistryCreds, DockerAuthConfig: o.DockerRegistryCreds,
DockerCertPath: o.DockerCertPath, DockerCertPath: o.DockerCertPath,
DockerInsecureSkipTLSVerify: o.DockerInsecureSkipTLSVerify, DockerInsecureSkipTLSVerify: o.DockerInsecureSkipTLSVerify,
AuthFilePath: authFile,
} }
return sc return sc
} }

View file

@ -4,6 +4,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
"os"
"strings" "strings"
"syscall" "syscall"
"time" "time"
@ -14,6 +15,7 @@ import (
"github.com/containers/image/docker/tarfile" "github.com/containers/image/docker/tarfile"
"github.com/containers/image/manifest" "github.com/containers/image/manifest"
ociarchive "github.com/containers/image/oci/archive" ociarchive "github.com/containers/image/oci/archive"
"github.com/containers/image/pkg/sysregistries"
"github.com/containers/image/signature" "github.com/containers/image/signature"
is "github.com/containers/image/storage" is "github.com/containers/image/storage"
"github.com/containers/image/transports" "github.com/containers/image/transports"
@ -42,6 +44,9 @@ var (
// OCIArchive is the transport we prepend to an image name // OCIArchive is the transport we prepend to an image name
// when saving to oci-archive // when saving to oci-archive
OCIArchive = ociarchive.Transport.Name() 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 // 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 // SigningPolicyPath this points to a alternative signature policy file, used mainly for testing
SignaturePolicyPath string 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 // Image API
@ -76,45 +85,124 @@ type ImageFilterParams struct {
ImageInput string 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 // 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 // command output. Images to be outputted are tested using the function. A true
// return will include the image, a false return will exclude it. // return will include the image, a false return will exclude it.
type ImageFilter func(*storage.Image, *types.ImageInspectInfo) bool type ImageFilter func(*storage.Image, *types.ImageInspectInfo) bool
// PullImage pulls an image from configured registries func (ips imageDecomposeStruct) returnFQName() string {
// By default, only the latest tag (or a specific tag if requested) will be return fmt.Sprintf("%s%s/%s:%s", ips.transport, ips.registry, ips.imageName, ips.tag)
// 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()
if !r.valid { func getRegistriesToTry(image string, store storage.Store) ([]*pullStruct, error) {
return ErrRuntimeStopped var pStructs []*pullStruct
} var imageError = fmt.Sprintf("unable to parse '%s'\n", image)
imgRef, err := reference.Parse(image)
// 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)
if err != nil { if err != nil {
defaultName := DefaultRegistry + imgName return nil, errors.Wrapf(err, imageError)
srcRef2, err2 := alltransports.ParseImageName(defaultName) }
if err2 != nil { tagged, isTagged := imgRef.(reference.NamedTagged)
return errors.Errorf("error parsing image name %q: %v", defaultName, err2) 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, ":") splitArr := strings.Split(imgName, ":")
archFile := splitArr[len(splitArr)-1] archFile := splitArr[len(splitArr)-1]
@ -123,41 +211,107 @@ func (r *Runtime) PullImage(imgName string, allTags bool, signaturePolicyPath st
tarSource := tarfile.NewSource(archFile) tarSource := tarfile.NewSource(archFile)
manifest, err := tarSource.LoadTarManifest() manifest, err := tarSource.LoadTarManifest()
if err != nil { 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 // to pull the first image stored in the tar file
for i := range manifest { if len(manifest) == 0 {
if manifest[i].RepoTags != nil { // create an image object and use the hex value of the digest as the image ID
images = append(images, manifest[i].RepoTags[0]) // for parsing the store reference
} else { newImg, err := srcRef.NewImage(sc)
// create an image object and use the hex value of the digest as the image ID if err != nil {
// for parsing the store reference return nil, err
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")
}
} }
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 { } else if srcRef.Transport().Name() == OCIArchive {
// retrieve the manifest from index.json to access the image name // retrieve the manifest from index.json to access the image name
manifest, err := ociarchive.LoadManifestDescriptor(srcRef) manifest, err := ociarchive.LoadManifestDescriptor(srcRef)
if err != nil { 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"] == "" { 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 { } 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) policy, err := signature.DefaultPolicy(sc)
@ -171,25 +325,21 @@ func (r *Runtime) PullImage(imgName string, allTags bool, signaturePolicyPath st
} }
defer policyContext.Destroy() defer policyContext.Destroy()
copyOptions := common.GetCopyOptions(reportWriter, signaturePolicyPath, nil, nil, common.SigningOptions{}) copyOptions := common.GetCopyOptions(options.Writer, signaturePolicyPath, &options.DockerRegistryOptions, nil, options.SigningOptions, options.AuthFile)
for _, image := range images {
reference := image for _, imageInfo := range pullStructs {
if srcRef.DockerReference() != nil { fmt.Printf("Trying to pull %s...\n", imageInfo.image)
reference = srcRef.DockerReference().String() if err = cp.Image(policyContext, imageInfo.dstRef, imageInfo.srcRef, copyOptions); err != nil {
} fmt.Println("Failed")
destRef, err := is.Transport.ParseStoreReference(r.store, reference) } else {
if err != nil { return 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)
} }
} }
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 // 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() r.lock.Lock()
defer r.lock.Unlock() defer r.lock.Unlock()
@ -214,9 +364,16 @@ func (r *Runtime) PushImage(source string, destination string, options CopyOptio
signaturePolicyPath = options.SignaturePolicyPath signaturePolicyPath = options.SignaturePolicyPath
} }
policyContext, err := common.GetPolicyContext(signaturePolicyPath) sc := common.GetSystemContext(signaturePolicyPath, options.AuthFile)
policy, err := signature.DefaultPolicy(sc)
if err != nil { 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() defer policyContext.Destroy()
// Look up the image name and its layer, then build the imagePushData from // 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") 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 // Copy the image to the remote destination
err = cp.Image(policyContext, dest, src, copyOptions) err = cp.Image(policyContext, dest, src, copyOptions)

View file

@ -76,3 +76,63 @@ function teardown() {
echo "$output" echo "$output"
[ "$status" -eq 0 ] [ "$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
}

View file

@ -49,17 +49,14 @@ function teardown() {
[ "$status" -eq 0 ] [ "$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" run ${KPOD_BINARY} $KPOD_OPTIONS pull "$IMAGE"
echo "$output" echo "$output"
[ "$status" -eq 0 ] [ "$status" -eq 0 ]
run mkdir /tmp/oci-busybox run ${KPOD_BINARY} $KPOD_OPTIONS push "$IMAGE" oci-archive:/tmp/oci-busybox.tar:alpine
echo "$output" echo "$output"
[ "$status" -eq 0 ] [ "$status" -eq 0 ]
run ${KPOD_BINARY} $KPOD_OPTIONS push "$IMAGE" oci:/tmp/oci-busybox:busybox rm -f /tmp/oci-busybox.tar
echo "$output"
[ "$status" -eq 0 ]
rm -rf /tmp/oci-busybox
run ${KPOD_BINARY} $KPOD_OPTIONS rmi "$IMAGE" run ${KPOD_BINARY} $KPOD_OPTIONS rmi "$IMAGE"
echo "$output" echo "$output"
[ "$status" -eq 0 ] [ "$status" -eq 0 ]