2016-07-20 09:46:01 +00:00
|
|
|
|
package docker
|
|
|
|
|
|
|
|
|
|
import (
|
2017-08-05 11:40:46 +00:00
|
|
|
|
"context"
|
2016-07-20 09:46:01 +00:00
|
|
|
|
"fmt"
|
|
|
|
|
"io"
|
|
|
|
|
"io/ioutil"
|
|
|
|
|
"mime"
|
|
|
|
|
"net/http"
|
2016-09-17 13:50:35 +00:00
|
|
|
|
"net/url"
|
|
|
|
|
"os"
|
2016-07-20 09:46:01 +00:00
|
|
|
|
"strconv"
|
|
|
|
|
|
2017-03-13 16:33:17 +00:00
|
|
|
|
"github.com/containers/image/docker/reference"
|
2016-07-20 09:46:01 +00:00
|
|
|
|
"github.com/containers/image/manifest"
|
|
|
|
|
"github.com/containers/image/types"
|
2016-11-22 19:32:10 +00:00
|
|
|
|
"github.com/docker/distribution/registry/client"
|
2016-10-17 13:53:40 +00:00
|
|
|
|
"github.com/opencontainers/go-digest"
|
|
|
|
|
"github.com/pkg/errors"
|
2017-08-05 11:40:46 +00:00
|
|
|
|
"github.com/sirupsen/logrus"
|
2016-07-20 09:46:01 +00:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type dockerImageSource struct {
|
2017-09-13 17:01:06 +00:00
|
|
|
|
ref dockerReference
|
|
|
|
|
c *dockerClient
|
2016-09-17 13:50:35 +00:00
|
|
|
|
// State
|
|
|
|
|
cachedManifest []byte // nil if not loaded yet
|
|
|
|
|
cachedManifestMIMEType string // Only valid if cachedManifest != nil
|
2016-07-20 09:46:01 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-09-13 17:01:06 +00:00
|
|
|
|
// newImageSource creates a new ImageSource for the specified image reference.
|
2016-09-17 13:50:35 +00:00
|
|
|
|
// The caller must call .Close() on the returned ImageSource.
|
2017-09-13 17:01:06 +00:00
|
|
|
|
func newImageSource(ctx *types.SystemContext, ref dockerReference) (*dockerImageSource, error) {
|
2017-08-29 14:54:45 +00:00
|
|
|
|
c, err := newDockerClientFromRef(ctx, ref, false, "pull")
|
2016-07-20 09:46:01 +00:00
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
return &dockerImageSource{
|
|
|
|
|
ref: ref,
|
2017-09-13 17:01:06 +00:00
|
|
|
|
c: c,
|
2016-07-20 09:46:01 +00:00
|
|
|
|
}, 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 *dockerImageSource) Reference() types.ImageReference {
|
|
|
|
|
return s.ref
|
|
|
|
|
}
|
|
|
|
|
|
2016-09-17 13:50:35 +00:00
|
|
|
|
// Close removes resources associated with an initialized ImageSource, if any.
|
2017-03-13 16:33:17 +00:00
|
|
|
|
func (s *dockerImageSource) Close() error {
|
|
|
|
|
return nil
|
2016-09-17 13:50:35 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-07-20 09:46:01 +00:00
|
|
|
|
// simplifyContentType drops parameters from a HTTP media type (see https://tools.ietf.org/html/rfc7231#section-3.1.1.1)
|
|
|
|
|
// Alternatively, an empty string is returned unchanged, and invalid values are "simplified" to an empty string.
|
|
|
|
|
func simplifyContentType(contentType string) string {
|
|
|
|
|
if contentType == "" {
|
|
|
|
|
return contentType
|
|
|
|
|
}
|
|
|
|
|
mimeType, _, err := mime.ParseMediaType(contentType)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return ""
|
|
|
|
|
}
|
|
|
|
|
return mimeType
|
|
|
|
|
}
|
|
|
|
|
|
2016-11-22 19:32:10 +00:00
|
|
|
|
// GetManifest returns the image's manifest along with its MIME type (which may be empty when it can't be determined but the manifest is available).
|
|
|
|
|
// It may use a remote (= slow) service.
|
2016-09-17 13:50:35 +00:00
|
|
|
|
func (s *dockerImageSource) GetManifest() ([]byte, string, error) {
|
2017-08-05 11:40:46 +00:00
|
|
|
|
err := s.ensureManifestIsLoaded(context.TODO())
|
2016-07-20 09:46:01 +00:00
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, "", err
|
|
|
|
|
}
|
2016-09-17 13:50:35 +00:00
|
|
|
|
return s.cachedManifest, s.cachedManifestMIMEType, nil
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-05 11:40:46 +00:00
|
|
|
|
func (s *dockerImageSource) fetchManifest(ctx context.Context, tagOrDigest string) ([]byte, string, error) {
|
2017-04-03 07:22:44 +00:00
|
|
|
|
path := fmt.Sprintf(manifestPath, reference.Path(s.ref.ref), tagOrDigest)
|
2016-11-22 19:32:10 +00:00
|
|
|
|
headers := make(map[string][]string)
|
2017-09-13 17:01:06 +00:00
|
|
|
|
headers["Accept"] = manifest.DefaultRequestedManifestMIMETypes
|
2017-08-05 11:40:46 +00:00
|
|
|
|
res, err := s.c.makeRequest(ctx, "GET", path, headers, nil)
|
2016-11-22 19:32:10 +00:00
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, "", err
|
|
|
|
|
}
|
|
|
|
|
defer res.Body.Close()
|
|
|
|
|
if res.StatusCode != http.StatusOK {
|
|
|
|
|
return nil, "", client.HandleErrorResponse(res)
|
|
|
|
|
}
|
|
|
|
|
manblob, err := ioutil.ReadAll(res.Body)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, "", err
|
|
|
|
|
}
|
|
|
|
|
return manblob, simplifyContentType(res.Header.Get("Content-Type")), nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// GetTargetManifest returns an image's manifest given a digest.
|
|
|
|
|
// This is mainly used to retrieve a single image's manifest out of a manifest list.
|
|
|
|
|
func (s *dockerImageSource) GetTargetManifest(digest digest.Digest) ([]byte, string, error) {
|
2017-08-05 11:40:46 +00:00
|
|
|
|
return s.fetchManifest(context.TODO(), digest.String())
|
2016-11-22 19:32:10 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-09-17 13:50:35 +00:00
|
|
|
|
// ensureManifestIsLoaded sets s.cachedManifest and s.cachedManifestMIMEType
|
|
|
|
|
//
|
|
|
|
|
// ImageSource implementations are not required or expected to do any caching,
|
|
|
|
|
// but because our signatures are “attached” to the manifest digest,
|
|
|
|
|
// we need to ensure that the digest of the manifest returned by GetManifest
|
|
|
|
|
// and used by GetSignatures are consistent, otherwise we would get spurious
|
|
|
|
|
// signature verification failures when pulling while a tag is being updated.
|
2017-08-05 11:40:46 +00:00
|
|
|
|
func (s *dockerImageSource) ensureManifestIsLoaded(ctx context.Context) error {
|
2016-09-17 13:50:35 +00:00
|
|
|
|
if s.cachedManifest != nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
reference, err := s.ref.tagOrDigest()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2016-11-22 19:32:10 +00:00
|
|
|
|
|
2017-08-05 11:40:46 +00:00
|
|
|
|
manblob, mt, err := s.fetchManifest(ctx, reference)
|
2016-07-20 09:46:01 +00:00
|
|
|
|
if err != nil {
|
2016-09-17 13:50:35 +00:00
|
|
|
|
return err
|
2016-07-20 09:46:01 +00:00
|
|
|
|
}
|
|
|
|
|
// We might validate manblob against the Docker-Content-Digest header here to protect against transport errors.
|
2016-09-17 13:50:35 +00:00
|
|
|
|
s.cachedManifest = manblob
|
2016-11-22 19:32:10 +00:00
|
|
|
|
s.cachedManifestMIMEType = mt
|
2016-09-17 13:50:35 +00:00
|
|
|
|
return nil
|
2016-07-20 09:46:01 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-11-22 19:32:10 +00:00
|
|
|
|
func (s *dockerImageSource) getExternalBlob(urls []string) (io.ReadCloser, int64, error) {
|
|
|
|
|
var (
|
|
|
|
|
resp *http.Response
|
|
|
|
|
err error
|
|
|
|
|
)
|
|
|
|
|
for _, url := range urls {
|
2017-08-05 11:40:46 +00:00
|
|
|
|
resp, err = s.c.makeRequestToResolvedURL(context.TODO(), "GET", url, nil, nil, -1, false)
|
2016-11-22 19:32:10 +00:00
|
|
|
|
if err == nil {
|
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
2016-10-17 13:53:40 +00:00
|
|
|
|
err = errors.Errorf("error fetching external blob from %q: %d", url, resp.StatusCode)
|
2016-11-22 19:32:10 +00:00
|
|
|
|
logrus.Debug(err)
|
|
|
|
|
continue
|
|
|
|
|
}
|
2017-10-10 14:11:06 +00:00
|
|
|
|
break
|
2016-11-22 19:32:10 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if resp.Body != nil && err == nil {
|
|
|
|
|
return resp.Body, getBlobSize(resp), nil
|
|
|
|
|
}
|
|
|
|
|
return nil, 0, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func getBlobSize(resp *http.Response) int64 {
|
|
|
|
|
size, err := strconv.ParseInt(resp.Header.Get("Content-Length"), 10, 64)
|
|
|
|
|
if err != nil {
|
|
|
|
|
size = -1
|
|
|
|
|
}
|
|
|
|
|
return size
|
|
|
|
|
}
|
|
|
|
|
|
2016-09-17 13:50:35 +00:00
|
|
|
|
// GetBlob returns a stream for the specified blob, and the blob’s size (or -1 if unknown).
|
2016-11-22 19:32:10 +00:00
|
|
|
|
func (s *dockerImageSource) GetBlob(info types.BlobInfo) (io.ReadCloser, int64, error) {
|
|
|
|
|
if len(info.URLs) != 0 {
|
|
|
|
|
return s.getExternalBlob(info.URLs)
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-03 07:22:44 +00:00
|
|
|
|
path := fmt.Sprintf(blobsPath, reference.Path(s.ref.ref), info.Digest.String())
|
|
|
|
|
logrus.Debugf("Downloading %s", path)
|
2017-08-05 11:40:46 +00:00
|
|
|
|
res, err := s.c.makeRequest(context.TODO(), "GET", path, nil, nil)
|
2016-07-20 09:46:01 +00:00
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, 0, err
|
|
|
|
|
}
|
|
|
|
|
if res.StatusCode != http.StatusOK {
|
|
|
|
|
// print url also
|
2016-10-17 13:53:40 +00:00
|
|
|
|
return nil, 0, errors.Errorf("Invalid status code returned when fetching blob %d", res.StatusCode)
|
2016-07-20 09:46:01 +00:00
|
|
|
|
}
|
2016-11-22 19:32:10 +00:00
|
|
|
|
return res.Body, getBlobSize(res), nil
|
2016-07-20 09:46:01 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-08-05 11:40:46 +00:00
|
|
|
|
func (s *dockerImageSource) GetSignatures(ctx context.Context) ([][]byte, error) {
|
|
|
|
|
if err := s.c.detectProperties(ctx); err != nil {
|
2017-04-03 07:22:44 +00:00
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
switch {
|
|
|
|
|
case s.c.signatureBase != nil:
|
2017-08-05 11:40:46 +00:00
|
|
|
|
return s.getSignaturesFromLookaside(ctx)
|
2017-04-03 07:22:44 +00:00
|
|
|
|
case s.c.supportsSignatures:
|
2017-08-05 11:40:46 +00:00
|
|
|
|
return s.getSignaturesFromAPIExtension(ctx)
|
2017-04-03 07:22:44 +00:00
|
|
|
|
default:
|
2016-09-17 13:50:35 +00:00
|
|
|
|
return [][]byte{}, nil
|
|
|
|
|
}
|
2017-04-03 07:22:44 +00:00
|
|
|
|
}
|
2016-09-17 13:50:35 +00:00
|
|
|
|
|
2017-08-05 11:40:46 +00:00
|
|
|
|
// manifestDigest returns a digest of the manifest, either from the supplied reference or from a fetched manifest.
|
|
|
|
|
func (s *dockerImageSource) manifestDigest(ctx context.Context) (digest.Digest, error) {
|
|
|
|
|
if digested, ok := s.ref.ref.(reference.Digested); ok {
|
|
|
|
|
d := digested.Digest()
|
|
|
|
|
if d.Algorithm() == digest.Canonical {
|
|
|
|
|
return d, nil
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if err := s.ensureManifestIsLoaded(ctx); err != nil {
|
|
|
|
|
return "", err
|
|
|
|
|
}
|
|
|
|
|
return manifest.Digest(s.cachedManifest)
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-03 07:22:44 +00:00
|
|
|
|
// getSignaturesFromLookaside implements GetSignatures() from the lookaside location configured in s.c.signatureBase,
|
|
|
|
|
// which is not nil.
|
2017-08-05 11:40:46 +00:00
|
|
|
|
func (s *dockerImageSource) getSignaturesFromLookaside(ctx context.Context) ([][]byte, error) {
|
|
|
|
|
manifestDigest, err := s.manifestDigest(ctx)
|
2016-09-17 13:50:35 +00:00
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-03 07:22:44 +00:00
|
|
|
|
// NOTE: Keep this in sync with docs/signature-protocols.md!
|
2016-09-17 13:50:35 +00:00
|
|
|
|
signatures := [][]byte{}
|
|
|
|
|
for i := 0; ; i++ {
|
|
|
|
|
url := signatureStorageURL(s.c.signatureBase, manifestDigest, i)
|
|
|
|
|
if url == nil {
|
2016-10-17 13:53:40 +00:00
|
|
|
|
return nil, errors.Errorf("Internal error: signatureStorageURL with non-nil base returned nil")
|
2016-09-17 13:50:35 +00:00
|
|
|
|
}
|
2017-08-05 11:40:46 +00:00
|
|
|
|
signature, missing, err := s.getOneSignature(ctx, url)
|
2016-09-17 13:50:35 +00:00
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if missing {
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
signatures = append(signatures, signature)
|
|
|
|
|
}
|
|
|
|
|
return signatures, nil
|
2016-07-20 09:46:01 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-09-17 13:50:35 +00:00
|
|
|
|
// 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.
|
2017-04-03 07:22:44 +00:00
|
|
|
|
// NOTE: Keep this in sync with docs/signature-protocols.md!
|
2017-08-05 11:40:46 +00:00
|
|
|
|
func (s *dockerImageSource) getOneSignature(ctx context.Context, url *url.URL) (signature []byte, missing bool, err error) {
|
2016-09-17 13:50:35 +00:00
|
|
|
|
switch url.Scheme {
|
|
|
|
|
case "file":
|
|
|
|
|
logrus.Debugf("Reading %s", url.Path)
|
|
|
|
|
sig, err := ioutil.ReadFile(url.Path)
|
|
|
|
|
if err != nil {
|
|
|
|
|
if os.IsNotExist(err) {
|
|
|
|
|
return nil, true, nil
|
|
|
|
|
}
|
|
|
|
|
return nil, false, err
|
|
|
|
|
}
|
|
|
|
|
return sig, false, nil
|
|
|
|
|
|
|
|
|
|
case "http", "https":
|
|
|
|
|
logrus.Debugf("GET %s", url)
|
2017-08-05 11:40:46 +00:00
|
|
|
|
req, err := http.NewRequest("GET", url.String(), nil)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, false, err
|
|
|
|
|
}
|
|
|
|
|
req = req.WithContext(ctx)
|
|
|
|
|
res, err := s.c.client.Do(req)
|
2016-09-17 13:50:35 +00:00
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, false, err
|
|
|
|
|
}
|
|
|
|
|
defer res.Body.Close()
|
|
|
|
|
if res.StatusCode == http.StatusNotFound {
|
|
|
|
|
return nil, true, nil
|
|
|
|
|
} else if res.StatusCode != http.StatusOK {
|
2016-10-17 13:53:40 +00:00
|
|
|
|
return nil, false, errors.Errorf("Error reading signature from %s: status %d", url.String(), res.StatusCode)
|
2016-09-17 13:50:35 +00:00
|
|
|
|
}
|
|
|
|
|
sig, err := ioutil.ReadAll(res.Body)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, false, err
|
|
|
|
|
}
|
|
|
|
|
return sig, false, nil
|
|
|
|
|
|
|
|
|
|
default:
|
2016-10-17 13:53:40 +00:00
|
|
|
|
return nil, false, errors.Errorf("Unsupported scheme when reading signature from %s", url.String())
|
2016-09-17 13:50:35 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-03 07:22:44 +00:00
|
|
|
|
// getSignaturesFromAPIExtension implements GetSignatures() using the X-Registry-Supports-Signatures API extension.
|
2017-08-05 11:40:46 +00:00
|
|
|
|
func (s *dockerImageSource) getSignaturesFromAPIExtension(ctx context.Context) ([][]byte, error) {
|
|
|
|
|
manifestDigest, err := s.manifestDigest(ctx)
|
2017-04-03 07:22:44 +00:00
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-05 11:40:46 +00:00
|
|
|
|
parsedBody, err := s.c.getExtensionsSignatures(ctx, s.ref, manifestDigest)
|
2017-04-03 07:22:44 +00:00
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
2016-09-17 13:50:35 +00:00
|
|
|
|
// deleteImage deletes the named image from the registry, if supported.
|
|
|
|
|
func deleteImage(ctx *types.SystemContext, ref dockerReference) error {
|
2017-08-29 14:54:45 +00:00
|
|
|
|
c, err := newDockerClientFromRef(ctx, ref, true, "push")
|
2016-09-17 13:50:35 +00:00
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2016-07-20 09:46:01 +00:00
|
|
|
|
|
|
|
|
|
// When retrieving the digest from a registry >= 2.3 use the following header:
|
|
|
|
|
// "Accept": "application/vnd.docker.distribution.manifest.v2+json"
|
|
|
|
|
headers := make(map[string][]string)
|
2016-09-17 13:50:35 +00:00
|
|
|
|
headers["Accept"] = []string{manifest.DockerV2Schema2MediaType}
|
2016-07-20 09:46:01 +00:00
|
|
|
|
|
2017-03-13 16:33:17 +00:00
|
|
|
|
refTail, err := ref.tagOrDigest()
|
2016-07-20 09:46:01 +00:00
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2017-04-03 07:22:44 +00:00
|
|
|
|
getPath := fmt.Sprintf(manifestPath, reference.Path(ref.ref), refTail)
|
2017-08-05 11:40:46 +00:00
|
|
|
|
get, err := c.makeRequest(context.TODO(), "GET", getPath, headers, nil)
|
2016-07-20 09:46:01 +00:00
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
defer get.Body.Close()
|
2016-09-17 13:50:35 +00:00
|
|
|
|
manifestBody, err := ioutil.ReadAll(get.Body)
|
2016-07-20 09:46:01 +00:00
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
switch get.StatusCode {
|
|
|
|
|
case http.StatusOK:
|
|
|
|
|
case http.StatusNotFound:
|
2016-10-17 13:53:40 +00:00
|
|
|
|
return errors.Errorf("Unable to delete %v. Image may not exist or is not stored with a v2 Schema in a v2 registry", ref.ref)
|
2016-07-20 09:46:01 +00:00
|
|
|
|
default:
|
2016-10-17 13:53:40 +00:00
|
|
|
|
return errors.Errorf("Failed to delete %v: %s (%v)", ref.ref, manifestBody, get.Status)
|
2016-07-20 09:46:01 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
digest := get.Header.Get("Docker-Content-Digest")
|
2017-04-03 07:22:44 +00:00
|
|
|
|
deletePath := fmt.Sprintf(manifestPath, reference.Path(ref.ref), digest)
|
2016-07-20 09:46:01 +00:00
|
|
|
|
|
|
|
|
|
// When retrieving the digest from a registry >= 2.3 use the following header:
|
|
|
|
|
// "Accept": "application/vnd.docker.distribution.manifest.v2+json"
|
2017-08-05 11:40:46 +00:00
|
|
|
|
delete, err := c.makeRequest(context.TODO(), "DELETE", deletePath, headers, nil)
|
2016-07-20 09:46:01 +00:00
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
defer delete.Body.Close()
|
|
|
|
|
|
2016-09-17 13:50:35 +00:00
|
|
|
|
body, err := ioutil.ReadAll(delete.Body)
|
2016-07-20 09:46:01 +00:00
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if delete.StatusCode != http.StatusAccepted {
|
2017-04-03 07:22:44 +00:00
|
|
|
|
return errors.Errorf("Failed to delete %v: %s (%v)", deletePath, string(body), delete.Status)
|
2016-09-17 13:50:35 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if c.signatureBase != nil {
|
|
|
|
|
manifestDigest, err := manifest.Digest(manifestBody)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for i := 0; ; i++ {
|
|
|
|
|
url := signatureStorageURL(c.signatureBase, manifestDigest, i)
|
|
|
|
|
if url == nil {
|
2016-10-17 13:53:40 +00:00
|
|
|
|
return errors.Errorf("Internal error: signatureStorageURL with non-nil base returned nil")
|
2016-09-17 13:50:35 +00:00
|
|
|
|
}
|
|
|
|
|
missing, err := c.deleteOneSignature(url)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if missing {
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-07-20 09:46:01 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|