Merge pull request #144 from mtrmac/reference-abstraction

Reference abstraction
This commit is contained in:
Miloslav Trmač 2016-07-18 16:45:59 +02:00 committed by GitHub
commit 177463ed03
21 changed files with 668 additions and 271 deletions

View File

@ -6,6 +6,7 @@ import (
"github.com/containers/image/image"
"github.com/containers/image/signature"
"github.com/containers/image/transports"
"github.com/urfave/cli"
)
@ -58,9 +59,9 @@ func copyHandler(context *cli.Context) error {
if err != nil {
return fmt.Errorf("Error initializing GPG: %v", err)
}
dockerReference := dest.CanonicalDockerReference()
dockerReference := dest.Reference().DockerReference()
if dockerReference == nil {
return errors.New("Destination image does not have an associated Docker reference")
return fmt.Errorf("Cannot determine canonical Docker reference for destination %s", transports.ImageName(dest.Reference()))
}
newSig, err := signature.SignDockerManifest(manifest, dockerReference.String(), mech, signBy)

View File

@ -37,7 +37,11 @@ var layersCmd = cli.Command{
if err != nil {
return err
}
dest := directory.NewImageDestination(tmpDir)
tmpDirRef := directory.NewReference(tmpDir)
dest, err := tmpDirRef.NewImageDestination("", true)
if err != nil {
return err
}
manifest, _, err := src.Manifest()
if err != nil {
return err

View File

@ -1,30 +1,11 @@
package main
import (
"errors"
"fmt"
"strings"
"github.com/containers/image/directory"
"github.com/containers/image/docker"
"github.com/containers/image/image"
"github.com/containers/image/oci"
"github.com/containers/image/openshift"
"github.com/containers/image/transports"
"github.com/containers/image/types"
"github.com/urfave/cli"
)
const (
// atomicPrefix is the URL-like schema prefix used for Atomic registry image references.
atomicPrefix = "atomic:"
// dockerPrefix is the URL-like schema prefix used for Docker image references.
dockerPrefix = "docker://"
// directoryPrefix is the URL-like schema prefix used for local directories (for debugging)
directoryPrefix = "dir:"
// ociPrefix is the URL-like schema prefix used for OCI images.
ociPrefix = "oci:"
)
// ParseImage converts image URL-like string to an initialized handler for that image.
func parseImage(c *cli.Context) (types.Image, error) {
var (
@ -32,16 +13,11 @@ func parseImage(c *cli.Context) (types.Image, error) {
certPath = c.GlobalString("cert-path")
tlsVerify = c.GlobalBool("tls-verify")
)
switch {
case strings.HasPrefix(imgName, dockerPrefix):
return docker.NewImage(strings.TrimPrefix(imgName, dockerPrefix), certPath, tlsVerify)
//case strings.HasPrefix(img, appcPrefix):
//
case strings.HasPrefix(imgName, directoryPrefix):
src := directory.NewImageSource(strings.TrimPrefix(imgName, directoryPrefix))
return image.FromSource(src, nil), nil
ref, err := transports.ParseImageName(imgName)
if err != nil {
return nil, err
}
return nil, errors.New("no valid prefix provided")
return ref.NewImage(certPath, tlsVerify)
}
// parseImageSource converts image URL-like string to an ImageSource.
@ -50,15 +26,11 @@ func parseImageSource(c *cli.Context, name string) (types.ImageSource, error) {
certPath = c.GlobalString("cert-path")
tlsVerify = c.GlobalBool("tls-verify") // FIXME!! defaults to false?
)
switch {
case strings.HasPrefix(name, dockerPrefix):
return docker.NewImageSource(strings.TrimPrefix(name, dockerPrefix), certPath, tlsVerify)
case strings.HasPrefix(name, atomicPrefix):
return openshift.NewImageSource(strings.TrimPrefix(name, atomicPrefix), certPath, tlsVerify)
case strings.HasPrefix(name, directoryPrefix):
return directory.NewImageSource(strings.TrimPrefix(name, directoryPrefix)), nil
ref, err := transports.ParseImageName(name)
if err != nil {
return nil, err
}
return nil, fmt.Errorf("Unrecognized image reference %s", name)
return ref.NewImageSource(certPath, tlsVerify)
}
// parseImageDestination converts image URL-like string to an ImageDestination.
@ -67,15 +39,9 @@ func parseImageDestination(c *cli.Context, name string) (types.ImageDestination,
certPath = c.GlobalString("cert-path")
tlsVerify = c.GlobalBool("tls-verify") // FIXME!! defaults to false?
)
switch {
case strings.HasPrefix(name, dockerPrefix):
return docker.NewImageDestination(strings.TrimPrefix(name, dockerPrefix), certPath, tlsVerify)
case strings.HasPrefix(name, atomicPrefix):
return openshift.NewImageDestination(strings.TrimPrefix(name, atomicPrefix), certPath, tlsVerify)
case strings.HasPrefix(name, directoryPrefix):
return directory.NewImageDestination(strings.TrimPrefix(name, directoryPrefix)), nil
case strings.HasPrefix(name, ociPrefix):
return oci.NewImageDestination(strings.TrimPrefix(name, ociPrefix))
ref, err := transports.ParseImageName(name)
if err != nil {
return nil, err
}
return nil, fmt.Errorf("Unrecognized image reference %s", name)
return ref.NewImageDestination(certPath, tlsVerify)
}

View File

@ -6,20 +6,21 @@ import (
"os"
"github.com/containers/image/types"
"github.com/docker/docker/reference"
)
type dirImageDestination struct {
dir string
ref dirReference
}
// NewImageDestination returns an ImageDestination for writing to an existing directory.
func NewImageDestination(dir string) types.ImageDestination {
return &dirImageDestination{dir}
// newImageDestination returns an ImageDestination for writing to an existing directory.
func newImageDestination(ref dirReference) types.ImageDestination {
return &dirImageDestination{ref}
}
func (d *dirImageDestination) CanonicalDockerReference() reference.Named {
return nil
// Reference returns the reference used to set up this destination. Note that this should directly correspond to user's intent,
// e.g. it should use the public hostname instead of the result of resolving CNAMEs or following redirects.
func (d *dirImageDestination) Reference() types.ImageReference {
return d.ref
}
func (d *dirImageDestination) SupportedManifestMIMETypes() []string {
@ -27,11 +28,11 @@ func (d *dirImageDestination) SupportedManifestMIMETypes() []string {
}
func (d *dirImageDestination) PutManifest(manifest []byte) error {
return ioutil.WriteFile(manifestPath(d.dir), manifest, 0644)
return ioutil.WriteFile(manifestPath(d.ref.path), manifest, 0644)
}
func (d *dirImageDestination) PutBlob(digest string, stream io.Reader) error {
layerFile, err := os.Create(layerPath(d.dir, digest))
layerFile, err := os.Create(layerPath(d.ref.path, digest))
if err != nil {
return err
}
@ -47,7 +48,7 @@ func (d *dirImageDestination) PutBlob(digest string, stream io.Reader) error {
func (d *dirImageDestination) PutSignatures(signatures [][]byte) error {
for i, sig := range signatures {
if err := ioutil.WriteFile(signaturePath(d.dir, i), sig, 0644); err != nil {
if err := ioutil.WriteFile(signaturePath(d.ref.path, i), sig, 0644); err != nil {
return err
}
}

View File

@ -7,29 +7,26 @@ import (
"os"
"github.com/containers/image/types"
"github.com/docker/docker/reference"
)
type dirImageSource struct {
dir string
ref dirReference
}
// NewImageSource returns an ImageSource reading from an existing directory.
func NewImageSource(dir string) types.ImageSource {
return &dirImageSource{dir}
// newImageSource returns an ImageSource reading from an existing directory.
func newImageSource(ref dirReference) types.ImageSource {
return &dirImageSource{ref}
}
// IntendedDockerReference returns the Docker reference for this image, _as specified by the user_
// (not as the image itself, or its underlying storage, claims). Should be fully expanded, i.e. !reference.IsNameOnly.
// This can be used e.g. to determine which public keys are trusted for this image.
// May be nil if unknown.
func (s *dirImageSource) IntendedDockerReference() reference.Named {
return nil
// Reference returns the reference used to set up this source, _as specified by the user_
// (not as the image itself, or its underlying storage, claims). This can be used e.g. to determine which public keys are trusted for this image.
func (s *dirImageSource) Reference() types.ImageReference {
return s.ref
}
// it's up to the caller to determine the MIME type of the returned manifest's bytes
func (s *dirImageSource) GetManifest(_ []string) ([]byte, string, error) {
m, err := ioutil.ReadFile(manifestPath(s.dir))
m, err := ioutil.ReadFile(manifestPath(s.ref.path))
if err != nil {
return nil, "", err
}
@ -37,11 +34,11 @@ func (s *dirImageSource) GetManifest(_ []string) ([]byte, string, error) {
}
func (s *dirImageSource) GetBlob(digest string) (io.ReadCloser, int64, error) {
r, err := os.Open(layerPath(s.dir, digest))
r, err := os.Open(layerPath(s.ref.path, digest))
if err != nil {
return nil, 0, nil
}
fi, err := os.Stat(layerPath(s.dir, digest))
fi, err := os.Stat(layerPath(s.ref.path, digest))
if err != nil {
return nil, 0, nil
}
@ -51,7 +48,7 @@ func (s *dirImageSource) GetBlob(digest string) (io.ReadCloser, int64, error) {
func (s *dirImageSource) GetSignatures() ([][]byte, error) {
signatures := [][]byte{}
for i := 0; ; i++ {
signature, err := ioutil.ReadFile(signaturePath(s.dir, i))
signature, err := ioutil.ReadFile(signaturePath(s.ref.path, i))
if err != nil {
if os.IsNotExist(err) {
break

View File

@ -0,0 +1,73 @@
package directory
import (
"github.com/containers/image/image"
"github.com/containers/image/types"
"github.com/docker/docker/reference"
)
// Transport is an ImageTransport for directory paths.
var Transport = dirTransport{}
type dirTransport struct{}
func (t dirTransport) Name() string {
return "dir"
}
// ParseReference converts a string, which should not start with the ImageTransport.Name prefix, into an ImageReference.
func (t dirTransport) ParseReference(reference string) (types.ImageReference, error) {
return NewReference(reference), nil
}
// dirReference is an ImageReference for directory paths.
type dirReference struct {
// Note that the interpretation of paths below depends on the underlying filesystem state, which may change under us at any time!
path string // As specified by the user. May be relative, contain symlinks, etc.
}
// There is no directory.ParseReference because it is rather pointless.
// Callers who need a transport-independent interface will go through
// dirTransport.ParseReference; callers who intentionally deal with directories
// can use directory.NewReference.
// NewReference returns a directory reference for a specified path.
func NewReference(path string) types.ImageReference {
return dirReference{path: path}
}
func (ref dirReference) Transport() types.ImageTransport {
return Transport
}
// StringWithinTransport returns a string representation of the reference, which MUST be such that
// reference.Transport().ParseReference(reference.StringWithinTransport()) returns an equivalent reference.
// NOTE: The returned string is not promised to be equal to the original input to ParseReference;
// e.g. default attribute values omitted by the user may be filled in in the return value, or vice versa.
// WARNING: Do not use the return value in the UI to describe an image, it does not contain the Transport().Name() prefix.
func (ref dirReference) StringWithinTransport() string {
return ref.path
}
// DockerReference returns a Docker reference associated with this reference
// (fully explicit, i.e. !reference.IsNameOnly, but reflecting user intent,
// not e.g. after redirect or alias processing), or nil if unknown/not applicable.
func (ref dirReference) DockerReference() reference.Named {
return nil
}
// NewImage returns a types.Image for this reference.
func (ref dirReference) NewImage(certPath string, tlsVerify bool) (types.Image, error) {
src := newImageSource(ref)
return image.FromSource(src, nil), nil
}
// NewImageSource returns a types.ImageSource for this reference.
func (ref dirReference) NewImageSource(certPath string, tlsVerify bool) (types.ImageSource, error) {
return newImageSource(ref), nil
}
// NewImageDestination returns a types.ImageDestination for this reference.
func (ref dirReference) NewImageDestination(certPath string, tlsVerify bool) (types.ImageDestination, error) {
return newImageDestination(ref), nil
}

View File

@ -16,10 +16,10 @@ type Image struct {
src *dockerImageSource
}
// NewImage returns a new Image interface type after setting up
// newImage returns a new Image interface type after setting up
// a client to the registry hosting the given image.
func NewImage(img, certPath string, tlsVerify bool) (types.Image, error) {
s, err := newDockerImageSource(img, certPath, tlsVerify)
func newImage(ref dockerReference, certPath string, tlsVerify bool) (types.Image, error) {
s, err := newImageSource(ref, certPath, tlsVerify)
if err != nil {
return nil, err
}
@ -28,12 +28,12 @@ func NewImage(img, certPath string, tlsVerify bool) (types.Image, error) {
// SourceRefFullName returns a fully expanded name for the repository this image is in.
func (i *Image) SourceRefFullName() string {
return i.src.ref.FullName()
return i.src.ref.ref.FullName()
}
// GetRepositoryTags list all tags available in the repository. Note that this has no connection with the tag(s) used for this specific image, if any.
func (i *Image) GetRepositoryTags() ([]string, error) {
url := fmt.Sprintf(tagsURL, i.src.ref.RemoteName())
url := fmt.Sprintf(tagsURL, i.src.ref.ref.RemoteName())
res, err := i.src.c.makeRequest("GET", url, nil, nil)
if err != nil {
return nil, err

View File

@ -10,21 +10,16 @@ import (
"github.com/Sirupsen/logrus"
"github.com/containers/image/manifest"
"github.com/containers/image/types"
"github.com/docker/docker/reference"
)
type dockerImageDestination struct {
ref reference.Named
ref dockerReference
c *dockerClient
}
// NewImageDestination creates a new ImageDestination for the specified image and connection specification.
func NewImageDestination(img, certPath string, tlsVerify bool) (types.ImageDestination, error) {
ref, err := parseImageName(img)
if err != nil {
return nil, err
}
c, err := newDockerClient(ref.Hostname(), certPath, tlsVerify)
// newImageDestination creates a new ImageDestination for the specified image reference and connection specification.
func newImageDestination(ref dockerReference, certPath string, tlsVerify bool) (types.ImageDestination, error) {
c, err := newDockerClient(ref.ref.Hostname(), certPath, tlsVerify)
if err != nil {
return nil, err
}
@ -34,6 +29,12 @@ func NewImageDestination(img, certPath string, tlsVerify bool) (types.ImageDesti
}, nil
}
// Reference returns the reference used to set up this destination. Note that this should directly correspond to user's intent,
// e.g. it should use the public hostname instead of the result of resolving CNAMEs or following redirects.
func (d *dockerImageDestination) Reference() types.ImageReference {
return d.ref
}
func (d *dockerImageDestination) SupportedManifestMIMETypes() []string {
return []string{
// TODO(runcom): we'll add OCI as part of another PR here
@ -43,10 +44,6 @@ func (d *dockerImageDestination) SupportedManifestMIMETypes() []string {
}
}
func (d *dockerImageDestination) CanonicalDockerReference() reference.Named {
return d.ref
}
func (d *dockerImageDestination) PutManifest(m []byte) error {
// FIXME: This only allows upload by digest, not creating a tag. See the
// corresponding comment in openshift.NewImageDestination.
@ -54,7 +51,7 @@ func (d *dockerImageDestination) PutManifest(m []byte) error {
if err != nil {
return err
}
url := fmt.Sprintf(manifestURL, d.ref.RemoteName(), digest)
url := fmt.Sprintf(manifestURL, d.ref.ref.RemoteName(), digest)
headers := map[string][]string{}
mimeType := manifest.GuessMIMEType(m)
@ -78,7 +75,7 @@ func (d *dockerImageDestination) PutManifest(m []byte) error {
}
func (d *dockerImageDestination) PutBlob(digest string, stream io.Reader) error {
checkURL := fmt.Sprintf(blobsURL, d.ref.RemoteName(), digest)
checkURL := fmt.Sprintf(blobsURL, d.ref.ref.RemoteName(), digest)
logrus.Debugf("Checking %s", checkURL)
res, err := d.c.makeRequest("HEAD", checkURL, nil, nil)
@ -93,7 +90,7 @@ func (d *dockerImageDestination) PutBlob(digest string, stream io.Reader) error
logrus.Debugf("... failed, status %d", res.StatusCode)
// FIXME? Chunked upload, progress reporting, etc.
uploadURL := fmt.Sprintf(blobUploadURL, d.ref.RemoteName())
uploadURL := fmt.Sprintf(blobUploadURL, d.ref.ref.RemoteName())
logrus.Debugf("Uploading %s", uploadURL)
res, err = d.c.makeRequest("POST", uploadURL, nil, nil)
if err != nil {

View File

@ -11,7 +11,6 @@ import (
"github.com/Sirupsen/logrus"
"github.com/containers/image/manifest"
"github.com/containers/image/types"
"github.com/docker/docker/reference"
)
type errFetchManifest struct {
@ -24,17 +23,13 @@ func (e errFetchManifest) Error() string {
}
type dockerImageSource struct {
ref reference.Named
ref dockerReference
c *dockerClient
}
// newDockerImageSource is the same as NewImageSource, only it returns the more specific *dockerImageSource type.
func newDockerImageSource(img, certPath string, tlsVerify bool) (*dockerImageSource, error) {
ref, err := parseImageName(img)
if err != nil {
return nil, err
}
c, err := newDockerClient(ref.Hostname(), certPath, tlsVerify)
// newImageSource creates a new ImageSource for the specified image reference and connection specification.
func newImageSource(ref dockerReference, certPath string, tlsVerify bool) (*dockerImageSource, error) {
c, err := newDockerClient(ref.ref.Hostname(), certPath, tlsVerify)
if err != nil {
return nil, err
}
@ -44,16 +39,9 @@ func newDockerImageSource(img, certPath string, tlsVerify bool) (*dockerImageSou
}, nil
}
// NewImageSource creates a new ImageSource for the specified image and connection specification.
func NewImageSource(img, certPath string, tlsVerify bool) (types.ImageSource, error) {
return newDockerImageSource(img, certPath, tlsVerify)
}
// IntendedDockerReference returns the Docker reference for this image, _as specified by the user_
// (not as the image itself, or its underlying storage, claims). Should be fully expanded, i.e. !reference.IsNameOnly.
// This can be used e.g. to determine which public keys are trusted for this image.
// May be nil if unknown.
func (s *dockerImageSource) IntendedDockerReference() reference.Named {
// Reference returns the reference used to set up this source, _as specified by the user_
// (not as the image itself, or its underlying storage, claims). This can be used e.g. to determine which public keys are trusted for this image.
func (s *dockerImageSource) Reference() types.ImageReference {
return s.ref
}
@ -71,11 +59,11 @@ func simplifyContentType(contentType string) string {
}
func (s *dockerImageSource) GetManifest(mimetypes []string) ([]byte, string, error) {
reference, err := tagOrDigest(s.ref)
reference, err := tagOrDigest(s.ref.ref)
if err != nil {
return nil, "", err
}
url := fmt.Sprintf(manifestURL, s.ref.RemoteName(), reference)
url := fmt.Sprintf(manifestURL, s.ref.ref.RemoteName(), reference)
// TODO(runcom) set manifest version header! schema1 for now - then schema2 etc etc and v1
// TODO(runcom) NO, switch on the resulter manifest like Docker is doing
headers := make(map[string][]string)
@ -97,7 +85,7 @@ func (s *dockerImageSource) GetManifest(mimetypes []string) ([]byte, string, err
}
func (s *dockerImageSource) GetBlob(digest string) (io.ReadCloser, int64, error) {
url := fmt.Sprintf(blobsURL, s.ref.RemoteName(), digest)
url := fmt.Sprintf(blobsURL, s.ref.ref.RemoteName(), digest)
logrus.Debugf("Downloading %s", url)
res, err := s.c.makeRequest("GET", url, nil, nil)
if err != nil {
@ -126,11 +114,11 @@ func (s *dockerImageSource) Delete() error {
headers := make(map[string][]string)
headers["Accept"] = []string{manifest.DockerV2Schema2MIMEType}
reference, err := tagOrDigest(s.ref)
reference, err := tagOrDigest(s.ref.ref)
if err != nil {
return err
}
getURL := fmt.Sprintf(manifestURL, s.ref.RemoteName(), reference)
getURL := fmt.Sprintf(manifestURL, s.ref.ref.RemoteName(), reference)
get, err := s.c.makeRequest("GET", getURL, headers, nil)
if err != nil {
return err
@ -143,13 +131,13 @@ func (s *dockerImageSource) Delete() error {
switch get.StatusCode {
case http.StatusOK:
case http.StatusNotFound:
return fmt.Errorf("Unable to delete %v. Image may not exist or is not stored with a v2 Schema in a v2 registry.", s.ref)
return fmt.Errorf("Unable to delete %v. Image may not exist or is not stored with a v2 Schema in a v2 registry.", s.ref.ref)
default:
return fmt.Errorf("Failed to delete %v: %v (%v)", s.ref, body, get.Status)
return fmt.Errorf("Failed to delete %v: %v (%v)", s.ref.ref, body, get.Status)
}
digest := get.Header.Get("Docker-Content-Digest")
deleteURL := fmt.Sprintf(manifestURL, s.ref.RemoteName(), digest)
deleteURL := fmt.Sprintf(manifestURL, s.ref.ref.RemoteName(), digest)
// When retrieving the digest from a registry >= 2.3 use the following header:
// "Accept": "application/vnd.docker.distribution.manifest.v2+json"

View File

@ -0,0 +1,95 @@
package docker
import (
"fmt"
"strings"
"github.com/containers/image/types"
"github.com/docker/docker/reference"
)
// Transport is an ImageTransport for Docker references.
var Transport = dockerTransport{}
type dockerTransport struct{}
func (t dockerTransport) Name() string {
return "docker"
}
// ParseReference converts a string, which should not start with the ImageTransport.Name prefix, into an ImageReference.
func (t dockerTransport) ParseReference(reference string) (types.ImageReference, error) {
return ParseReference(reference)
}
// dockerReference is an ImageReference for Docker images.
type dockerReference struct {
ref reference.Named // By construction we know that !reference.IsNameOnly(ref)
}
// ParseReference converts a string, which should not start with the ImageTransport.Name prefix, into an Docker ImageReference.
func ParseReference(refString string) (types.ImageReference, error) {
if !strings.HasPrefix(refString, "//") {
return nil, fmt.Errorf("docker: image reference %s does not start with //", refString)
}
ref, err := reference.ParseNamed(strings.TrimPrefix(refString, "//"))
if err != nil {
return nil, err
}
ref = reference.WithDefaultTag(ref)
return NewReference(ref)
}
// NewReference returns a Docker reference for a named reference. The reference must satisfy !reference.IsNameOnly().
func NewReference(ref reference.Named) (types.ImageReference, error) {
if reference.IsNameOnly(ref) {
return nil, fmt.Errorf("Docker reference %s has neither a tag nor a digest", ref.String())
}
// A github.com/distribution/reference value can have a tag and a digest at the same time!
// docker/reference does not handle that, so fail.
// (Even if it were supported, the semantics of policy namespaces are unclear - should we drop
// the tag or the digest first?)
_, isTagged := ref.(reference.NamedTagged)
_, isDigested := ref.(reference.Canonical)
if isTagged && isDigested {
return nil, fmt.Errorf("Docker references with both a tag and digest are currently not supported")
}
return dockerReference{
ref: ref,
}, nil
}
func (ref dockerReference) Transport() types.ImageTransport {
return Transport
}
// StringWithinTransport returns a string representation of the reference, which MUST be such that
// reference.Transport().ParseReference(reference.StringWithinTransport()) returns an equivalent reference.
// NOTE: The returned string is not promised to be equal to the original input to ParseReference;
// e.g. default attribute values omitted by the user may be filled in in the return value, or vice versa.
// WARNING: Do not use the return value in the UI to describe an image, it does not contain the Transport().Name() prefix.
func (ref dockerReference) StringWithinTransport() string {
return "//" + ref.ref.String()
}
// DockerReference returns a Docker reference associated with this reference
// (fully explicit, i.e. !reference.IsNameOnly, but reflecting user intent,
// not e.g. after redirect or alias processing), or nil if unknown/not applicable.
func (ref dockerReference) DockerReference() reference.Named {
return ref.ref
}
// NewImage returns a types.Image for this reference.
func (ref dockerReference) NewImage(certPath string, tlsVerify bool) (types.Image, error) {
return newImage(ref, certPath, tlsVerify)
}
// NewImageSource returns a types.ImageSource for this reference.
func (ref dockerReference) NewImageSource(certPath string, tlsVerify bool) (types.ImageSource, error) {
return newImageSource(ref, certPath, tlsVerify)
}
// NewImageDestination returns a types.ImageDestination for this reference.
func (ref dockerReference) NewImageDestination(certPath string, tlsVerify bool) (types.ImageDestination, error) {
return newImageDestination(ref, certPath, tlsVerify)
}

View File

@ -6,20 +6,6 @@ import (
"github.com/docker/docker/reference"
)
// parseImageName converts a string into a reference.
// It is guaranteed that reference.IsNameOnly is false for the returned value.
func parseImageName(img string) (reference.Named, error) {
ref, err := reference.ParseNamed(img)
if err != nil {
return nil, err
}
ref = reference.WithDefaultTag(ref)
if reference.IsNameOnly(ref) { // Sanity check that we are fulfulling our contract
return nil, fmt.Errorf("Internal inconsistency: reference.IsNameOnly for reference %s (parsed from %s)", ref.String(), img)
}
return ref, nil
}
// tagOrDigest returns a tag or digest from a reference for which !reference.IsNameOnly.
func tagOrDigest(ref reference.Named) (string, error) {
if ref, ok := ref.(reference.Canonical); ok {

View File

@ -13,7 +13,6 @@ import (
"github.com/containers/image/manifest"
"github.com/containers/image/types"
"github.com/docker/docker/reference"
)
var (
@ -51,12 +50,10 @@ func FromSource(src types.ImageSource, requestedManifestMIMETypes []string) type
return &genericImage{src: src, requestedManifestMIMETypes: requestedManifestMIMETypes}
}
// IntendedDockerReference returns the Docker reference for this image, _as specified by the user_
// (not as the image itself, or its underlying storage, claims). Should be fully expanded, i.e. !reference.IsNameOnly.
// This can be used e.g. to determine which public keys are trusted for this image.
// May be nil if unknown.
func (i *genericImage) IntendedDockerReference() reference.Named {
return i.src.IntendedDockerReference()
// Reference returns the reference used to set up this source, _as specified by the user_
// (not as the image itself, or its underlying storage, claims). This can be used e.g. to determine which public keys are trusted for this image.
func (i *genericImage) Reference() types.ImageReference {
return i.src.Reference()
}
// Manifest is like ImageSource.GetManifest, but the result is cached; it is OK to call this however often you need.

View File

@ -7,12 +7,10 @@ import (
"io/ioutil"
"os"
"path/filepath"
"regexp"
"strings"
"github.com/containers/image/manifest"
"github.com/containers/image/types"
"github.com/docker/docker/reference"
)
type ociManifest struct {
@ -30,32 +28,18 @@ type descriptor struct {
}
type ociImageDestination struct {
dir string
tag string
ref ociReference
}
var refRegexp = regexp.MustCompile(`^([A-Za-z0-9._-]+)+$`)
// NewImageDestination returns an ImageDestination for writing to an existing directory.
func NewImageDestination(dest string) (types.ImageDestination, error) {
dir := dest
sep := strings.LastIndex(dest, ":")
tag := "latest"
if sep != -1 {
dir = dest[:sep]
tag = dest[sep+1:]
if !refRegexp.MatchString(tag) {
return nil, fmt.Errorf("Invalid reference %s", tag)
}
}
return &ociImageDestination{
dir: dir,
tag: tag,
}, nil
// newImageDestination returns an ImageDestination for writing to an existing directory.
func newImageDestination(ref ociReference) types.ImageDestination {
return &ociImageDestination{ref: ref}
}
func (d *ociImageDestination) CanonicalDockerReference() reference.Named {
return nil
// Reference returns the reference used to set up this destination. Note that this should directly correspond to user's intent,
// e.g. it should use the public hostname instead of the result of resolving CNAMEs or following redirects.
func (d *ociImageDestination) Reference() types.ImageReference {
return d.ref
}
func createManifest(m []byte) ([]byte, string, error) {
@ -116,21 +100,21 @@ func (d *ociImageDestination) PutManifest(m []byte) error {
return err
}
if err := ioutil.WriteFile(blobPath(d.dir, digest), ociMan, 0644); err != nil {
if err := ioutil.WriteFile(blobPath(d.ref.dir, digest), ociMan, 0644); err != nil {
return err
}
// TODO(runcom): ugly here?
if err := ioutil.WriteFile(ociLayoutPath(d.dir), []byte(`{"imageLayoutVersion": "1.0.0"}`), 0644); err != nil {
if err := ioutil.WriteFile(ociLayoutPath(d.ref.dir), []byte(`{"imageLayoutVersion": "1.0.0"}`), 0644); err != nil {
return err
}
return ioutil.WriteFile(descriptorPath(d.dir, d.tag), data, 0644)
return ioutil.WriteFile(descriptorPath(d.ref.dir, d.ref.tag), data, 0644)
}
func (d *ociImageDestination) PutBlob(digest string, stream io.Reader) error {
if err := d.ensureParentDirectoryExists("blobs"); err != nil {
return err
}
blob, err := os.Create(blobPath(d.dir, digest))
blob, err := os.Create(blobPath(d.ref.dir, digest))
if err != nil {
return err
}
@ -145,7 +129,7 @@ func (d *ociImageDestination) PutBlob(digest string, stream io.Reader) error {
}
func (d *ociImageDestination) ensureParentDirectoryExists(parent string) error {
path := filepath.Join(d.dir, parent)
path := filepath.Join(d.ref.dir, parent)
if _, err := os.Stat(path); err != nil && os.IsNotExist(err) {
if err := os.MkdirAll(path, 0755); err != nil {
return err

View File

@ -0,0 +1,91 @@
package oci
import (
"errors"
"fmt"
"regexp"
"strings"
"github.com/containers/image/types"
"github.com/docker/docker/reference"
)
// Transport is an ImageTransport for Docker references.
var Transport = ociTransport{}
type ociTransport struct{}
func (t ociTransport) Name() string {
return "oci"
}
// ParseReference converts a string, which should not start with the ImageTransport.Name prefix, into an ImageReference.
func (t ociTransport) ParseReference(reference string) (types.ImageReference, error) {
return ParseReference(reference)
}
// ociReference is an ImageReference for OCI directory paths.
type ociReference struct {
// Note that the interpretation of paths below depends on the underlying filesystem state, which may change under us at any time!
dir string // As specified by the user. May be relative, contain symlinks, etc.
tag string
}
var refRegexp = regexp.MustCompile(`^([A-Za-z0-9._-]+)+$`)
// ParseReference converts a string, which should not start with the ImageTransport.Name prefix, into an OCI ImageReference.
func ParseReference(reference string) (types.ImageReference, error) {
var dir, tag string
sep := strings.LastIndex(reference, ":")
if sep == -1 {
dir = reference
tag = "latest"
} else {
dir = reference[:sep]
tag = reference[sep+1:]
if !refRegexp.MatchString(tag) {
return nil, fmt.Errorf("Invalid tag %s", tag)
}
}
return NewReference(dir, tag), nil
}
// NewReference returns an OCI reference for a directory and a tag.
func NewReference(dir, tag string) types.ImageReference {
return ociReference{dir: dir, tag: tag}
}
func (ref ociReference) Transport() types.ImageTransport {
return Transport
}
// StringWithinTransport returns a string representation of the reference, which MUST be such that
// reference.Transport().ParseReference(reference.StringWithinTransport()) returns an equivalent reference.
// NOTE: The returned string is not promised to be equal to the original input to ParseReference;
// e.g. default attribute values omitted by the user may be filled in in the return value, or vice versa.
// WARNING: Do not use the return value in the UI to describe an image, it does not contain the Transport().Name() prefix.
func (ref ociReference) StringWithinTransport() string {
return fmt.Sprintf("%s:%s", ref.dir, ref.tag)
}
// DockerReference returns a Docker reference associated with this reference
// (fully explicit, i.e. !reference.IsNameOnly, but reflecting user intent,
// not e.g. after redirect or alias processing), or nil if unknown/not applicable.
func (ref ociReference) DockerReference() reference.Named {
return nil
}
// NewImage returns a types.Image for this reference.
func (ref ociReference) NewImage(certPath string, tlsVerify bool) (types.Image, error) {
return nil, errors.New("Full Image support not implemented for oci: image names")
}
// NewImageSource returns a types.ImageSource for this reference.
func (ref ociReference) NewImageSource(certPath string, tlsVerify bool) (types.ImageSource, error) {
return nil, errors.New("Reading images not implemented for oci: image names")
}
// NewImageDestination returns a types.ImageDestination for this reference.
func (ref ociReference) NewImageDestination(certPath string, tlsVerify bool) (types.ImageDestination, error) {
return newImageDestination(ref), nil
}

View File

@ -8,8 +8,6 @@ import (
"io"
"io/ioutil"
"net/http"
"net/url"
"regexp"
"strings"
"github.com/Sirupsen/logrus"
@ -17,29 +15,26 @@ import (
"github.com/containers/image/manifest"
"github.com/containers/image/types"
"github.com/containers/image/version"
"github.com/docker/docker/reference"
)
// openshiftClient is configuration for dealing with a single image stream, for reading or writing.
type openshiftClient struct {
ref openshiftReference
// Values from Kubernetes configuration
baseURL *url.URL
httpClient *http.Client
bearerToken string // "" if not used
username string // "" if not used
password string // if username != ""
// Values specific to this image
namespace string
stream string
tag string
canonicalDockerReference reference.Named // Computed from the above in advance, so that later references can not fail.
}
// FIXME: Is imageName like this a good way to refer to OpenShift images?
var imageNameRegexp = regexp.MustCompile("^([^:/]*)/([^:/]*):([^:/]*)$")
// newOpenshiftClient creates a new openshiftClient for the specified reference.
func newOpenshiftClient(ref openshiftReference) (*openshiftClient, error) {
// We have already done this parsing in ParseReference, but thrown away
// httpClient. So, parse again.
// (We could also rework/split restClientFor to "get base URL" to be done
// in ParseReference, and "get httpClient" to be done here. But until/unless
// we support non-default clusters, this is good enough.)
// newOpenshiftClient creates a new openshiftClient for the specified image.
func newOpenshiftClient(imageName string) (*openshiftClient, error) {
// Overall, this is modelled on openshift/origin/pkg/cmd/util/clientcmd.New().ClientConfig() and openshift/origin/pkg/client.
cmdConfig := defaultClientConfig()
logrus.Debugf("cmdConfig: %#v", cmdConfig)
@ -54,42 +49,22 @@ func newOpenshiftClient(imageName string) (*openshiftClient, error) {
return nil, err
}
logrus.Debugf("URL: %#v", *baseURL)
m := imageNameRegexp.FindStringSubmatch(imageName)
if m == nil || len(m) != 4 {
return nil, fmt.Errorf("Invalid image reference %s, %#v", imageName, m)
if *baseURL != *ref.baseURL {
return nil, fmt.Errorf("Unexpected baseURL mismatch: default %#v, reference %#v", *baseURL, *ref.baseURL)
}
c := &openshiftClient{
baseURL: baseURL,
return &openshiftClient{
ref: ref,
httpClient: httpClient,
bearerToken: restConfig.BearerToken,
username: restConfig.Username,
password: restConfig.Password,
namespace: m[1],
stream: m[2],
tag: m[3],
}
// Precompute also c.canonicalDockerReference so that later references can not fail.
// FIXME: This is, strictly speaking, a namespace conflict with images placed in a Docker registry running on the same host.
// Do we need to do something else, perhaps disambiguate (port number?) or namespace Docker and OpenShift separately?
dockerRef, err := reference.WithName(fmt.Sprintf("%s/%s/%s", c.baseURL.Host, c.namespace, c.stream))
if err != nil {
return nil, err
}
c.canonicalDockerReference, err = reference.WithTag(dockerRef, c.tag)
if err != nil {
return nil, err
}
return c, nil
}, nil
}
// doRequest performs a correctly authenticated request to a specified path, and returns response body or an error object.
func (c *openshiftClient) doRequest(method, path string, requestBody []byte) ([]byte, error) {
url := *c.baseURL
url := *c.ref.baseURL
url.Path = path
var requestBodyReader io.Reader
if requestBody != nil {
@ -168,7 +143,7 @@ func (c *openshiftClient) convertDockerImageReference(ref string) (string, error
// about how the OpenShift Atomic Registry is configured, per examples/atomic-registry/run.sh:
// -p OPENSHIFT_OAUTH_PROVIDER_URL=https://${INSTALL_HOST}:8443,COCKPIT_KUBE_URL=https://${INSTALL_HOST},REGISTRY_HOST=${INSTALL_HOST}:5000
func (c *openshiftClient) dockerRegistryHostPart() string {
return strings.SplitN(c.baseURL.Host, ":", 2)[0] + ":5000"
return strings.SplitN(c.ref.baseURL.Host, ":", 2)[0] + ":5000"
}
type openshiftImageSource struct {
@ -181,9 +156,9 @@ type openshiftImageSource struct {
imageStreamImageName string // Resolved image identifier, or "" if not known yet
}
// NewImageSource creates a new ImageSource for the specified image and connection specification.
func NewImageSource(imageName, certPath string, tlsVerify bool) (types.ImageSource, error) {
client, err := newOpenshiftClient(imageName)
// newImageSource creates a new ImageSource for the specified reference and connection specification.
func newImageSource(ref openshiftReference, certPath string, tlsVerify bool) (types.ImageSource, error) {
client, err := newOpenshiftClient(ref)
if err != nil {
return nil, err
}
@ -195,12 +170,10 @@ func NewImageSource(imageName, certPath string, tlsVerify bool) (types.ImageSour
}, nil
}
// IntendedDockerReference returns the Docker reference for this image, _as specified by the user_
// (not as the image itself, or its underlying storage, claims). Should be fully expanded, i.e. !reference.IsNameOnly.
// This can be used e.g. to determine which public keys are trusted for this image.
// May be nil if unknown.
func (s *openshiftImageSource) IntendedDockerReference() reference.Named {
return s.client.canonicalDockerReference
// Reference returns the reference used to set up this source, _as specified by the user_
// (not as the image itself, or its underlying storage, claims). This can be used e.g. to determine which public keys are trusted for this image.
func (s *openshiftImageSource) Reference() types.ImageReference {
return s.client.ref
}
func (s *openshiftImageSource) GetManifest(mimetypes []string) ([]byte, string, error) {
@ -228,7 +201,7 @@ func (s *openshiftImageSource) ensureImageIsResolved() error {
}
// FIXME: validate components per validation.IsValidPathSegmentName?
path := fmt.Sprintf("/oapi/v1/namespaces/%s/imagestreams/%s", s.client.namespace, s.client.stream)
path := fmt.Sprintf("/oapi/v1/namespaces/%s/imagestreams/%s", s.client.ref.namespace, s.client.ref.stream)
body, err := s.client.doRequest("GET", path, nil)
if err != nil {
return err
@ -240,7 +213,7 @@ func (s *openshiftImageSource) ensureImageIsResolved() error {
}
var te *tagEvent
for _, tag := range is.Status.Tags {
if tag.Tag != s.client.tag {
if tag.Tag != s.client.ref.tag {
continue
}
if len(tag.Items) > 0 {
@ -252,12 +225,16 @@ func (s *openshiftImageSource) ensureImageIsResolved() error {
return fmt.Errorf("No matching tag found")
}
logrus.Debugf("tag event %#v", te)
dockerRef, err := s.client.convertDockerImageReference(te.DockerImageReference)
dockerRefString, err := s.client.convertDockerImageReference(te.DockerImageReference)
if err != nil {
return err
}
logrus.Debugf("Resolved reference %#v", dockerRef)
d, err := docker.NewImageSource(dockerRef, s.certPath, s.tlsVerify)
logrus.Debugf("Resolved reference %#v", dockerRefString)
dockerRef, err := docker.ParseReference("//" + dockerRefString)
if err != nil {
return err
}
d, err := dockerRef.NewImageSource(s.certPath, s.tlsVerify)
if err != nil {
return err
}
@ -271,9 +248,9 @@ type openshiftImageDestination struct {
docker types.ImageDestination // The Docker Registry endpoint
}
// NewImageDestination creates a new ImageDestination for the specified image and connection specification.
func NewImageDestination(imageName, certPath string, tlsVerify bool) (types.ImageDestination, error) {
client, err := newOpenshiftClient(imageName)
// newImageDestination creates a new ImageDestination for the specified reference and connection specification.
func newImageDestination(ref openshiftReference, certPath string, tlsVerify bool) (types.ImageDestination, error) {
client, err := newOpenshiftClient(ref)
if err != nil {
return nil, err
}
@ -281,8 +258,12 @@ func NewImageDestination(imageName, certPath string, tlsVerify bool) (types.Imag
// FIXME: Should this always use a digest, not a tag? Uploading to Docker by tag requires the tag _inside_ the manifest to match,
// i.e. a single signed image cannot be available under multiple tags. But with types.ImageDestination, we don't know
// the manifest digest at this point.
dockerRef := fmt.Sprintf("%s/%s/%s:%s", client.dockerRegistryHostPart(), client.namespace, client.stream, client.tag)
docker, err := docker.NewImageDestination(dockerRef, certPath, tlsVerify)
dockerRefString := fmt.Sprintf("//%s/%s/%s:%s", client.dockerRegistryHostPart(), client.ref.namespace, client.ref.stream, client.ref.tag)
dockerRef, err := docker.ParseReference(dockerRefString)
if err != nil {
return nil, err
}
docker, err := dockerRef.NewImageDestination(certPath, tlsVerify)
if err != nil {
return nil, err
}
@ -293,6 +274,12 @@ func NewImageDestination(imageName, certPath string, tlsVerify bool) (types.Imag
}, nil
}
// Reference returns the reference used to set up this destination. Note that this should directly correspond to user's intent,
// e.g. it should use the public hostname instead of the result of resolving CNAMEs or following redirects.
func (d *openshiftImageDestination) Reference() types.ImageReference {
return d.client.ref
}
func (d *openshiftImageDestination) SupportedManifestMIMETypes() []string {
return []string{
manifest.DockerV2Schema1SignedMIMEType,
@ -300,10 +287,6 @@ func (d *openshiftImageDestination) SupportedManifestMIMETypes() []string {
}
}
func (d *openshiftImageDestination) CanonicalDockerReference() reference.Named {
return d.client.canonicalDockerReference
}
func (d *openshiftImageDestination) PutManifest(m []byte) error {
// Note: This does absolutely no kind/version checking or conversions.
manifestDigest, err := manifest.Digest(m)
@ -311,15 +294,15 @@ func (d *openshiftImageDestination) PutManifest(m []byte) error {
return err
}
// FIXME: We can't do what respositorymiddleware.go does because we don't know the internal address. Does any of this matter?
dockerImageReference := fmt.Sprintf("%s/%s/%s@%s", d.client.dockerRegistryHostPart(), d.client.namespace, d.client.stream, manifestDigest)
dockerImageReference := fmt.Sprintf("%s/%s/%s@%s", d.client.dockerRegistryHostPart(), d.client.ref.namespace, d.client.ref.stream, manifestDigest)
ism := imageStreamMapping{
typeMeta: typeMeta{
Kind: "ImageStreamMapping",
APIVersion: "v1",
},
objectMeta: objectMeta{
Namespace: d.client.namespace,
Name: d.client.stream,
Namespace: d.client.ref.namespace,
Name: d.client.ref.stream,
},
Image: image{
objectMeta: objectMeta{
@ -328,7 +311,7 @@ func (d *openshiftImageDestination) PutManifest(m []byte) error {
DockerImageReference: dockerImageReference,
DockerImageManifest: string(m),
},
Tag: d.client.tag,
Tag: d.client.ref.tag,
}
body, err := json.Marshal(ism)
if err != nil {
@ -336,7 +319,7 @@ func (d *openshiftImageDestination) PutManifest(m []byte) error {
}
// FIXME: validate components per validation.IsValidPathSegmentName?
path := fmt.Sprintf("/oapi/v1/namespaces/%s/imagestreammappings", d.client.namespace)
path := fmt.Sprintf("/oapi/v1/namespaces/%s/imagestreammappings", d.client.ref.namespace)
body, err = d.client.doRequest("POST", path, body)
if err != nil {
return err

View File

@ -0,0 +1,127 @@
package openshift
import (
"errors"
"fmt"
"net/url"
"regexp"
"github.com/Sirupsen/logrus"
"github.com/containers/image/types"
"github.com/docker/docker/reference"
)
// Transport is an ImageTransport for directory paths.
var Transport = openshiftTransport{}
type openshiftTransport struct{}
func (t openshiftTransport) Name() string {
return "atomic"
}
// ParseReference converts a string, which should not start with the ImageTransport.Name prefix, into an ImageReference.
func (t openshiftTransport) ParseReference(reference string) (types.ImageReference, error) {
return ParseReference(reference)
}
// openshiftReference is an ImageReference for OpenShift images.
type openshiftReference struct {
baseURL *url.URL
namespace string
stream string
tag string
dockerReference reference.Named // Computed from the above in advance, so that later references can not fail.
}
// FIXME: Is imageName like this a good way to refer to OpenShift images?
var imageNameRegexp = regexp.MustCompile("^([^:/]*)/([^:/]*):([^:/]*)$")
// ParseReference converts a string, which should not start with the ImageTransport.Name prefix, into an OpenShift ImageReference.
func ParseReference(reference string) (types.ImageReference, error) {
// Overall, this is modelled on openshift/origin/pkg/cmd/util/clientcmd.New().ClientConfig() and openshift/origin/pkg/client.
cmdConfig := defaultClientConfig()
logrus.Debugf("cmdConfig: %#v", cmdConfig)
restConfig, err := cmdConfig.ClientConfig()
if err != nil {
return nil, err
}
// REMOVED: SetOpenShiftDefaults (values are not overridable in config files, so hard-coded these defaults.)
logrus.Debugf("restConfig: %#v", restConfig)
baseURL, _, err := restClientFor(restConfig)
if err != nil {
return nil, err
}
logrus.Debugf("URL: %#v", *baseURL)
m := imageNameRegexp.FindStringSubmatch(reference)
if m == nil || len(m) != 4 {
return nil, fmt.Errorf("Invalid image reference %s, %#v", reference, m)
}
return NewReference(baseURL, m[1], m[2], m[3])
}
// NewReference returns an OpenShift reference for a base URL, namespace, stream and tag.
func NewReference(baseURL *url.URL, namespace, stream, tag string) (types.ImageReference, error) {
// Precompute also dockerReference so that later references can not fail.
//
// This discards ref.baseURL.Path, which is unexpected for a “base URL”;
// but openshiftClient.doRequest actually completely overrides url.Path
// (and defaultServerURL rejects non-trivial Path values), so it is OK for
// us to ignore it as well.
//
// FIXME: This is, strictly speaking, a namespace conflict with images placed in a Docker registry running on the same host.
// Do we need to do something else, perhaps disambiguate (port number?) or namespace Docker and OpenShift separately?
dockerRef, err := reference.WithName(fmt.Sprintf("%s/%s/%s", baseURL.Host, namespace, stream))
if err != nil {
return nil, err
}
dockerRef, err = reference.WithTag(dockerRef, tag)
if err != nil {
return nil, err
}
return openshiftReference{
baseURL: baseURL,
namespace: namespace,
stream: stream,
tag: tag,
dockerReference: dockerRef,
}, nil
}
func (ref openshiftReference) Transport() types.ImageTransport {
return Transport
}
// StringWithinTransport returns a string representation of the reference, which MUST be such that
// reference.Transport().ParseReference(reference.StringWithinTransport()) returns an equivalent reference.
// NOTE: The returned string is not promised to be equal to the original input to ParseReference;
// e.g. default attribute values omitted by the user may be filled in in the return value, or vice versa.
// WARNING: Do not use the return value in the UI to describe an image, it does not contain the Transport().Name() prefix.
func (ref openshiftReference) StringWithinTransport() string {
return fmt.Sprintf("%s/%s:%s", ref.namespace, ref.stream, ref.tag)
}
// DockerReference returns a Docker reference associated with this reference
// (fully explicit, i.e. !reference.IsNameOnly, but reflecting user intent,
// not e.g. after redirect or alias processing), or nil if unknown/not applicable.
func (ref openshiftReference) DockerReference() reference.Named {
return ref.dockerReference
}
// NewImage returns a types.Image for this reference.
func (ref openshiftReference) NewImage(certPath string, tlsVerify bool) (types.Image, error) {
return nil, errors.New("Full Image support not implemented for atomic: image names")
}
// NewImageSource returns a types.ImageSource for this reference.
func (ref openshiftReference) NewImageSource(certPath string, tlsVerify bool) (types.ImageSource, error) {
return newImageSource(ref, certPath, tlsVerify)
}
// NewImageDestination returns a types.ImageDestination for this reference.
func (ref openshiftReference) NewImageDestination(certPath string, tlsVerify bool) (types.ImageDestination, error) {
return newImageDestination(ref, certPath, tlsVerify)
}

View File

@ -10,6 +10,7 @@ import (
"strings"
"github.com/Sirupsen/logrus"
"github.com/containers/image/transports"
"github.com/containers/image/types"
"github.com/docker/docker/reference"
)
@ -70,8 +71,8 @@ type PolicyRequirement interface {
// The type is public, but its implementation is private.
type PolicyReferenceMatch interface {
// matchesDockerReference decides whether a specific image identity is accepted for an image
// (or, usually, for the image's IntendedDockerReference()). Note that
// image.IntendedDockerReference() may be nil.
// (or, usually, for the image's Reference().DockerReference()). Note that
// image.Reference().DockerReference() may be nil.
matchesDockerReference(image types.Image, signatureDockerReference string) bool
}
@ -155,10 +156,9 @@ func fullyExpandedDockerReference(ref reference.Named) (string, error) {
// requirementsForImage selects the appropriate requirements for image.
func (pc *PolicyContext) requirementsForImage(image types.Image) (PolicyRequirements, error) {
ref := image.IntendedDockerReference()
ref := image.Reference().DockerReference()
if ref == nil {
// FIXME: Tell the user which image this is.
return nil, fmt.Errorf("Can not determine policy for an image with no known Docker reference identity")
return nil, fmt.Errorf("Can not determine policy for image %s with no known Docker reference identity", transports.ImageName(image.Reference()))
}
ref = reference.WithDefaultTag(ref) // This should not be needed, but if we did receive a name-only reference, this is a reasonable thing to do.
@ -222,7 +222,7 @@ func (pc *PolicyContext) GetSignaturesWithAcceptedAuthor(image types.Image) (sig
}
}()
logrus.Debugf("GetSignaturesWithAcceptedAuthor for image %s", image.IntendedDockerReference())
logrus.Debugf("GetSignaturesWithAcceptedAuthor for image %s", image.Reference().DockerReference())
reqs, err := pc.requirementsForImage(image)
if err != nil {
@ -306,7 +306,7 @@ func (pc *PolicyContext) IsRunningImageAllowed(image types.Image) (res bool, fin
}
}()
logrus.Debugf("IsRunningImageAllowed for image %s", image.IntendedDockerReference())
logrus.Debugf("IsRunningImageAllowed for image %s", image.Reference().DockerReference())
reqs, err := pc.requirementsForImage(image)
if err != nil {

View File

@ -2,7 +2,12 @@
package signature
import "github.com/containers/image/types"
import (
"fmt"
"github.com/containers/image/transports"
"github.com/containers/image/types"
)
func (pr *prInsecureAcceptAnything) isSignatureAuthorAccepted(image types.Image, sig []byte) (signatureAcceptanceResult, *Signature, error) {
// prInsecureAcceptAnything semantics: Every image is allowed to run,
@ -15,11 +20,9 @@ func (pr *prInsecureAcceptAnything) isRunningImageAllowed(image types.Image) (bo
}
func (pr *prReject) isSignatureAuthorAccepted(image types.Image, sig []byte) (signatureAcceptanceResult, *Signature, error) {
// FIXME? Name the image, or better the matched scope in Policy.Specific.
return sarRejected, nil, PolicyRequirementError("Any signatures for these images are rejected by policy.")
return sarRejected, nil, PolicyRequirementError(fmt.Sprintf("Any signatures for image %s are rejected by policy.", transports.ImageName(image.Reference())))
}
func (pr *prReject) isRunningImageAllowed(image types.Image) (bool, error) {
// FIXME? Name the image, or better the matched scope in Policy.Specific.
return false, PolicyRequirementError("Running these images is rejected by policy.")
return false, PolicyRequirementError(fmt.Sprintf("Running image %s is rejected by policy.", transports.ImageName(image.Reference())))
}

View File

@ -3,16 +3,19 @@
package signature
import (
"github.com/docker/docker/reference"
"fmt"
"github.com/containers/image/transports"
"github.com/containers/image/types"
"github.com/docker/docker/reference"
)
// parseImageAndDockerReference converts an image and a reference string into two parsed entities, failing on any error and handling unidentified images.
func parseImageAndDockerReference(image types.Image, s2 string) (reference.Named, reference.Named, error) {
r1 := image.IntendedDockerReference()
r1 := image.Reference().DockerReference()
if r1 == nil {
// FIXME: Tell the user which image this is.
return nil, nil, PolicyRequirementError("Docker reference match attempted on an image with no known Docker reference identity")
return nil, nil, PolicyRequirementError(fmt.Sprintf("Docker reference match attempted on image %s with no known Docker reference identity",
transports.ImageName(image.Reference())))
}
r2, err := reference.ParseNamed(s2)
if err != nil {
@ -26,7 +29,7 @@ func (prm *prmMatchExact) matchesDockerReference(image types.Image, signatureDoc
if err != nil {
return false
}
// Do not add default tags: image.IntendedDockerReference() has it added already per its construction, and signatureDockerReference should be exact; so, verify that now.
// Do not add default tags: image.Reference().DockerReference() should contain it already, and signatureDockerReference should be exact; so, verify that now.
if reference.IsNameOnly(intended) || reference.IsNameOnly(signature) {
return false
}

View File

@ -0,0 +1,55 @@
package transports
import (
"fmt"
"strings"
"github.com/containers/image/directory"
"github.com/containers/image/docker"
"github.com/containers/image/oci"
"github.com/containers/image/openshift"
"github.com/containers/image/types"
)
// KnownTransports is a registry of known ImageTransport instances.
var KnownTransports map[string]types.ImageTransport
func init() {
KnownTransports = make(map[string]types.ImageTransport)
for _, t := range []types.ImageTransport{
directory.Transport,
docker.Transport,
oci.Transport,
openshift.Transport,
} {
name := t.Name()
if _, ok := KnownTransports[name]; ok {
panic(fmt.Sprintf("Duplicate image transport name %s", name))
}
KnownTransports[name] = t
}
}
// ParseImageName converts a URL-like image name to a types.ImageReference.
func ParseImageName(imgName string) (types.ImageReference, error) {
parts := strings.SplitN(imgName, ":", 2)
if len(parts) != 2 {
return nil, fmt.Errorf(`Invalid image name "%s", expected colon-separated transport:reference`, imgName)
}
transport, ok := KnownTransports[parts[0]]
if !ok {
return nil, fmt.Errorf(`Invalid image name "%s", unknown transport "%s"`, imgName, parts[0])
}
return transport.ParseReference(parts[1])
}
// ImageName converts a types.ImageReference into an URL-like image name, which MUST be such that
// ParseImageName(ImageName(reference)) returns an equivalent reference.
//
// This is the generally recommended way to refer to images in the UI.
//
// NOTE: The returned string is not promised to be equal to the original input to ParseImageName;
// e.g. default attribute values omitted by the user may be filled in in the return value, or vice versa.
func ImageName(ref types.ImageReference) string {
return ref.Transport().Name() + ":" + ref.StringWithinTransport()
}

View File

@ -7,15 +7,63 @@ import (
"github.com/docker/docker/reference"
)
// ImageTransport is a top-level namespace for ways to to store/load an image.
// It should generally correspond to ImageSource/ImageDestination implementations.
//
// Note that ImageTransport is based on "ways the users refer to image storage", not necessarily on the underlying physical transport.
// For example, all Docker References would be used within a single "docker" transport, regardless of whether the images are pulled over HTTP or HTTPS
// (or, even, IPv4 or IPv6).
//
// OTOH all images using the same transport should (apart from versions of the image format), be interoperable.
// For example, several different ImageTransport implementations may be based on local filesystem paths,
// but using completely different formats for the contents of that path (a single tar file, a directory containing tarballs, a fully expanded container filesystem, ...)
//
// See also transports.KnownTransports.
type ImageTransport interface {
// Name returns the name of the transport, which must be unique among other transports.
Name() string
// ParseReference converts a string, which should not start with the ImageTransport.Name prefix, into an ImageReference.
ParseReference(reference string) (ImageReference, error)
}
// ImageReference is an abstracted way to refer to an image location, namespaced within an ImageTransport.
//
// The object should preferably be immutable after creation, with any parsing/state-dependent resolving happening
// within an ImageTransport.ParseReference() or equivalent API creating the reference object.
// That's also why the various identification/formatting methods of this type do not support returning errors.
//
// WARNING: While this design freezes the content of the reference within this process, it can not freeze the outside
// world: paths may be replaced by symlinks elsewhere, HTTP APIs may start returning different results, and so on.
type ImageReference interface {
Transport() ImageTransport
// StringWithinTransport returns a string representation of the reference, which MUST be such that
// reference.Transport().ParseReference(reference.StringWithinTransport()) returns an equivalent reference.
// NOTE: The returned string is not promised to be equal to the original input to ParseReference;
// e.g. default attribute values omitted by the user may be filled in in the return value, or vice versa.
// WARNING: Do not use the return value in the UI to describe an image, it does not contain the Transport().Name() prefix;
// instead, see transports.ImageName().
StringWithinTransport() string
// DockerReference returns a Docker reference associated with this reference
// (fully explicit, i.e. !reference.IsNameOnly, but reflecting user intent,
// not e.g. after redirect or alias processing), or nil if unknown/not applicable.
DockerReference() reference.Named
// NewImage returns a types.Image for this reference.
NewImage(certPath string, tlsVerify bool) (Image, error)
// NewImageSource returns a types.ImageSource for this reference.
NewImageSource(certPath string, tlsVerify bool) (ImageSource, error)
// NewImageDestination returns a types.ImageDestination for this reference.
NewImageDestination(certPath string, tlsVerify bool) (ImageDestination, error)
}
// ImageSource is a service, possibly remote (= slow), to download components of a single image.
// This is primarily useful for copying images around; for examining their properties, Image (below)
// is usually more useful.
type ImageSource interface {
// IntendedDockerReference returns the Docker reference for this image, _as specified by the user_
// (not as the image itself, or its underlying storage, claims). Should be fully expanded, i.e. !reference.IsNameOnly.
// This can be used e.g. to determine which public keys are trusted for this image.
// May be nil if unknown.
IntendedDockerReference() reference.Named
// Reference returns the reference used to set up this source, _as specified by the user_
// (not as the image itself, or its underlying storage, claims). This can be used e.g. to determine which public keys are trusted for this image.
Reference() ImageReference
// GetManifest returns the image's manifest along with its MIME type. The empty string is returned if the MIME type is unknown. The slice parameter indicates the supported mime types the manifest should be when getting it.
// It may use a remote (= slow) service.
GetManifest([]string) ([]byte, string, error)
@ -30,9 +78,9 @@ type ImageSource interface {
// ImageDestination is a service, possibly remote (= slow), to store components of a single image.
type ImageDestination interface {
// CanonicalDockerReference returns the Docker reference for this image (fully expanded, i.e. !reference.IsNameOnly, but
// reflecting user intent, not e.g. after redirect or alias processing), or nil if unknown.
CanonicalDockerReference() reference.Named
// Reference returns the reference used to set up this destination. Note that this should directly correspond to user's intent,
// e.g. it should use the public hostname instead of the result of resolving CNAMEs or following redirects.
Reference() ImageReference
// FIXME? This should also receive a MIME type if known, to differentiate between schema versions.
PutManifest([]byte) error
// Note: Calling PutBlob() and other methods may have ordering dependencies WRT other methods of this type. FIXME: Figure out and document.
@ -45,12 +93,10 @@ type ImageDestination interface {
// Image is the primary API for inspecting properties of images.
type Image interface {
// Reference returns the reference used to set up this source, _as specified by the user_
// (not as the image itself, or its underlying storage, claims). This can be used e.g. to determine which public keys are trusted for this image.
Reference() ImageReference
// ref to repository?
// IntendedDockerReference returns the Docker reference for this image, _as specified by the user_
// (not as the image itself, or its underlying storage, claims). Should be fully expanded, i.e. !reference.IsNameOnly.
// This can be used e.g. to determine which public keys are trusted for this image.
// May be nil if unknown.
IntendedDockerReference() reference.Named
// Manifest is like ImageSource.GetManifest, but the result is cached; it is OK to call this however often you need.
// NOTE: It is essential for signature verification that Manifest returns the manifest from which BlobDigests is computed.
Manifest() ([]byte, string, error)