Merge pull request #202 from runcom/change-os-uri

Change atomic URI
This commit is contained in:
Antonio Murdaca 2016-09-13 17:11:07 +02:00 committed by GitHub
commit 14847101c0
5 changed files with 76 additions and 118 deletions

View File

@ -98,9 +98,9 @@ func (s *CopySuite) TestCopySimple(c *check.C) {
// "pull": docker: → dir:
assertSkopeoSucceeds(c, "", "copy", "docker://estesp/busybox:latest", "dir:"+dir1)
// "push": dir: → atomic:
assertSkopeoSucceeds(c, "", "--debug", "copy", "dir:"+dir1, "atomic:myns/unsigned:unsigned")
assertSkopeoSucceeds(c, "", "--debug", "copy", "dir:"+dir1, "atomic:localhost:5000/myns/unsigned:unsigned")
// The result of pushing and pulling is an unmodified image.
assertSkopeoSucceeds(c, "", "copy", "atomic:myns/unsigned:unsigned", "dir:"+dir2)
assertSkopeoSucceeds(c, "", "copy", "atomic:localhost:5000/myns/unsigned:unsigned", "dir:"+dir2)
out := combinedOutputOfCommand(c, "diff", "-urN", dir1, dir2)
c.Assert(out, check.Equals, "")
@ -127,10 +127,10 @@ func (s *CopySuite) TestCopyStreaming(c *check.C) {
// FIXME: It would be nice to use one of the local Docker registries instead of neeeding an Internet connection.
// streaming: docker: → atomic:
assertSkopeoSucceeds(c, "", "--debug", "copy", "docker://estesp/busybox:amd64", "atomic:myns/unsigned:streaming")
assertSkopeoSucceeds(c, "", "--debug", "copy", "docker://estesp/busybox:amd64", "atomic:localhost:5000/myns/unsigned:streaming")
// Compare (copies of) the original and the copy:
assertSkopeoSucceeds(c, "", "copy", "docker://estesp/busybox:amd64", "dir:"+dir1)
assertSkopeoSucceeds(c, "", "copy", "atomic:myns/unsigned:streaming", "dir:"+dir2)
assertSkopeoSucceeds(c, "", "copy", "atomic:localhost:5000/myns/unsigned:streaming", "dir:"+dir2)
// The manifests will have different JWS signatures; so, compare the manifests by digests, which
// strips the signatures, and remove them, comparing the rest file by file.
digests := []string{}
@ -170,34 +170,34 @@ func (s *CopySuite) TestCopySignatures(c *check.C) {
// type: signedBy
// Sign the images
assertSkopeoSucceeds(c, "", "copy", "--sign-by", "personal@example.com", "docker://busybox:1.23", "atomic:myns/personal:personal")
assertSkopeoSucceeds(c, "", "copy", "--sign-by", "official@example.com", "docker://busybox:1.23.2", "atomic:myns/official:official")
assertSkopeoSucceeds(c, "", "copy", "--sign-by", "personal@example.com", "docker://busybox:1.23", "atomic:localhost:5000/myns/personal:personal")
assertSkopeoSucceeds(c, "", "copy", "--sign-by", "official@example.com", "docker://busybox:1.23.2", "atomic:localhost:5000/myns/official:official")
// Verify that we can pull them
assertSkopeoSucceeds(c, "", "--policy", policy, "copy", "atomic:myns/personal:personal", dirDest)
assertSkopeoSucceeds(c, "", "--policy", policy, "copy", "atomic:myns/official:official", dirDest)
assertSkopeoSucceeds(c, "", "--policy", policy, "copy", "atomic:localhost:5000/myns/personal:personal", dirDest)
assertSkopeoSucceeds(c, "", "--policy", policy, "copy", "atomic:localhost:5000/myns/official:official", dirDest)
// Verify that mis-signed images are rejected
assertSkopeoSucceeds(c, "", "copy", "atomic:myns/personal:personal", "atomic:myns/official:attack")
assertSkopeoSucceeds(c, "", "copy", "atomic:myns/official:official", "atomic:myns/personal:attack")
assertSkopeoSucceeds(c, "", "copy", "atomic:localhost:5000/myns/personal:personal", "atomic:localhost:5000/myns/official:attack")
assertSkopeoSucceeds(c, "", "copy", "atomic:localhost:5000/myns/official:official", "atomic:localhost:5000/myns/personal:attack")
assertSkopeoFails(c, ".*Source image rejected: Invalid GPG signature.*",
"--policy", policy, "copy", "atomic:myns/personal:attack", dirDest)
"--policy", policy, "copy", "atomic:localhost:5000/myns/personal:attack", dirDest)
assertSkopeoFails(c, ".*Source image rejected: Invalid GPG signature.*",
"--policy", policy, "copy", "atomic:myns/official:attack", dirDest)
"--policy", policy, "copy", "atomic:localhost:5000/myns/official:attack", dirDest)
// Verify that signed identity is verified.
assertSkopeoSucceeds(c, "", "copy", "atomic:myns/official:official", "atomic:myns/naming:test1")
assertSkopeoFails(c, ".*Source image rejected: Signature for identity localhost:8443/myns/official:official is not accepted.*",
"--policy", policy, "copy", "atomic:myns/naming:test1", dirDest)
assertSkopeoSucceeds(c, "", "copy", "atomic:localhost:5000/myns/official:official", "atomic:localhost:5000/myns/naming:test1")
assertSkopeoFails(c, ".*Source image rejected: Signature for identity localhost:5000/myns/official:official is not accepted.*",
"--policy", policy, "copy", "atomic:localhost:5000/myns/naming:test1", dirDest)
// signedIdentity works
assertSkopeoSucceeds(c, "", "copy", "atomic:myns/official:official", "atomic:myns/naming:naming")
assertSkopeoSucceeds(c, "", "--policy", policy, "copy", "atomic:myns/naming:naming", dirDest)
assertSkopeoSucceeds(c, "", "copy", "atomic:localhost:5000/myns/official:official", "atomic:localhost:5000/myns/naming:naming")
assertSkopeoSucceeds(c, "", "--policy", policy, "copy", "atomic:localhost:5000/myns/naming:naming", dirDest)
// Verify that cosigning requirements are enforced
assertSkopeoSucceeds(c, "", "copy", "atomic:myns/official:official", "atomic:myns/cosigned:cosigned")
assertSkopeoSucceeds(c, "", "copy", "atomic:localhost:5000/myns/official:official", "atomic:localhost:5000/myns/cosigned:cosigned")
assertSkopeoFails(c, ".*Source image rejected: Invalid GPG signature.*",
"--policy", policy, "copy", "atomic:myns/cosigned:cosigned", dirDest)
"--policy", policy, "copy", "atomic:localhost:5000/myns/cosigned:cosigned", dirDest)
assertSkopeoSucceeds(c, "", "copy", "--sign-by", "personal@example.com", "atomic:myns/official:official", "atomic:myns/cosigned:cosigned")
assertSkopeoSucceeds(c, "", "--policy", policy, "copy", "atomic:myns/cosigned:cosigned", dirDest)
assertSkopeoSucceeds(c, "", "copy", "--sign-by", "personal@example.com", "atomic:localhost:5000/myns/official:official", "atomic:localhost:5000/myns/cosigned:cosigned")
assertSkopeoSucceeds(c, "", "--policy", policy, "copy", "atomic:localhost:5000/myns/cosigned:cosigned", dirDest)
}
// --policy copy for dir: sources
@ -224,10 +224,10 @@ func (s *CopySuite) TestCopyDirSignatures(c *check.C) {
// Sign the images. By coping fom a topDirDest/dirN, also test that non-/restricted paths
// use the dir:"" default of insecureAcceptAnything.
// (For signing, we must push to atomic: to get a Docker identity to use in the signature.)
assertSkopeoSucceeds(c, "", "--policy", policy, "copy", "--sign-by", "personal@example.com", topDirDest+"/dir1", "atomic:myns/personal:dirstaging")
assertSkopeoSucceeds(c, "", "--policy", policy, "copy", "--sign-by", "official@example.com", topDirDest+"/dir2", "atomic:myns/official:dirstaging")
assertSkopeoSucceeds(c, "", "copy", "atomic:myns/personal:dirstaging", topDirDest+"/restricted/personal")
assertSkopeoSucceeds(c, "", "copy", "atomic:myns/official:dirstaging", topDirDest+"/restricted/official")
assertSkopeoSucceeds(c, "", "--policy", policy, "copy", "--sign-by", "personal@example.com", topDirDest+"/dir1", "atomic:localhost:5000/myns/personal:dirstaging")
assertSkopeoSucceeds(c, "", "--policy", policy, "copy", "--sign-by", "official@example.com", topDirDest+"/dir2", "atomic:localhost:5000/myns/official:dirstaging")
assertSkopeoSucceeds(c, "", "copy", "atomic:localhost:5000/myns/personal:dirstaging", topDirDest+"/restricted/personal")
assertSkopeoSucceeds(c, "", "copy", "atomic:localhost:5000/myns/official:dirstaging", topDirDest+"/restricted/official")
// type: signedBy, with a signedIdentity override (necessary because dir: identities can't be signed)
// Verify that correct images are accepted
@ -237,8 +237,8 @@ func (s *CopySuite) TestCopyDirSignatures(c *check.C) {
"--policy", policy, "copy", topDirDest+"/restricted/personal", topDirDest+"/dest")
// Verify that the signed identity is verified.
assertSkopeoSucceeds(c, "", "--policy", policy, "copy", "--sign-by", "official@example.com", topDirDest+"/dir1", "atomic:myns/personal:dirstaging2")
assertSkopeoSucceeds(c, "", "copy", "atomic:myns/personal:dirstaging2", topDirDest+"/restricted/badidentity")
assertSkopeoFails(c, ".*Source image rejected: .*Signature for identity localhost:8443/myns/personal:dirstaging2 is not accepted.*",
assertSkopeoSucceeds(c, "", "--policy", policy, "copy", "--sign-by", "official@example.com", topDirDest+"/dir1", "atomic:localhost:5000/myns/personal:dirstaging2")
assertSkopeoSucceeds(c, "", "copy", "atomic:localhost:5000/myns/personal:dirstaging2", topDirDest+"/restricted/badidentity")
assertSkopeoFails(c, ".*Source image rejected: .*Signature for identity localhost:5000/myns/personal:dirstaging2 is not accepted.*",
"--policy", policy, "copy", topDirDest+"/restricted/badidentity", topDirDest+"/dest")
}

View File

@ -20,7 +20,7 @@
"keyPath": "@keydir@/official-pubkey.gpg",
"signedIdentity": {
"type": "exactRepository",
"dockerRepository": "localhost:8443/myns/official"
"dockerRepository": "localhost:5000/myns/official"
}
}
],
@ -31,46 +31,46 @@
]
},
"atomic": {
"localhost:8443/myns/personal": [
"localhost:5000/myns/personal": [
{
"type": "signedBy",
"keyType": "GPGKeys",
"keyPath": "@keydir@/personal-pubkey.gpg"
}
],
"localhost:8443/myns/official": [
"localhost:5000/myns/official": [
{
"type": "signedBy",
"keyType": "GPGKeys",
"keyPath": "@keydir@/official-pubkey.gpg"
}
],
"localhost:8443/myns/naming:test1": [
"localhost:5000/myns/naming:test1": [
{
"type": "signedBy",
"keyType": "GPGKeys",
"keyPath": "@keydir@/official-pubkey.gpg"
}
],
"localhost:8443/myns/naming:naming": [
"localhost:5000/myns/naming:naming": [
{
"type": "signedBy",
"keyType": "GPGKeys",
"keyPath": "@keydir@/official-pubkey.gpg",
"signedIdentity": {
"type": "exactRepository",
"dockerRepository": "localhost:8443/myns/official"
"dockerRepository": "localhost:5000/myns/official"
}
}
],
"localhost:8443/myns/cosigned:cosigned": [
"localhost:5000/myns/cosigned:cosigned": [
{
"type": "signedBy",
"keyType": "GPGKeys",
"keyPath": "@keydir@/official-pubkey.gpg",
"signedIdentity": {
"type": "exactRepository",
"dockerRepository": "localhost:8443/myns/official"
"dockerRepository": "localhost:5000/myns/official"
}
},
{
@ -81,4 +81,4 @@
]
}
}
}
}

View File

@ -9,6 +9,7 @@ import (
"io"
"io/ioutil"
"net/http"
"net/url"
"strings"
"time"
@ -21,7 +22,8 @@ import (
// openshiftClient is configuration for dealing with a single image stream, for reading or writing.
type openshiftClient struct {
ref openshiftReference
ref openshiftReference
baseURL *url.URL
// Values from Kubernetes configuration
httpClient *http.Client
bearerToken string // "" if not used
@ -51,9 +53,7 @@ func newOpenshiftClient(ref openshiftReference) (*openshiftClient, error) {
return nil, err
}
logrus.Debugf("URL: %#v", *baseURL)
if *baseURL != *ref.baseURL {
return nil, fmt.Errorf("Unexpected baseURL mismatch: default %#v, reference %#v", *baseURL, *ref.baseURL)
}
if httpClient == nil {
httpClient = http.DefaultClient
}
@ -61,6 +61,7 @@ func newOpenshiftClient(ref openshiftReference) (*openshiftClient, error) {
return &openshiftClient{
ref: ref,
baseURL: baseURL,
httpClient: httpClient,
bearerToken: restConfig.BearerToken,
username: restConfig.Username,
@ -70,7 +71,7 @@ func newOpenshiftClient(ref openshiftReference) (*openshiftClient, error) {
// 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.ref.baseURL
url := *c.baseURL
url.Path = path
var requestBodyReader io.Reader
if requestBody != nil {
@ -153,19 +154,7 @@ func (c *openshiftClient) convertDockerImageReference(ref string) (string, error
if len(parts) != 2 {
return "", fmt.Errorf("Invalid format of docker reference %s: missing '/'", ref)
}
// Sanity check that the reference is at least plausibly similar, i.e. uses the hard-coded port we expect.
if !strings.HasSuffix(parts[0], ":5000") {
return "", fmt.Errorf("Invalid format of docker reference %s: expecting port 5000", ref)
}
return c.dockerRegistryHostPart() + "/" + parts[1], nil
}
// dockerRegistryHostPart returns the host:port of the embedded Docker Registry API endpoint
// FIXME: There seems to be no way to discover the correct:host port using the API, so hard-code our knowledge
// 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.ref.baseURL.Host, ":", 2)[0] + ":5000"
return c.ref.dockerReference.Hostname() + "/" + parts[1], nil
}
type openshiftImageSource struct {
@ -261,7 +250,7 @@ func (s *openshiftImageSource) ensureImageIsResolved() error {
}
var te *tagEvent
for _, tag := range is.Status.Tags {
if tag.Tag != s.client.ref.tag {
if tag.Tag != s.client.ref.dockerReference.Tag() {
continue
}
if len(tag.Items) > 0 {
@ -308,7 +297,7 @@ func newImageDestination(ctx *types.SystemContext, ref openshiftReference) (type
// 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.
dockerRefString := fmt.Sprintf("//%s/%s/%s:%s", client.dockerRegistryHostPart(), client.ref.namespace, client.ref.stream, client.ref.tag)
dockerRefString := fmt.Sprintf("//%s/%s/%s:%s", client.ref.dockerReference.Hostname(), client.ref.namespace, client.ref.stream, client.ref.dockerReference.Tag())
dockerRef, err := docker.ParseReference(dockerRefString)
if err != nil {
return nil, err
@ -370,7 +359,7 @@ func (d *openshiftImageDestination) PutManifest(m []byte) error {
}
d.imageStreamImageName = manifestDigest
// 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.ref.namespace, d.client.ref.stream, manifestDigest)
dockerImageReference := fmt.Sprintf("%s/%s/%s@%s", d.client.ref.dockerReference.Hostname(), d.client.ref.namespace, d.client.ref.stream, manifestDigest)
ism := imageStreamMapping{
typeMeta: typeMeta{
Kind: "ImageStreamMapping",
@ -387,7 +376,7 @@ func (d *openshiftImageDestination) PutManifest(m []byte) error {
DockerImageReference: dockerImageReference,
DockerImageManifest: string(m),
},
Tag: d.client.ref.tag,
Tag: d.client.ref.dockerReference.Tag(),
}
body, err := json.Marshal(ism)
if err != nil {

View File

@ -3,10 +3,9 @@ package openshift
import (
"errors"
"fmt"
"net/url"
"regexp"
"strings"
"github.com/Sirupsen/logrus"
"github.com/containers/image/docker/policyconfiguration"
"github.com/containers/image/types"
"github.com/docker/docker/reference"
@ -44,67 +43,33 @@ func (t openshiftTransport) ValidatePolicyConfigurationScope(scope string) error
// 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.
dockerReference reference.NamedTagged
namespace string // Computed from dockerReference in advance.
stream string // Computed from dockerReference in advance.
}
// FIXME: Is imageName like this a good way to refer to OpenShift images?
// Keep this in sync with scopeRegexp!
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()
func ParseReference(ref string) (types.ImageReference, error) {
r, err := reference.ParseNamed(ref)
if err != nil {
return nil, err
return nil, fmt.Errorf("failed to parse image reference %q, %v", ref, 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
tagged, ok := r.(reference.NamedTagged)
if !ok {
return nil, fmt.Errorf("invalid image reference %s, %#v", ref, r)
}
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])
return NewReference(tagged)
}
// 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
// NewReference returns an OpenShift reference for a reference.NamedTagged
func NewReference(dockerRef reference.NamedTagged) (types.ImageReference, error) {
r := strings.SplitN(dockerRef.RemoteName(), "/", 3)
if len(r) != 2 {
return nil, fmt.Errorf("invalid image reference %s", dockerRef.String())
}
dockerRef, err = reference.WithTag(dockerRef, tag)
if err != nil {
return nil, err
}
return openshiftReference{
baseURL: baseURL,
namespace: namespace,
stream: stream,
tag: tag,
namespace: r[0],
stream: r[1],
dockerReference: dockerRef,
}, nil
}
@ -119,7 +84,7 @@ func (ref openshiftReference) Transport() types.ImageTransport {
// 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)
return ref.dockerReference.String()
}
// DockerReference returns a Docker reference associated with this reference

View File

@ -15,18 +15,22 @@
package v1
const (
// MediaTypeDescriptor specifies the mediaType for a content descriptor.
// MediaTypeDescriptor specifies the media type for a content descriptor.
MediaTypeDescriptor = "application/vnd.oci.descriptor.v1+json"
// MediaTypeImageManifest specifies the mediaType for an image manifest.
// MediaTypeImageManifest specifies the media type for an image manifest.
MediaTypeImageManifest = "application/vnd.oci.image.manifest.v1+json"
// MediaTypeImageManifestList specifies the mediaType for an image manifest list.
// MediaTypeImageManifestList specifies the media type for an image manifest list.
MediaTypeImageManifestList = "application/vnd.oci.image.manifest.list.v1+json"
// MediaTypeImageLayer is the mediaType used for layers referenced by the manifest.
// MediaTypeImageLayer is the media type used for layers referenced by the manifest.
MediaTypeImageLayer = "application/vnd.oci.image.layer.tar+gzip"
// MediaTypeImageConfig specifies the mediaType for the image configuration.
// MediaTypeImageLayerNonDistributable is the media type for layers referenced by
// the manifest but with distribution restrictions.
MediaTypeImageLayerNonDistributable = "application/vnd.oci.image.layer.nondistributable.tar+gzip"
// MediaTypeImageConfig specifies the media type for the image configuration.
MediaTypeImageConfig = "application/vnd.oci.image.config.v1+json"
)