read image config from docker v2s1 manifests
Signed-off-by: Antonio Murdaca <runcom@redhat.com>
This commit is contained in:
parent
bbe2dea0a6
commit
a0071de607
39 changed files with 1272 additions and 506 deletions
12
vendor/github.com/containers/image/docker/daemon/daemon_dest.go
generated
vendored
12
vendor/github.com/containers/image/docker/daemon/daemon_dest.go
generated
vendored
|
@ -151,7 +151,11 @@ func (d *daemonImageDestination) PutBlob(stream io.Reader, inputInfo types.BlobI
|
|||
return types.BlobInfo{}, errors.Errorf(`Can not stream a blob with unknown digest to "docker-daemon:"`)
|
||||
}
|
||||
|
||||
if ok, size, err := d.HasBlob(inputInfo); err == nil && ok {
|
||||
ok, size, err := d.HasBlob(inputInfo)
|
||||
if err != nil {
|
||||
return types.BlobInfo{}, err
|
||||
}
|
||||
if ok {
|
||||
return types.BlobInfo{Digest: inputInfo.Digest, Size: size}, nil
|
||||
}
|
||||
|
||||
|
@ -186,6 +190,10 @@ func (d *daemonImageDestination) PutBlob(stream io.Reader, inputInfo types.BlobI
|
|||
return types.BlobInfo{Digest: digester.Digest(), Size: inputInfo.Size}, nil
|
||||
}
|
||||
|
||||
// HasBlob returns true iff the image destination already contains a blob with the matching digest which can be reapplied using ReapplyBlob.
|
||||
// Unlike PutBlob, the digest can not be empty. If HasBlob returns true, the size of the blob must also be returned.
|
||||
// If the destination does not contain the blob, or it is unknown, HasBlob ordinarily returns (false, -1, nil);
|
||||
// it returns a non-nil error only on an unexpected failure.
|
||||
func (d *daemonImageDestination) HasBlob(info types.BlobInfo) (bool, int64, error) {
|
||||
if info.Digest == "" {
|
||||
return false, -1, errors.Errorf(`"Can not check for a blob with unknown digest`)
|
||||
|
@ -193,7 +201,7 @@ func (d *daemonImageDestination) HasBlob(info types.BlobInfo) (bool, int64, erro
|
|||
if blob, ok := d.blobs[info.Digest]; ok {
|
||||
return true, blob.Size, nil
|
||||
}
|
||||
return false, -1, types.ErrBlobNotFound
|
||||
return false, -1, nil
|
||||
}
|
||||
|
||||
func (d *daemonImageDestination) ReapplyBlob(info types.BlobInfo) (types.BlobInfo, error) {
|
||||
|
|
108
vendor/github.com/containers/image/docker/docker_client.go
generated
vendored
108
vendor/github.com/containers/image/docker/docker_client.go
generated
vendored
|
@ -18,8 +18,10 @@ import (
|
|||
"github.com/containers/image/docker/reference"
|
||||
"github.com/containers/image/types"
|
||||
"github.com/containers/storage/pkg/homedir"
|
||||
"github.com/docker/distribution/registry/client"
|
||||
"github.com/docker/go-connections/sockets"
|
||||
"github.com/docker/go-connections/tlsconfig"
|
||||
"github.com/opencontainers/go-digest"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
|
@ -32,20 +34,38 @@ const (
|
|||
dockerCfgFileName = "config.json"
|
||||
dockerCfgObsolete = ".dockercfg"
|
||||
|
||||
baseURL = "%s://%s/v2/"
|
||||
baseURLV1 = "%s://%s/v1/_ping"
|
||||
tagsURL = "%s/tags/list"
|
||||
manifestURL = "%s/manifests/%s"
|
||||
blobsURL = "%s/blobs/%s"
|
||||
blobUploadURL = "%s/blobs/uploads/"
|
||||
resolvedPingV2URL = "%s://%s/v2/"
|
||||
resolvedPingV1URL = "%s://%s/v1/_ping"
|
||||
tagsPath = "/v2/%s/tags/list"
|
||||
manifestPath = "/v2/%s/manifests/%s"
|
||||
blobsPath = "/v2/%s/blobs/%s"
|
||||
blobUploadPath = "/v2/%s/blobs/uploads/"
|
||||
extensionsSignaturePath = "/extensions/v2/%s/signatures/%s"
|
||||
|
||||
minimumTokenLifetimeSeconds = 60
|
||||
|
||||
extensionSignatureSchemaVersion = 2 // extensionSignature.Version
|
||||
extensionSignatureTypeAtomic = "atomic" // extensionSignature.Type
|
||||
)
|
||||
|
||||
// ErrV1NotSupported is returned when we're trying to talk to a
|
||||
// docker V1 registry.
|
||||
var ErrV1NotSupported = errors.New("can't talk to a V1 docker registry")
|
||||
|
||||
// extensionSignature and extensionSignatureList come from github.com/openshift/origin/pkg/dockerregistry/server/signaturedispatcher.go:
|
||||
// signature represents a Docker image signature.
|
||||
type extensionSignature struct {
|
||||
Version int `json:"schemaVersion"` // Version specifies the schema version
|
||||
Name string `json:"name"` // Name must be in "sha256:<digest>@signatureName" format
|
||||
Type string `json:"type"` // Type is optional, of not set it will be defaulted to "AtomicImageV1"
|
||||
Content []byte `json:"content"` // Content contains the signature
|
||||
}
|
||||
|
||||
// signatureList represents list of Docker image signatures.
|
||||
type extensionSignatureList struct {
|
||||
Signatures []extensionSignature `json:"signatures"`
|
||||
}
|
||||
|
||||
type bearerToken struct {
|
||||
Token string `json:"token"`
|
||||
ExpiresIn int `json:"expires_in"`
|
||||
|
@ -54,15 +74,20 @@ type bearerToken struct {
|
|||
|
||||
// dockerClient is configuration for dealing with a single Docker registry.
|
||||
type dockerClient struct {
|
||||
ctx *types.SystemContext
|
||||
registry string
|
||||
username string
|
||||
password string
|
||||
scheme string // Cache of a value returned by a successful ping() if not empty
|
||||
client *http.Client
|
||||
signatureBase signatureStorageBase
|
||||
challenges []challenge
|
||||
scope authScope
|
||||
// The following members are set by newDockerClient and do not change afterwards.
|
||||
ctx *types.SystemContext
|
||||
registry string
|
||||
username string
|
||||
password string
|
||||
client *http.Client
|
||||
signatureBase signatureStorageBase
|
||||
scope authScope
|
||||
// The following members are detected registry properties:
|
||||
// They are set after a successful detectProperties(), and never change afterwards.
|
||||
scheme string // Empty value also used to indicate detectProperties() has not yet succeeded.
|
||||
challenges []challenge
|
||||
supportsSignatures bool
|
||||
// The following members are private state for setupRequestAuth, both are valid if token != nil.
|
||||
token *bearerToken
|
||||
tokenExpiration time.Time
|
||||
}
|
||||
|
@ -209,15 +234,13 @@ func newDockerClient(ctx *types.SystemContext, ref dockerReference, write bool,
|
|||
}
|
||||
|
||||
// makeRequest creates and executes a http.Request with the specified parameters, adding authentication and TLS options for the Docker client.
|
||||
// url is NOT an absolute URL, but a path relative to the /v2/ top-level API path. The host name and schema is taken from the client or autodetected.
|
||||
func (c *dockerClient) makeRequest(method, url string, headers map[string][]string, stream io.Reader) (*http.Response, error) {
|
||||
if c.scheme == "" {
|
||||
if err := c.ping(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// The host name and schema is taken from the client or autodetected, and the path is relative to it, i.e. the path usually starts with /v2/.
|
||||
func (c *dockerClient) makeRequest(method, path string, headers map[string][]string, stream io.Reader) (*http.Response, error) {
|
||||
if err := c.detectProperties(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
url = fmt.Sprintf(baseURL, c.scheme, c.registry) + url
|
||||
url := fmt.Sprintf("%s://%s%s", c.scheme, c.registry, path)
|
||||
return c.makeRequestToResolvedURL(method, url, headers, stream, -1, true)
|
||||
}
|
||||
|
||||
|
@ -398,21 +421,28 @@ func getAuth(ctx *types.SystemContext, registry string) (string, string, error)
|
|||
return "", "", nil
|
||||
}
|
||||
|
||||
func (c *dockerClient) ping() error {
|
||||
// detectProperties detects various properties of the registry.
|
||||
// See the dockerClient documentation for members which are affected by this.
|
||||
func (c *dockerClient) detectProperties() error {
|
||||
if c.scheme != "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
ping := func(scheme string) error {
|
||||
url := fmt.Sprintf(baseURL, scheme, c.registry)
|
||||
url := fmt.Sprintf(resolvedPingV2URL, scheme, c.registry)
|
||||
resp, err := c.makeRequestToResolvedURL("GET", url, nil, nil, -1, true)
|
||||
logrus.Debugf("Ping %s err %#v", url, err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
logrus.Debugf("Ping %s status %d", scheme+"://"+c.registry+"/v2/", resp.StatusCode)
|
||||
logrus.Debugf("Ping %s status %d", url, resp.StatusCode)
|
||||
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusUnauthorized {
|
||||
return errors.Errorf("error pinging repository, response code %d", resp.StatusCode)
|
||||
}
|
||||
c.challenges = parseAuthHeader(resp.Header)
|
||||
c.scheme = scheme
|
||||
c.supportsSignatures = resp.Header.Get("X-Registry-Supports-Signatures") == "1"
|
||||
return nil
|
||||
}
|
||||
err := ping("https")
|
||||
|
@ -426,14 +456,14 @@ func (c *dockerClient) ping() error {
|
|||
}
|
||||
// best effort to understand if we're talking to a V1 registry
|
||||
pingV1 := func(scheme string) bool {
|
||||
url := fmt.Sprintf(baseURLV1, scheme, c.registry)
|
||||
url := fmt.Sprintf(resolvedPingV1URL, scheme, c.registry)
|
||||
resp, err := c.makeRequestToResolvedURL("GET", url, nil, nil, -1, true)
|
||||
logrus.Debugf("Ping %s err %#v", url, err)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
logrus.Debugf("Ping %s status %d", scheme+"://"+c.registry+"/v1/_ping", resp.StatusCode)
|
||||
logrus.Debugf("Ping %s status %d", url, resp.StatusCode)
|
||||
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusUnauthorized {
|
||||
return false
|
||||
}
|
||||
|
@ -450,6 +480,30 @@ func (c *dockerClient) ping() error {
|
|||
return err
|
||||
}
|
||||
|
||||
// getExtensionsSignatures returns signatures from the X-Registry-Supports-Signatures API extension,
|
||||
// using the original data structures.
|
||||
func (c *dockerClient) getExtensionsSignatures(ref dockerReference, manifestDigest digest.Digest) (*extensionSignatureList, error) {
|
||||
path := fmt.Sprintf(extensionsSignaturePath, reference.Path(ref.ref), manifestDigest)
|
||||
res, err := c.makeRequest("GET", path, nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return nil, client.HandleErrorResponse(res)
|
||||
}
|
||||
body, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var parsedBody extensionSignatureList
|
||||
if err := json.Unmarshal(body, &parsedBody); err != nil {
|
||||
return nil, errors.Wrapf(err, "Error decoding signature list")
|
||||
}
|
||||
return &parsedBody, nil
|
||||
}
|
||||
|
||||
func getDefaultConfigDir(confPath string) string {
|
||||
return filepath.Join(homedir.Get(), confPath)
|
||||
}
|
||||
|
|
4
vendor/github.com/containers/image/docker/docker_image.go
generated
vendored
4
vendor/github.com/containers/image/docker/docker_image.go
generated
vendored
|
@ -40,8 +40,8 @@ func (i *Image) SourceRefFullName() string {
|
|||
|
||||
// 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, reference.Path(i.src.ref.ref))
|
||||
res, err := i.src.c.makeRequest("GET", url, nil, nil)
|
||||
path := fmt.Sprintf(tagsPath, reference.Path(i.src.ref.ref))
|
||||
res, err := i.src.c.makeRequest("GET", path, nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
151
vendor/github.com/containers/image/docker/docker_image_dest.go
generated
vendored
151
vendor/github.com/containers/image/docker/docker_image_dest.go
generated
vendored
|
@ -2,6 +2,8 @@ package docker
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
@ -42,7 +44,7 @@ type dockerImageDestination struct {
|
|||
|
||||
// newImageDestination creates a new ImageDestination for the specified image reference.
|
||||
func newImageDestination(ctx *types.SystemContext, ref dockerReference) (types.ImageDestination, error) {
|
||||
c, err := newDockerClient(ctx, ref, true, "push")
|
||||
c, err := newDockerClient(ctx, ref, true, "pull,push")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -70,7 +72,17 @@ func (d *dockerImageDestination) SupportedManifestMIMETypes() []string {
|
|||
// SupportsSignatures returns an error (to be displayed to the user) if the destination certainly can't store signatures.
|
||||
// Note: It is still possible for PutSignatures to fail if SupportsSignatures returns nil.
|
||||
func (d *dockerImageDestination) SupportsSignatures() error {
|
||||
return errors.Errorf("Pushing signatures to a Docker Registry is not supported")
|
||||
if err := d.c.detectProperties(); err != nil {
|
||||
return err
|
||||
}
|
||||
switch {
|
||||
case d.c.signatureBase != nil:
|
||||
return nil
|
||||
case d.c.supportsSignatures:
|
||||
return nil
|
||||
default:
|
||||
return errors.Errorf("X-Registry-Supports-Signatures extension not supported, and lookaside is not configured")
|
||||
}
|
||||
}
|
||||
|
||||
// ShouldCompressLayers returns true iff it is desirable to compress layer blobs written to this destination.
|
||||
|
@ -101,26 +113,25 @@ func (c *sizeCounter) Write(p []byte) (n int, err error) {
|
|||
func (d *dockerImageDestination) PutBlob(stream io.Reader, inputInfo types.BlobInfo) (types.BlobInfo, error) {
|
||||
if inputInfo.Digest.String() != "" {
|
||||
haveBlob, size, err := d.HasBlob(inputInfo)
|
||||
if err != nil && err != types.ErrBlobNotFound {
|
||||
if err != nil {
|
||||
return types.BlobInfo{}, err
|
||||
}
|
||||
// Now err == nil || err == types.ErrBlobNotFound
|
||||
if err == nil && haveBlob {
|
||||
if haveBlob {
|
||||
return types.BlobInfo{Digest: inputInfo.Digest, Size: size}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME? Chunked upload, progress reporting, etc.
|
||||
uploadURL := fmt.Sprintf(blobUploadURL, reference.Path(d.ref.ref))
|
||||
logrus.Debugf("Uploading %s", uploadURL)
|
||||
res, err := d.c.makeRequest("POST", uploadURL, nil, nil)
|
||||
uploadPath := fmt.Sprintf(blobUploadPath, reference.Path(d.ref.ref))
|
||||
logrus.Debugf("Uploading %s", uploadPath)
|
||||
res, err := d.c.makeRequest("POST", uploadPath, nil, nil)
|
||||
if err != nil {
|
||||
return types.BlobInfo{}, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != http.StatusAccepted {
|
||||
logrus.Debugf("Error initiating layer upload, response %#v", *res)
|
||||
return types.BlobInfo{}, errors.Errorf("Error initiating layer upload to %s, status %d", uploadURL, res.StatusCode)
|
||||
return types.BlobInfo{}, errors.Errorf("Error initiating layer upload to %s, status %d", uploadPath, res.StatusCode)
|
||||
}
|
||||
uploadLocation, err := res.Location()
|
||||
if err != nil {
|
||||
|
@ -132,7 +143,7 @@ func (d *dockerImageDestination) PutBlob(stream io.Reader, inputInfo types.BlobI
|
|||
tee := io.TeeReader(stream, io.MultiWriter(digester.Hash(), sizeCounter))
|
||||
res, err = d.c.makeRequestToResolvedURL("PATCH", uploadLocation.String(), map[string][]string{"Content-Type": {"application/octet-stream"}}, tee, inputInfo.Size, true)
|
||||
if err != nil {
|
||||
logrus.Debugf("Error uploading layer chunked, response %#v", *res)
|
||||
logrus.Debugf("Error uploading layer chunked, response %#v", res)
|
||||
return types.BlobInfo{}, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
@ -163,14 +174,18 @@ func (d *dockerImageDestination) PutBlob(stream io.Reader, inputInfo types.BlobI
|
|||
return types.BlobInfo{Digest: computedDigest, Size: sizeCounter.size}, nil
|
||||
}
|
||||
|
||||
// HasBlob returns true iff the image destination already contains a blob with the matching digest which can be reapplied using ReapplyBlob.
|
||||
// Unlike PutBlob, the digest can not be empty. If HasBlob returns true, the size of the blob must also be returned.
|
||||
// If the destination does not contain the blob, or it is unknown, HasBlob ordinarily returns (false, -1, nil);
|
||||
// it returns a non-nil error only on an unexpected failure.
|
||||
func (d *dockerImageDestination) HasBlob(info types.BlobInfo) (bool, int64, error) {
|
||||
if info.Digest == "" {
|
||||
return false, -1, errors.Errorf(`"Can not check for a blob with unknown digest`)
|
||||
}
|
||||
checkURL := fmt.Sprintf(blobsURL, reference.Path(d.ref.ref), info.Digest.String())
|
||||
checkPath := fmt.Sprintf(blobsPath, reference.Path(d.ref.ref), info.Digest.String())
|
||||
|
||||
logrus.Debugf("Checking %s", checkURL)
|
||||
res, err := d.c.makeRequest("HEAD", checkURL, nil, nil)
|
||||
logrus.Debugf("Checking %s", checkPath)
|
||||
res, err := d.c.makeRequest("HEAD", checkPath, nil, nil)
|
||||
if err != nil {
|
||||
return false, -1, err
|
||||
}
|
||||
|
@ -184,7 +199,7 @@ func (d *dockerImageDestination) HasBlob(info types.BlobInfo) (bool, int64, erro
|
|||
return false, -1, errors.Errorf("not authorized to read from destination repository %s", reference.Path(d.ref.ref))
|
||||
case http.StatusNotFound:
|
||||
logrus.Debugf("... not present")
|
||||
return false, -1, types.ErrBlobNotFound
|
||||
return false, -1, nil
|
||||
default:
|
||||
return false, -1, errors.Errorf("failed to read from destination repository %s: %v", reference.Path(d.ref.ref), http.StatusText(res.StatusCode))
|
||||
}
|
||||
|
@ -205,14 +220,14 @@ func (d *dockerImageDestination) PutManifest(m []byte) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
url := fmt.Sprintf(manifestURL, reference.Path(d.ref.ref), refTail)
|
||||
path := fmt.Sprintf(manifestPath, reference.Path(d.ref.ref), refTail)
|
||||
|
||||
headers := map[string][]string{}
|
||||
mimeType := manifest.GuessMIMEType(m)
|
||||
if mimeType != "" {
|
||||
headers["Content-Type"] = []string{mimeType}
|
||||
}
|
||||
res, err := d.c.makeRequest("PUT", url, headers, bytes.NewReader(m))
|
||||
res, err := d.c.makeRequest("PUT", path, headers, bytes.NewReader(m))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -223,12 +238,32 @@ func (d *dockerImageDestination) PutManifest(m []byte) error {
|
|||
logrus.Debugf("Error body %s", string(body))
|
||||
}
|
||||
logrus.Debugf("Error uploading manifest, status %d, %#v", res.StatusCode, res)
|
||||
return errors.Errorf("Error uploading manifest to %s, status %d", url, res.StatusCode)
|
||||
return errors.Errorf("Error uploading manifest to %s, status %d", path, res.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *dockerImageDestination) PutSignatures(signatures [][]byte) error {
|
||||
// Do not fail if we don’t really need to support signatures.
|
||||
if len(signatures) == 0 {
|
||||
return nil
|
||||
}
|
||||
if err := d.c.detectProperties(); err != nil {
|
||||
return err
|
||||
}
|
||||
switch {
|
||||
case d.c.signatureBase != nil:
|
||||
return d.putSignaturesToLookaside(signatures)
|
||||
case d.c.supportsSignatures:
|
||||
return d.putSignaturesToAPIExtension(signatures)
|
||||
default:
|
||||
return errors.Errorf("X-Registry-Supports-Signatures extension not supported, and lookaside is not configured")
|
||||
}
|
||||
}
|
||||
|
||||
// putSignaturesToLookaside implements PutSignatures() from the lookaside location configured in s.c.signatureBase,
|
||||
// which is not nil.
|
||||
func (d *dockerImageDestination) putSignaturesToLookaside(signatures [][]byte) error {
|
||||
// FIXME? This overwrites files one at a time, definitely not atomic.
|
||||
// A failure when updating signatures with a reordered copy could lose some of them.
|
||||
|
||||
|
@ -236,15 +271,13 @@ func (d *dockerImageDestination) PutSignatures(signatures [][]byte) error {
|
|||
if len(signatures) == 0 {
|
||||
return nil
|
||||
}
|
||||
if d.c.signatureBase == nil {
|
||||
return errors.Errorf("Pushing signatures to a Docker Registry is not supported, and there is no applicable signature storage configured")
|
||||
}
|
||||
|
||||
if d.manifestDigest.String() == "" {
|
||||
// This shouldn’t happen, ImageDestination users are required to call PutManifest before PutSignatures
|
||||
return errors.Errorf("Unknown manifest digest, can't add signatures")
|
||||
}
|
||||
|
||||
// NOTE: Keep this in sync with docs/signature-protocols.md!
|
||||
for i, signature := range signatures {
|
||||
url := signatureStorageURL(d.c.signatureBase, d.manifestDigest, i)
|
||||
if url == nil {
|
||||
|
@ -278,6 +311,7 @@ func (d *dockerImageDestination) PutSignatures(signatures [][]byte) error {
|
|||
}
|
||||
|
||||
// putOneSignature stores one signature to url.
|
||||
// NOTE: Keep this in sync with docs/signature-protocols.md!
|
||||
func (d *dockerImageDestination) putOneSignature(url *url.URL, signature []byte) error {
|
||||
switch url.Scheme {
|
||||
case "file":
|
||||
|
@ -301,6 +335,7 @@ func (d *dockerImageDestination) putOneSignature(url *url.URL, signature []byte)
|
|||
|
||||
// deleteOneSignature deletes a signature from url, if it exists.
|
||||
// If it successfully determines that the signature does not exist, returns (true, nil)
|
||||
// NOTE: Keep this in sync with docs/signature-protocols.md!
|
||||
func (c *dockerClient) deleteOneSignature(url *url.URL) (missing bool, err error) {
|
||||
switch url.Scheme {
|
||||
case "file":
|
||||
|
@ -318,6 +353,82 @@ func (c *dockerClient) deleteOneSignature(url *url.URL) (missing bool, err error
|
|||
}
|
||||
}
|
||||
|
||||
// putSignaturesToAPIExtension implements PutSignatures() using the X-Registry-Supports-Signatures API extension.
|
||||
func (d *dockerImageDestination) putSignaturesToAPIExtension(signatures [][]byte) error {
|
||||
// Skip dealing with the manifest digest, or reading the old state, if not necessary.
|
||||
if len(signatures) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if d.manifestDigest.String() == "" {
|
||||
// This shouldn’t happen, ImageDestination users are required to call PutManifest before PutSignatures
|
||||
return errors.Errorf("Unknown manifest digest, can't add signatures")
|
||||
}
|
||||
|
||||
// Because image signatures are a shared resource in Atomic Registry, the default upload
|
||||
// always adds signatures. Eventually we should also allow removing signatures,
|
||||
// but the X-Registry-Supports-Signatures API extension does not support that yet.
|
||||
|
||||
existingSignatures, err := d.c.getExtensionsSignatures(d.ref, d.manifestDigest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
existingSigNames := map[string]struct{}{}
|
||||
for _, sig := range existingSignatures.Signatures {
|
||||
existingSigNames[sig.Name] = struct{}{}
|
||||
}
|
||||
|
||||
sigExists:
|
||||
for _, newSig := range signatures {
|
||||
for _, existingSig := range existingSignatures.Signatures {
|
||||
if existingSig.Version == extensionSignatureSchemaVersion && existingSig.Type == extensionSignatureTypeAtomic && bytes.Equal(existingSig.Content, newSig) {
|
||||
continue sigExists
|
||||
}
|
||||
}
|
||||
|
||||
// The API expect us to invent a new unique name. This is racy, but hopefully good enough.
|
||||
var signatureName string
|
||||
for {
|
||||
randBytes := make([]byte, 16)
|
||||
n, err := rand.Read(randBytes)
|
||||
if err != nil || n != 16 {
|
||||
return errors.Wrapf(err, "Error generating random signature len %d", n)
|
||||
}
|
||||
signatureName = fmt.Sprintf("%s@%032x", d.manifestDigest.String(), randBytes)
|
||||
if _, ok := existingSigNames[signatureName]; !ok {
|
||||
break
|
||||
}
|
||||
}
|
||||
sig := extensionSignature{
|
||||
Version: extensionSignatureSchemaVersion,
|
||||
Name: signatureName,
|
||||
Type: extensionSignatureTypeAtomic,
|
||||
Content: newSig,
|
||||
}
|
||||
body, err := json.Marshal(sig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
path := fmt.Sprintf(extensionsSignaturePath, reference.Path(d.ref.ref), d.manifestDigest.String())
|
||||
res, err := d.c.makeRequest("PUT", path, nil, bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != http.StatusCreated {
|
||||
body, err := ioutil.ReadAll(res.Body)
|
||||
if err == nil {
|
||||
logrus.Debugf("Error body %s", string(body))
|
||||
}
|
||||
logrus.Debugf("Error uploading signature, status %d, %#v", res.StatusCode, res)
|
||||
return errors.Errorf("Error uploading signature to %s, status %d", path, res.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Commit marks the process of storing the image as successful and asks for the image to be persisted.
|
||||
// WARNING: This does not have any transactional semantics:
|
||||
// - Uploaded data MAY be visible to others before Commit() is called
|
||||
|
|
60
vendor/github.com/containers/image/docker/docker_image_src.go
generated
vendored
60
vendor/github.com/containers/image/docker/docker_image_src.go
generated
vendored
|
@ -93,10 +93,10 @@ func (s *dockerImageSource) GetManifest() ([]byte, string, error) {
|
|||
}
|
||||
|
||||
func (s *dockerImageSource) fetchManifest(tagOrDigest string) ([]byte, string, error) {
|
||||
url := fmt.Sprintf(manifestURL, reference.Path(s.ref.ref), tagOrDigest)
|
||||
path := fmt.Sprintf(manifestPath, reference.Path(s.ref.ref), tagOrDigest)
|
||||
headers := make(map[string][]string)
|
||||
headers["Accept"] = s.requestedManifestMIMETypes
|
||||
res, err := s.c.makeRequest("GET", url, headers, nil)
|
||||
res, err := s.c.makeRequest("GET", path, headers, nil)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
@ -179,9 +179,9 @@ func (s *dockerImageSource) GetBlob(info types.BlobInfo) (io.ReadCloser, int64,
|
|||
return s.getExternalBlob(info.URLs)
|
||||
}
|
||||
|
||||
url := fmt.Sprintf(blobsURL, reference.Path(s.ref.ref), info.Digest.String())
|
||||
logrus.Debugf("Downloading %s", url)
|
||||
res, err := s.c.makeRequest("GET", url, nil, nil)
|
||||
path := fmt.Sprintf(blobsPath, reference.Path(s.ref.ref), info.Digest.String())
|
||||
logrus.Debugf("Downloading %s", path)
|
||||
res, err := s.c.makeRequest("GET", path, nil, nil)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
@ -193,10 +193,22 @@ func (s *dockerImageSource) GetBlob(info types.BlobInfo) (io.ReadCloser, int64,
|
|||
}
|
||||
|
||||
func (s *dockerImageSource) GetSignatures() ([][]byte, error) {
|
||||
if s.c.signatureBase == nil { // Skip dealing with the manifest digest if not necessary.
|
||||
if err := s.c.detectProperties(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch {
|
||||
case s.c.signatureBase != nil:
|
||||
return s.getSignaturesFromLookaside()
|
||||
case s.c.supportsSignatures:
|
||||
return s.getSignaturesFromAPIExtension()
|
||||
default:
|
||||
return [][]byte{}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// getSignaturesFromLookaside implements GetSignatures() from the lookaside location configured in s.c.signatureBase,
|
||||
// which is not nil.
|
||||
func (s *dockerImageSource) getSignaturesFromLookaside() ([][]byte, error) {
|
||||
if err := s.ensureManifestIsLoaded(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -205,6 +217,7 @@ func (s *dockerImageSource) GetSignatures() ([][]byte, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
// NOTE: Keep this in sync with docs/signature-protocols.md!
|
||||
signatures := [][]byte{}
|
||||
for i := 0; ; i++ {
|
||||
url := signatureStorageURL(s.c.signatureBase, manifestDigest, i)
|
||||
|
@ -225,6 +238,7 @@ func (s *dockerImageSource) GetSignatures() ([][]byte, error) {
|
|||
|
||||
// getOneSignature downloads one signature from url.
|
||||
// If it successfully determines that the signature does not exist, returns with missing set to true and error set to nil.
|
||||
// NOTE: Keep this in sync with docs/signature-protocols.md!
|
||||
func (s *dockerImageSource) getOneSignature(url *url.URL) (signature []byte, missing bool, err error) {
|
||||
switch url.Scheme {
|
||||
case "file":
|
||||
|
@ -261,6 +275,30 @@ func (s *dockerImageSource) getOneSignature(url *url.URL) (signature []byte, mis
|
|||
}
|
||||
}
|
||||
|
||||
// getSignaturesFromAPIExtension implements GetSignatures() using the X-Registry-Supports-Signatures API extension.
|
||||
func (s *dockerImageSource) getSignaturesFromAPIExtension() ([][]byte, error) {
|
||||
if err := s.ensureManifestIsLoaded(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
manifestDigest, err := manifest.Digest(s.cachedManifest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
parsedBody, err := s.c.getExtensionsSignatures(s.ref, manifestDigest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var sigs [][]byte
|
||||
for _, sig := range parsedBody.Signatures {
|
||||
if sig.Version == extensionSignatureSchemaVersion && sig.Type == extensionSignatureTypeAtomic {
|
||||
sigs = append(sigs, sig.Content)
|
||||
}
|
||||
}
|
||||
return sigs, nil
|
||||
}
|
||||
|
||||
// deleteImage deletes the named image from the registry, if supported.
|
||||
func deleteImage(ctx *types.SystemContext, ref dockerReference) error {
|
||||
c, err := newDockerClient(ctx, ref, true, "push")
|
||||
|
@ -277,8 +315,8 @@ func deleteImage(ctx *types.SystemContext, ref dockerReference) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
getURL := fmt.Sprintf(manifestURL, reference.Path(ref.ref), refTail)
|
||||
get, err := c.makeRequest("GET", getURL, headers, nil)
|
||||
getPath := fmt.Sprintf(manifestPath, reference.Path(ref.ref), refTail)
|
||||
get, err := c.makeRequest("GET", getPath, headers, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -296,11 +334,11 @@ func deleteImage(ctx *types.SystemContext, ref dockerReference) error {
|
|||
}
|
||||
|
||||
digest := get.Header.Get("Docker-Content-Digest")
|
||||
deleteURL := fmt.Sprintf(manifestURL, reference.Path(ref.ref), digest)
|
||||
deletePath := fmt.Sprintf(manifestPath, reference.Path(ref.ref), digest)
|
||||
|
||||
// When retrieving the digest from a registry >= 2.3 use the following header:
|
||||
// "Accept": "application/vnd.docker.distribution.manifest.v2+json"
|
||||
delete, err := c.makeRequest("DELETE", deleteURL, headers, nil)
|
||||
delete, err := c.makeRequest("DELETE", deletePath, headers, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -311,7 +349,7 @@ func deleteImage(ctx *types.SystemContext, ref dockerReference) error {
|
|||
return err
|
||||
}
|
||||
if delete.StatusCode != http.StatusAccepted {
|
||||
return errors.Errorf("Failed to delete %v: %s (%v)", deleteURL, string(body), delete.Status)
|
||||
return errors.Errorf("Failed to delete %v: %s (%v)", deletePath, string(body), delete.Status)
|
||||
}
|
||||
|
||||
if c.signatureBase != nil {
|
||||
|
|
14
vendor/github.com/containers/image/docker/lookaside.go
generated
vendored
14
vendor/github.com/containers/image/docker/lookaside.go
generated
vendored
|
@ -9,12 +9,12 @@ import (
|
|||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/containers/image/docker/reference"
|
||||
"github.com/containers/image/types"
|
||||
"github.com/ghodss/yaml"
|
||||
"github.com/opencontainers/go-digest"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/containers/image/types"
|
||||
)
|
||||
|
||||
// systemRegistriesDirPath is the path to registries.d, used for locating lookaside Docker signature storage.
|
||||
|
@ -63,9 +63,10 @@ func configuredSignatureStorageBase(ctx *types.SystemContext, ref dockerReferenc
|
|||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "Invalid signature storage URL %s", topLevel)
|
||||
}
|
||||
// NOTE: Keep this in sync with docs/signature-protocols.md!
|
||||
// FIXME? Restrict to explicitly supported schemes?
|
||||
repo := ref.ref.Name() // Note that this is without a tag or digest.
|
||||
if path.Clean(repo) != repo { // Coverage: This should not be reachable because /./ and /../ components are not valid in docker references
|
||||
repo := reference.Path(ref.ref) // Note that this is without a tag or digest.
|
||||
if path.Clean(repo) != repo { // Coverage: This should not be reachable because /./ and /../ components are not valid in docker references
|
||||
return nil, errors.Errorf("Unexpected path elements in Docker reference %s for signature storage", ref.ref.String())
|
||||
}
|
||||
url.Path = url.Path + "/" + repo
|
||||
|
@ -190,11 +191,12 @@ func (ns registryNamespace) signatureTopLevel(write bool) string {
|
|||
|
||||
// signatureStorageURL returns an URL usable for acessing signature index in base with known manifestDigest, or nil if not applicable.
|
||||
// Returns nil iff base == nil.
|
||||
// NOTE: Keep this in sync with docs/signature-protocols.md!
|
||||
func signatureStorageURL(base signatureStorageBase, manifestDigest digest.Digest, index int) *url.URL {
|
||||
if base == nil {
|
||||
return nil
|
||||
}
|
||||
url := *base
|
||||
url.Path = fmt.Sprintf("%s@%s/signature-%d", url.Path, manifestDigest.String(), index+1)
|
||||
url.Path = fmt.Sprintf("%s@%s=%s/signature-%d", url.Path, manifestDigest.Algorithm(), manifestDigest.Hex(), index+1)
|
||||
return &url
|
||||
}
|
||||
|
|
21
vendor/github.com/containers/image/docker/lookaside_test.go
generated
vendored
21
vendor/github.com/containers/image/docker/lookaside_test.go
generated
vendored
|
@ -46,7 +46,7 @@ func TestConfiguredSignatureStorageBase(t *testing.T) {
|
|||
dockerRefFromString(t, "//example.com/my/project"), false)
|
||||
assert.NoError(t, err)
|
||||
require.NotNil(t, base)
|
||||
assert.Equal(t, "https://sigstore.example.com/example.com/my/project", (*url.URL)(base).String())
|
||||
assert.Equal(t, "https://sigstore.example.com/my/project", (*url.URL)(base).String())
|
||||
}
|
||||
|
||||
func TestRegistriesDirPath(t *testing.T) {
|
||||
|
@ -252,26 +252,27 @@ func TestRegistryNamespaceSignatureTopLevel(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestSignatureStorageBaseSignatureStorageURL(t *testing.T) {
|
||||
const md = "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
|
||||
const mdInput = "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
|
||||
const mdMapped = "sha256=0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
|
||||
|
||||
assert.True(t, signatureStorageURL(nil, md, 0) == nil)
|
||||
assert.True(t, signatureStorageURL(nil, mdInput, 0) == nil)
|
||||
for _, c := range []struct {
|
||||
base string
|
||||
index int
|
||||
expected string
|
||||
}{
|
||||
{"file:///tmp", 0, "file:///tmp@" + md + "/signature-1"},
|
||||
{"file:///tmp", 1, "file:///tmp@" + md + "/signature-2"},
|
||||
{"https://localhost:5555/root", 0, "https://localhost:5555/root@" + md + "/signature-1"},
|
||||
{"https://localhost:5555/root", 1, "https://localhost:5555/root@" + md + "/signature-2"},
|
||||
{"http://localhost:5555/root", 0, "http://localhost:5555/root@" + md + "/signature-1"},
|
||||
{"http://localhost:5555/root", 1, "http://localhost:5555/root@" + md + "/signature-2"},
|
||||
{"file:///tmp", 0, "file:///tmp@" + mdMapped + "/signature-1"},
|
||||
{"file:///tmp", 1, "file:///tmp@" + mdMapped + "/signature-2"},
|
||||
{"https://localhost:5555/root", 0, "https://localhost:5555/root@" + mdMapped + "/signature-1"},
|
||||
{"https://localhost:5555/root", 1, "https://localhost:5555/root@" + mdMapped + "/signature-2"},
|
||||
{"http://localhost:5555/root", 0, "http://localhost:5555/root@" + mdMapped + "/signature-1"},
|
||||
{"http://localhost:5555/root", 1, "http://localhost:5555/root@" + mdMapped + "/signature-2"},
|
||||
} {
|
||||
url, err := url.Parse(c.base)
|
||||
require.NoError(t, err)
|
||||
expectedURL, err := url.Parse(c.expected)
|
||||
require.NoError(t, err)
|
||||
res := signatureStorageURL(url, md, c.index)
|
||||
res := signatureStorageURL(url, mdInput, c.index)
|
||||
assert.Equal(t, expectedURL, res, c.expected)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue