From 2226ea615a471a8b2b95abdf259da7802c02074f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arda=20G=C3=BC=C3=A7l=C3=BC?= Date: Mon, 5 Jan 2026 12:30:54 +0300 Subject: [PATCH 1/2] Use new registryclient in library-go --- go.mod | 2 + go.sum | 4 +- pkg/cli/image/imagesource/s3.go | 2 +- .../credential_store_factory.go | 2 +- pkg/cli/image/mirror/mappings.go | 2 +- .../distribution}/client/auth/api_version.go | 0 .../client/auth/challenge/addr.go | 27 + .../client/auth/challenge/authchallenge.go | 237 +++++ .../distribution}/client/auth/session.go | 6 +- .../image/distribution/client/blob_writer.go | 163 +++ .../pkg/image/distribution/client/errors.go | 140 +++ .../image/distribution/client/repository.go | 944 ++++++++++++++++++ .../client/transport/http_reader.go | 253 +++++ .../client/transport/transport.go | 155 +++ .../manifest/schema1/config_builder.go | 303 ++++++ .../distribution/manifest/schema1/doc.go | 5 + .../distribution/manifest/schema1/manifest.go | 226 +++++ .../manifest/schema1/reference_builder.go | 112 +++ .../distribution/manifest/schema1/sign.go | 74 ++ .../distribution/manifest/schema1/verify.go | 38 + .../pkg/image/registryclient/client.go | 10 +- .../image/registryclient/client_mirrored.go | 4 +- .../pkg/image/registryclient/credentials.go | 2 +- vendor/modules.txt | 9 +- 24 files changed, 2702 insertions(+), 18 deletions(-) rename vendor/github.com/{distribution/distribution/v3/registry => openshift/library-go/pkg/image/distribution}/client/auth/api_version.go (100%) create mode 100644 vendor/github.com/openshift/library-go/pkg/image/distribution/client/auth/challenge/addr.go create mode 100644 vendor/github.com/openshift/library-go/pkg/image/distribution/client/auth/challenge/authchallenge.go rename vendor/github.com/{distribution/distribution/v3/registry => openshift/library-go/pkg/image/distribution}/client/auth/session.go (98%) create mode 100644 vendor/github.com/openshift/library-go/pkg/image/distribution/client/blob_writer.go create mode 100644 vendor/github.com/openshift/library-go/pkg/image/distribution/client/errors.go create mode 100644 vendor/github.com/openshift/library-go/pkg/image/distribution/client/repository.go create mode 100644 vendor/github.com/openshift/library-go/pkg/image/distribution/client/transport/http_reader.go create mode 100644 vendor/github.com/openshift/library-go/pkg/image/distribution/client/transport/transport.go create mode 100644 vendor/github.com/openshift/library-go/pkg/image/distribution/manifest/schema1/config_builder.go create mode 100644 vendor/github.com/openshift/library-go/pkg/image/distribution/manifest/schema1/doc.go create mode 100644 vendor/github.com/openshift/library-go/pkg/image/distribution/manifest/schema1/manifest.go create mode 100644 vendor/github.com/openshift/library-go/pkg/image/distribution/manifest/schema1/reference_builder.go create mode 100644 vendor/github.com/openshift/library-go/pkg/image/distribution/manifest/schema1/sign.go create mode 100644 vendor/github.com/openshift/library-go/pkg/image/distribution/manifest/schema1/verify.go diff --git a/go.mod b/go.mod index b7badc213e..d7455ffc6f 100644 --- a/go.mod +++ b/go.mod @@ -221,3 +221,5 @@ require ( ) replace github.com/apcera/gssapi => github.com/openshift/gssapi v0.0.0-20161010215902-5fb4217df13b + +replace github.com/openshift/library-go => github.com/ardaguclu/library-go v0.0.0-20260105092109-3fc6249cdc6d diff --git a/go.sum b/go.sum index ee591bb782..25d048b3ea 100644 --- a/go.sum +++ b/go.sum @@ -29,6 +29,8 @@ github.com/alicebob/sqlittle v1.4.0 h1:vgYt0nAjhdf/hg52MjKJ84g/uTzBPfrvI+VUBrIgh github.com/alicebob/sqlittle v1.4.0/go.mod h1:Co1L1qxHqCwf41puWhk2HOodojR0mcsAV4BIt8byZh8= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= +github.com/ardaguclu/library-go v0.0.0-20260105092109-3fc6249cdc6d h1:jfIVYPHpkT2NeFxlug1K3P/sCLUqJwbQ5lwH0jGNwsc= +github.com/ardaguclu/library-go v0.0.0-20260105092109-3fc6249cdc6d/go.mod h1:+fzLay07SiZuTyZhg0pPICqdcN4jvJWU95XOE9r+2Rg= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= @@ -388,8 +390,6 @@ github.com/openshift/client-go v0.0.0-20251015124057-db0dee36e235 h1:9JBeIXmnHlp github.com/openshift/client-go v0.0.0-20251015124057-db0dee36e235/go.mod h1:L49W6pfrZkfOE5iC1PqEkuLkXG4W0BX4w8b+L2Bv7fM= github.com/openshift/gssapi v0.0.0-20161010215902-5fb4217df13b h1:it0YPE/evO6/m8t8wxis9KFI2F/aleOKsI6d9uz0cEk= github.com/openshift/gssapi v0.0.0-20161010215902-5fb4217df13b/go.mod h1:tNrEB5k8SI+g5kOlsCmL2ELASfpqEofI0+FLBgBdN08= -github.com/openshift/library-go v0.0.0-20251222131241-289839b3ffe8 h1:TWqbSjaYbZGgB6EmnEN6Hc8lQYYCgju2qORBX7Ix1LI= -github.com/openshift/library-go v0.0.0-20251222131241-289839b3ffe8/go.mod h1:nIzWQQE49XbiKizVnVOip9CEB7HJ0hoJwNi3g3YKnKc= github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= diff --git a/pkg/cli/image/imagesource/s3.go b/pkg/cli/image/imagesource/s3.go index ac5fff361d..ee7e78a873 100644 --- a/pkg/cli/image/imagesource/s3.go +++ b/pkg/cli/image/imagesource/s3.go @@ -13,6 +13,7 @@ import ( "sync" "time" + "github.com/openshift/library-go/pkg/image/distribution/client/auth" "k8s.io/klog/v2" "github.com/aws/aws-sdk-go-v2/aws" @@ -24,7 +25,6 @@ import ( "github.com/distribution/distribution/v3" "github.com/distribution/distribution/v3/reference" - "github.com/distribution/distribution/v3/registry/client/auth" godigest "github.com/opencontainers/go-digest" ) diff --git a/pkg/cli/image/manifest/dockercredentials/credential_store_factory.go b/pkg/cli/image/manifest/dockercredentials/credential_store_factory.go index d3789bf3f5..5ccc29909d 100644 --- a/pkg/cli/image/manifest/dockercredentials/credential_store_factory.go +++ b/pkg/cli/image/manifest/dockercredentials/credential_store_factory.go @@ -5,8 +5,8 @@ import ( "sync" "github.com/containers/image/v5/docker/reference" - "github.com/distribution/distribution/v3/registry/client/auth" "github.com/docker/docker/api/types/registry" + "github.com/openshift/library-go/pkg/image/distribution/client/auth" "github.com/openshift/library-go/pkg/image/registryclient" ) diff --git a/pkg/cli/image/mirror/mappings.go b/pkg/cli/image/mirror/mappings.go index 9890a50188..18f42a8fed 100644 --- a/pkg/cli/image/mirror/mappings.go +++ b/pkg/cli/image/mirror/mappings.go @@ -8,8 +8,8 @@ import ( "strings" "sync" - "github.com/distribution/distribution/v3/registry/client/auth" digest "github.com/opencontainers/go-digest" + "github.com/openshift/library-go/pkg/image/distribution/client/auth" "github.com/openshift/oc/pkg/cli/image/imagesource" ) diff --git a/vendor/github.com/distribution/distribution/v3/registry/client/auth/api_version.go b/vendor/github.com/openshift/library-go/pkg/image/distribution/client/auth/api_version.go similarity index 100% rename from vendor/github.com/distribution/distribution/v3/registry/client/auth/api_version.go rename to vendor/github.com/openshift/library-go/pkg/image/distribution/client/auth/api_version.go diff --git a/vendor/github.com/openshift/library-go/pkg/image/distribution/client/auth/challenge/addr.go b/vendor/github.com/openshift/library-go/pkg/image/distribution/client/auth/challenge/addr.go new file mode 100644 index 0000000000..2c3ebe1653 --- /dev/null +++ b/vendor/github.com/openshift/library-go/pkg/image/distribution/client/auth/challenge/addr.go @@ -0,0 +1,27 @@ +package challenge + +import ( + "net/url" + "strings" +) + +// FROM: https://golang.org/src/net/http/http.go +// Given a string of the form "host", "host:port", or "[ipv6::address]:port", +// return true if the string includes a port. +func hasPort(s string) bool { return strings.LastIndex(s, ":") > strings.LastIndex(s, "]") } + +// FROM: http://golang.org/src/net/http/transport.go +var portMap = map[string]string{ + "http": "80", + "https": "443", +} + +// canonicalAddr returns url.Host but always with a ":port" suffix +// FROM: http://golang.org/src/net/http/transport.go +func canonicalAddr(url *url.URL) string { + addr := url.Host + if !hasPort(addr) { + return addr + ":" + portMap[url.Scheme] + } + return addr +} diff --git a/vendor/github.com/openshift/library-go/pkg/image/distribution/client/auth/challenge/authchallenge.go b/vendor/github.com/openshift/library-go/pkg/image/distribution/client/auth/challenge/authchallenge.go new file mode 100644 index 0000000000..3dae9538ea --- /dev/null +++ b/vendor/github.com/openshift/library-go/pkg/image/distribution/client/auth/challenge/authchallenge.go @@ -0,0 +1,237 @@ +package challenge + +import ( + "fmt" + "net/http" + "net/url" + "strings" + "sync" +) + +// Challenge carries information from a WWW-Authenticate response header. +// See RFC 2617. +type Challenge struct { + // Scheme is the auth-scheme according to RFC 2617 + Scheme string + + // Parameters are the auth-params according to RFC 2617 + Parameters map[string]string +} + +// Manager manages the challenges for endpoints. +// The challenges are pulled out of HTTP responses. Only +// responses which expect challenges should be added to +// the manager, since a non-unauthorized request will be +// viewed as not requiring challenges. +type Manager interface { + // GetChallenges returns the challenges for the given + // endpoint URL. + GetChallenges(endpoint url.URL) ([]Challenge, error) + + // AddResponse adds the response to the challenge + // manager. The challenges will be parsed out of + // the WWW-Authenicate headers and added to the + // URL which was produced the response. If the + // response was authorized, any challenges for the + // endpoint will be cleared. + AddResponse(resp *http.Response) error +} + +// NewSimpleManager returns an instance of +// Manager which only maps endpoints to challenges +// based on the responses which have been added the +// manager. The simple manager will make no attempt to +// perform requests on the endpoints or cache the responses +// to a backend. +func NewSimpleManager() Manager { + return &simpleManager{ + Challenges: make(map[string][]Challenge), + } +} + +type simpleManager struct { + sync.RWMutex + Challenges map[string][]Challenge +} + +func normalizeURL(endpoint *url.URL) { + endpoint.Host = strings.ToLower(endpoint.Host) + endpoint.Host = canonicalAddr(endpoint) +} + +func (m *simpleManager) GetChallenges(endpoint url.URL) ([]Challenge, error) { + normalizeURL(&endpoint) + + m.RLock() + defer m.RUnlock() + challenges := m.Challenges[endpoint.String()] + return challenges, nil +} + +func (m *simpleManager) AddResponse(resp *http.Response) error { + challenges := ResponseChallenges(resp) + if resp.Request == nil { + return fmt.Errorf("missing request reference") + } + urlCopy := url.URL{ + Path: resp.Request.URL.Path, + Host: resp.Request.URL.Host, + Scheme: resp.Request.URL.Scheme, + } + normalizeURL(&urlCopy) + + m.Lock() + defer m.Unlock() + m.Challenges[urlCopy.String()] = challenges + return nil +} + +// Octet types from RFC 2616. +type octetType byte + +var octetTypes [256]octetType + +const ( + isToken octetType = 1 << iota + isSpace +) + +func init() { + // OCTET = + // CHAR = + // CTL = + // CR = + // LF = + // SP = + // HT = + // <"> = + // CRLF = CR LF + // LWS = [CRLF] 1*( SP | HT ) + // TEXT = + // separators = "(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\" | <"> + // | "/" | "[" | "]" | "?" | "=" | "{" | "}" | SP | HT + // token = 1* + // qdtext = > + + for c := 0; c < 256; c++ { + var t octetType + isCtl := c <= 31 || c == 127 + isChar := 0 <= c && c <= 127 + isSeparator := strings.ContainsRune(" \t\"(),/:;<=>?@[]\\{}", rune(c)) + if strings.ContainsRune(" \t\r\n", rune(c)) { + t |= isSpace + } + if isChar && !isCtl && !isSeparator { + t |= isToken + } + octetTypes[c] = t + } +} + +// ResponseChallenges returns a list of authorization challenges +// for the given http Response. Challenges are only checked if +// the response status code was a 401. +func ResponseChallenges(resp *http.Response) []Challenge { + if resp.StatusCode == http.StatusUnauthorized { + // Parse the WWW-Authenticate Header and store the challenges + // on this endpoint object. + return parseAuthHeader(resp.Header) + } + + return nil +} + +func parseAuthHeader(header http.Header) []Challenge { + challenges := []Challenge{} + for _, h := range header[http.CanonicalHeaderKey("WWW-Authenticate")] { + v, p := parseValueAndParams(h) + if v != "" { + challenges = append(challenges, Challenge{Scheme: v, Parameters: p}) + } + } + return challenges +} + +func parseValueAndParams(header string) (value string, params map[string]string) { + params = make(map[string]string) + value, s := expectToken(header) + if value == "" { + return + } + value = strings.ToLower(value) + s = "," + skipSpace(s) + for strings.HasPrefix(s, ",") { + var pkey string + pkey, s = expectToken(skipSpace(s[1:])) + if pkey == "" { + return + } + if !strings.HasPrefix(s, "=") { + return + } + var pvalue string + pvalue, s = expectTokenOrQuoted(s[1:]) + if pvalue == "" { + return + } + pkey = strings.ToLower(pkey) + params[pkey] = pvalue + s = skipSpace(s) + } + return +} + +func skipSpace(s string) (rest string) { + i := 0 + for ; i < len(s); i++ { + if octetTypes[s[i]]&isSpace == 0 { + break + } + } + return s[i:] +} + +func expectToken(s string) (token, rest string) { + i := 0 + for ; i < len(s); i++ { + if octetTypes[s[i]]&isToken == 0 { + break + } + } + return s[:i], s[i:] +} + +func expectTokenOrQuoted(s string) (value string, rest string) { + if !strings.HasPrefix(s, "\"") { + return expectToken(s) + } + s = s[1:] + for i := 0; i < len(s); i++ { + switch s[i] { + case '"': + return s[:i], s[i+1:] + case '\\': + p := make([]byte, len(s)-1) + j := copy(p, s[:i]) + escape := true + for i = i + 1; i < len(s); i++ { + b := s[i] + switch { + case escape: + escape = false + p[j] = b + j++ + case b == '\\': + escape = true + case b == '"': + return string(p[:j]), s[i+1:] + default: + p[j] = b + j++ + } + } + return "", "" + } + } + return "", "" +} diff --git a/vendor/github.com/distribution/distribution/v3/registry/client/auth/session.go b/vendor/github.com/openshift/library-go/pkg/image/distribution/client/auth/session.go similarity index 98% rename from vendor/github.com/distribution/distribution/v3/registry/client/auth/session.go rename to vendor/github.com/openshift/library-go/pkg/image/distribution/client/auth/session.go index fe2128316b..2dbeed1f62 100644 --- a/vendor/github.com/distribution/distribution/v3/registry/client/auth/session.go +++ b/vendor/github.com/openshift/library-go/pkg/image/distribution/client/auth/session.go @@ -11,9 +11,9 @@ import ( "sync" "time" - "github.com/distribution/distribution/v3/registry/client" - "github.com/distribution/distribution/v3/registry/client/auth/challenge" - "github.com/distribution/distribution/v3/registry/client/transport" + "github.com/openshift/library-go/pkg/image/distribution/client" + "github.com/openshift/library-go/pkg/image/distribution/client/auth/challenge" + "github.com/openshift/library-go/pkg/image/distribution/client/transport" ) var ( diff --git a/vendor/github.com/openshift/library-go/pkg/image/distribution/client/blob_writer.go b/vendor/github.com/openshift/library-go/pkg/image/distribution/client/blob_writer.go new file mode 100644 index 0000000000..7b467d844a --- /dev/null +++ b/vendor/github.com/openshift/library-go/pkg/image/distribution/client/blob_writer.go @@ -0,0 +1,163 @@ +package client + +import ( + "bytes" + "context" + "fmt" + "io" + "net/http" + "time" + + "github.com/distribution/distribution/v3" +) + +type httpBlobUpload struct { + ctx context.Context + + statter distribution.BlobStatter + client *http.Client + + uuid string + startedAt time.Time + + location string // always the last value of the location header. + offset int64 + closed bool +} + +func (hbu *httpBlobUpload) Reader() (io.ReadCloser, error) { + panic("Not implemented") +} + +func (hbu *httpBlobUpload) handleErrorResponse(resp *http.Response) error { + if resp.StatusCode == http.StatusNotFound { + return distribution.ErrBlobUploadUnknown + } + return HandleErrorResponse(resp) +} + +func (hbu *httpBlobUpload) ReadFrom(r io.Reader) (n int64, err error) { + req, err := http.NewRequestWithContext(hbu.ctx, http.MethodPatch, hbu.location, io.NopCloser(r)) + if err != nil { + return 0, err + } + defer req.Body.Close() + + resp, err := hbu.client.Do(req) + if err != nil { + return 0, err + } + + if !SuccessStatus(resp.StatusCode) { + return 0, hbu.handleErrorResponse(resp) + } + + hbu.uuid = resp.Header.Get("Docker-Upload-UUID") + hbu.location, err = sanitizeLocation(resp.Header.Get("Location"), hbu.location) + if err != nil { + return 0, err + } + rng := resp.Header.Get("Range") + var start, end int64 + if n, err := fmt.Sscanf(rng, "%d-%d", &start, &end); err != nil { + return 0, err + } else if n != 2 || end < start { + return 0, fmt.Errorf("bad range format: %s", rng) + } + + hbu.offset += end - start + 1 + return (end - start + 1), nil +} + +func (hbu *httpBlobUpload) Write(p []byte) (n int, err error) { + req, err := http.NewRequestWithContext(hbu.ctx, http.MethodPatch, hbu.location, bytes.NewReader(p)) + if err != nil { + return 0, err + } + req.Header.Set("Content-Range", fmt.Sprintf("%d-%d", hbu.offset, hbu.offset+int64(len(p)-1))) + req.Header.Set("Content-Length", fmt.Sprintf("%d", len(p))) + req.Header.Set("Content-Type", "application/octet-stream") + + resp, err := hbu.client.Do(req) + if err != nil { + return 0, err + } + + if !SuccessStatus(resp.StatusCode) { + return 0, hbu.handleErrorResponse(resp) + } + + hbu.uuid = resp.Header.Get("Docker-Upload-UUID") + hbu.location, err = sanitizeLocation(resp.Header.Get("Location"), hbu.location) + if err != nil { + return 0, err + } + rng := resp.Header.Get("Range") + var start, end int + if n, err := fmt.Sscanf(rng, "%d-%d", &start, &end); err != nil { + return 0, err + } else if n != 2 || end < start { + return 0, fmt.Errorf("bad range format: %s", rng) + } + + hbu.offset += int64(end - start + 1) + return (end - start + 1), nil +} + +func (hbu *httpBlobUpload) Size() int64 { + return hbu.offset +} + +func (hbu *httpBlobUpload) ID() string { + return hbu.uuid +} + +func (hbu *httpBlobUpload) StartedAt() time.Time { + return hbu.startedAt +} + +func (hbu *httpBlobUpload) Commit(ctx context.Context, desc distribution.Descriptor) (distribution.Descriptor, error) { + // TODO(dmcgowan): Check if already finished, if so just fetch + req, err := http.NewRequestWithContext(hbu.ctx, http.MethodPut, hbu.location, nil) + if err != nil { + return distribution.Descriptor{}, err + } + + values := req.URL.Query() + values.Set("digest", desc.Digest.String()) + req.URL.RawQuery = values.Encode() + + resp, err := hbu.client.Do(req) + if err != nil { + return distribution.Descriptor{}, err + } + defer resp.Body.Close() + + if !SuccessStatus(resp.StatusCode) { + return distribution.Descriptor{}, hbu.handleErrorResponse(resp) + } + + return hbu.statter.Stat(ctx, desc.Digest) +} + +func (hbu *httpBlobUpload) Cancel(ctx context.Context) error { + req, err := http.NewRequestWithContext(hbu.ctx, http.MethodDelete, hbu.location, nil) + if err != nil { + return err + } + resp, err := hbu.client.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode == http.StatusNotFound || SuccessStatus(resp.StatusCode) { + return nil + } + return hbu.handleErrorResponse(resp) +} + +func (hbu *httpBlobUpload) Close() error { + hbu.closed = true + return nil +} diff --git a/vendor/github.com/openshift/library-go/pkg/image/distribution/client/errors.go b/vendor/github.com/openshift/library-go/pkg/image/distribution/client/errors.go new file mode 100644 index 0000000000..06ac479185 --- /dev/null +++ b/vendor/github.com/openshift/library-go/pkg/image/distribution/client/errors.go @@ -0,0 +1,140 @@ +package client + +import ( + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + + "github.com/distribution/distribution/v3/registry/api/errcode" + "github.com/openshift/library-go/pkg/image/distribution/client/auth/challenge" +) + +// ErrNoErrorsInBody is returned when an HTTP response body parses to an empty +// errcode.Errors slice. +var ErrNoErrorsInBody = errors.New("no error details found in HTTP response body") + +// UnexpectedHTTPStatusError is returned when an unexpected HTTP status is +// returned when making a registry api call. +type UnexpectedHTTPStatusError struct { + Status string +} + +func (e *UnexpectedHTTPStatusError) Error() string { + return fmt.Sprintf("received unexpected HTTP status: %s", e.Status) +} + +// UnexpectedHTTPResponseError is returned when an expected HTTP status code +// is returned, but the content was unexpected and failed to be parsed. +type UnexpectedHTTPResponseError struct { + ParseErr error + StatusCode int + Response []byte +} + +func (e *UnexpectedHTTPResponseError) Error() string { + return fmt.Sprintf("error parsing HTTP %d response body: %s: %q", e.StatusCode, e.ParseErr.Error(), string(e.Response)) +} + +func parseHTTPErrorResponse(statusCode int, r io.Reader) error { + var errors errcode.Errors + body, err := io.ReadAll(r) + if err != nil { + return err + } + + // For backward compatibility, handle irregularly formatted + // messages that contain a "details" field. + var detailsErr struct { + Details string `json:"details"` + } + err = json.Unmarshal(body, &detailsErr) + if err == nil && detailsErr.Details != "" { + switch statusCode { + case http.StatusUnauthorized: + return errcode.ErrorCodeUnauthorized.WithMessage(detailsErr.Details) + case http.StatusForbidden: + return errcode.ErrorCodeDenied.WithMessage(detailsErr.Details) + case http.StatusTooManyRequests: + return errcode.ErrorCodeTooManyRequests.WithMessage(detailsErr.Details) + default: + return errcode.ErrorCodeUnknown.WithMessage(detailsErr.Details) + } + } + + if err := json.Unmarshal(body, &errors); err != nil { + return &UnexpectedHTTPResponseError{ + ParseErr: err, + StatusCode: statusCode, + Response: body, + } + } + + if len(errors) == 0 { + // If there was no error specified in the body, return + // UnexpectedHTTPResponseError. + return &UnexpectedHTTPResponseError{ + ParseErr: ErrNoErrorsInBody, + StatusCode: statusCode, + Response: body, + } + } + + return errors +} + +func makeErrorList(err error) []error { + if errL, ok := err.(errcode.Errors); ok { + return []error(errL) + } + return []error{err} +} + +func mergeErrors(err1, err2 error) error { + return errcode.Errors(append(makeErrorList(err1), makeErrorList(err2)...)) +} + +// HandleErrorResponse returns error parsed from HTTP response for an +// unsuccessful HTTP response code (in the range 400 - 499 inclusive). An +// UnexpectedHTTPStatusError returned for response code outside of expected +// range. +func HandleErrorResponse(resp *http.Response) error { + if resp.StatusCode >= 400 && resp.StatusCode < 500 { + // Check for OAuth errors within the `WWW-Authenticate` header first + // See https://tools.ietf.org/html/rfc6750#section-3 + for _, c := range challenge.ResponseChallenges(resp) { + if c.Scheme == "bearer" { + var err errcode.Error + // codes defined at https://tools.ietf.org/html/rfc6750#section-3.1 + switch c.Parameters["error"] { + case "invalid_token": + err.Code = errcode.ErrorCodeUnauthorized + case "insufficient_scope": + err.Code = errcode.ErrorCodeDenied + default: + continue + } + if description := c.Parameters["error_description"]; description != "" { + err.Message = description + } else { + err.Message = err.Code.Message() + } + + return mergeErrors(err, parseHTTPErrorResponse(resp.StatusCode, resp.Body)) + } + } + err := parseHTTPErrorResponse(resp.StatusCode, resp.Body) + if uErr, ok := err.(*UnexpectedHTTPResponseError); ok && resp.StatusCode == 401 { + return errcode.ErrorCodeUnauthorized.WithDetail(uErr.Response) + } + return err + } + return &UnexpectedHTTPStatusError{Status: resp.Status} +} + +// SuccessStatus returns true if the argument is a successful HTTP response +// code (in the range 200 - 399 inclusive). +func SuccessStatus(status int) bool { + return status >= 200 && status <= 399 +} diff --git a/vendor/github.com/openshift/library-go/pkg/image/distribution/client/repository.go b/vendor/github.com/openshift/library-go/pkg/image/distribution/client/repository.go new file mode 100644 index 0000000000..a61ce5e876 --- /dev/null +++ b/vendor/github.com/openshift/library-go/pkg/image/distribution/client/repository.go @@ -0,0 +1,944 @@ +package client + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "path" + "strconv" + "strings" + "time" + + "github.com/distribution/distribution/v3" + "github.com/distribution/distribution/v3/reference" + v2 "github.com/distribution/distribution/v3/registry/api/v2" + "github.com/distribution/distribution/v3/registry/storage/cache" + "github.com/distribution/distribution/v3/registry/storage/cache/memory" + "github.com/opencontainers/go-digest" + "github.com/openshift/library-go/pkg/image/distribution/client/transport" +) + +// Registry provides an interface for calling Repositories, which returns a catalog of repositories. +type Registry interface { + Repositories(ctx context.Context, repos []string, last string) (n int, err error) +} + +// checkHTTPRedirect is a callback that can manipulate redirected HTTP +// requests. It is used to preserve Accept and Range headers. +func checkHTTPRedirect(req *http.Request, via []*http.Request) error { + if len(via) >= 10 { + return errors.New("stopped after 10 redirects") + } + + if len(via) > 0 { + for headerName, headerVals := range via[0].Header { + if headerName != "Accept" && headerName != "Range" { + continue + } + for _, val := range headerVals { + // Don't add to redirected request if redirected + // request already has a header with the same + // name and value. + hasValue := false + for _, existingVal := range req.Header[headerName] { + if existingVal == val { + hasValue = true + break + } + } + if !hasValue { + req.Header.Add(headerName, val) + } + } + } + } + + return nil +} + +// NewRegistry creates a registry namespace which can be used to get a listing of repositories +func NewRegistry(baseURL string, transport http.RoundTripper) (Registry, error) { + ub, err := v2.NewURLBuilderFromString(baseURL, false) + if err != nil { + return nil, err + } + + client := &http.Client{ + Transport: transport, + Timeout: 1 * time.Minute, + CheckRedirect: checkHTTPRedirect, + } + + return ®istry{ + client: client, + ub: ub, + }, nil +} + +type registry struct { + client *http.Client + ub *v2.URLBuilder +} + +// Repositories returns a lexigraphically sorted catalog given a base URL. The 'entries' slice will be filled up to the size +// of the slice, starting at the value provided in 'last'. The number of entries will be returned along with io.EOF if there +// are no more entries +func (r *registry) Repositories(ctx context.Context, entries []string, last string) (int, error) { + var numFilled int + var returnErr error + + values := buildCatalogValues(len(entries), last) + u, err := r.ub.BuildCatalogURL(values) + if err != nil { + return 0, err + } + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, u, nil) + if err != nil { + return 0, err + } + resp, err := r.client.Do(req) + if err != nil { + return 0, err + } + defer resp.Body.Close() + + if SuccessStatus(resp.StatusCode) { + var ctlg struct { + Repositories []string `json:"repositories"` + } + decoder := json.NewDecoder(resp.Body) + + if err := decoder.Decode(&ctlg); err != nil { + return 0, err + } + + copy(entries, ctlg.Repositories) + numFilled = len(ctlg.Repositories) + + link := resp.Header.Get("Link") + if link == "" { + returnErr = io.EOF + } + } else { + return 0, HandleErrorResponse(resp) + } + + return numFilled, returnErr +} + +// NewRepository creates a new Repository for the given repository name and base URL. +func NewRepository(name reference.Named, baseURL string, transport http.RoundTripper) (distribution.Repository, error) { + ub, err := v2.NewURLBuilderFromString(baseURL, false) + if err != nil { + return nil, err + } + + return &repository{ + client: &http.Client{ + Transport: transport, + CheckRedirect: checkHTTPRedirect, + // TODO(dmcgowan): create cookie jar + }, + ub: ub, + name: name, + }, nil +} + +type repository struct { + client *http.Client + ub *v2.URLBuilder + name reference.Named +} + +func (r *repository) Named() reference.Named { + return r.name +} + +func (r *repository) Blobs(ctx context.Context) distribution.BlobStore { + return &blobs{ + name: r.name, + ub: r.ub, + client: r.client, + statter: cache.NewCachedBlobStatter(memory.NewInMemoryBlobDescriptorCacheProvider(memory.UnlimitedSize), &blobStatter{ + name: r.name, + ub: r.ub, + client: r.client, + }), + } +} + +func (r *repository) Manifests(ctx context.Context, options ...distribution.ManifestServiceOption) (distribution.ManifestService, error) { + // todo(richardscothern): options should be sent over the wire + return &manifests{ + name: r.name, + ub: r.ub, + client: r.client, + etags: make(map[string]string), + }, nil +} + +func (r *repository) Tags(ctx context.Context) distribution.TagService { + return &tags{ + client: r.client, + ub: r.ub, + name: r.Named(), + } +} + +// tags implements remote tagging operations. +type tags struct { + client *http.Client + ub *v2.URLBuilder + name reference.Named +} + +// All returns all tags +func (t *tags) All(ctx context.Context) ([]string, error) { + var tags []string + + listURLStr, err := t.ub.BuildTagsURL(t.name) + if err != nil { + return tags, err + } + + listURL, err := url.Parse(listURLStr) + if err != nil { + return tags, err + } + + for { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, listURL.String(), nil) + if err != nil { + return nil, err + } + resp, err := t.client.Do(req) + if err != nil { + return tags, err + } + defer resp.Body.Close() + + if SuccessStatus(resp.StatusCode) { + b, err := io.ReadAll(resp.Body) + if err != nil { + return tags, err + } + + tagsResponse := struct { + Tags []string `json:"tags"` + }{} + if err := json.Unmarshal(b, &tagsResponse); err != nil { + return tags, err + } + tags = append(tags, tagsResponse.Tags...) + if link := resp.Header.Get("Link"); link != "" { + firsLink, _, _ := strings.Cut(link, ";") + linkURL, err := url.Parse(strings.Trim(firsLink, "<>")) + if err != nil { + return tags, err + } + + listURL = listURL.ResolveReference(linkURL) + } else { + return tags, nil + } + } else { + return tags, HandleErrorResponse(resp) + } + } +} + +func descriptorFromResponse(response *http.Response) (distribution.Descriptor, error) { + desc := distribution.Descriptor{} + headers := response.Header + + ctHeader := headers.Get("Content-Type") + if ctHeader == "" { + return distribution.Descriptor{}, errors.New("missing or empty Content-Type header") + } + desc.MediaType = ctHeader + + digestHeader := headers.Get("Docker-Content-Digest") + if digestHeader == "" { + data, err := io.ReadAll(response.Body) + if err != nil { + return distribution.Descriptor{}, err + } + _, desc, err := distribution.UnmarshalManifest(ctHeader, data) + if err != nil { + return distribution.Descriptor{}, err + } + return desc, nil + } + + dgst, err := digest.Parse(digestHeader) + if err != nil { + return distribution.Descriptor{}, err + } + desc.Digest = dgst + + lengthHeader := headers.Get("Content-Length") + if lengthHeader == "" { + return distribution.Descriptor{}, errors.New("missing or empty Content-Length header") + } + length, err := strconv.ParseInt(lengthHeader, 10, 64) + if err != nil { + return distribution.Descriptor{}, err + } + desc.Size = length + + return desc, nil +} + +// Get issues a HEAD request for a Manifest against its named endpoint in order +// to construct a descriptor for the tag. If the registry doesn't support HEADing +// a manifest, fallback to GET. +func (t *tags) Get(ctx context.Context, tag string) (distribution.Descriptor, error) { + ref, err := reference.WithTag(t.name, tag) + if err != nil { + return distribution.Descriptor{}, err + } + u, err := t.ub.BuildManifestURL(ref) + if err != nil { + return distribution.Descriptor{}, err + } + + newRequest := func(method string) (*http.Response, error) { + req, err := http.NewRequestWithContext(ctx, method, u, nil) + if err != nil { + return nil, err + } + + for _, t := range distribution.ManifestMediaTypes() { + req.Header.Add("Accept", t) + } + resp, err := t.client.Do(req) + return resp, err + } + + resp, err := newRequest(http.MethodHead) + if err != nil { + return distribution.Descriptor{}, err + } + defer resp.Body.Close() + + switch { + case resp.StatusCode >= 200 && resp.StatusCode < 400 && len(resp.Header.Get("Docker-Content-Digest")) > 0: + // if the response is a success AND a Docker-Content-Digest can be retrieved from the headers + return descriptorFromResponse(resp) + default: + // if the response is an error - there will be no body to decode. + // Issue a GET request: + // - for data from a server that does not handle HEAD + // - to get error details in case of a failure + resp, err = newRequest(http.MethodGet) + if err != nil { + return distribution.Descriptor{}, err + } + defer resp.Body.Close() + + if resp.StatusCode >= 200 && resp.StatusCode < 400 { + return descriptorFromResponse(resp) + } + return distribution.Descriptor{}, HandleErrorResponse(resp) + } +} + +func (t *tags) Lookup(ctx context.Context, digest distribution.Descriptor) ([]string, error) { + panic("not implemented") +} + +func (t *tags) Tag(ctx context.Context, tag string, desc distribution.Descriptor) error { + panic("not implemented") +} + +func (t *tags) Untag(ctx context.Context, tag string) error { + ref, err := reference.WithTag(t.name, tag) + if err != nil { + return err + } + u, err := t.ub.BuildManifestURL(ref) + if err != nil { + return err + } + + req, err := http.NewRequestWithContext(ctx, http.MethodDelete, u, nil) + if err != nil { + return err + } + + resp, err := t.client.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + if SuccessStatus(resp.StatusCode) { + return nil + } + return HandleErrorResponse(resp) +} + +type manifests struct { + name reference.Named + ub *v2.URLBuilder + client *http.Client + etags map[string]string +} + +func (ms *manifests) Exists(ctx context.Context, dgst digest.Digest) (bool, error) { + ref, err := reference.WithDigest(ms.name, dgst) + if err != nil { + return false, err + } + u, err := ms.ub.BuildManifestURL(ref) + if err != nil { + return false, err + } + + req, err := http.NewRequestWithContext(ctx, http.MethodHead, u, nil) + if err != nil { + return false, err + } + resp, err := ms.client.Do(req) + if err != nil { + return false, err + } + + if SuccessStatus(resp.StatusCode) { + return true, nil + } else if resp.StatusCode == http.StatusNotFound { + return false, nil + } + return false, HandleErrorResponse(resp) +} + +// AddEtagToTag allows a client to supply an eTag to Get which will be +// used for a conditional HTTP request. If the eTag matches, a nil manifest +// and ErrManifestNotModified error will be returned. etag is automatically +// quoted when added to this map. +func AddEtagToTag(tag, etag string) distribution.ManifestServiceOption { + return etagOption{tag, etag} +} + +type etagOption struct{ tag, etag string } + +func (o etagOption) Apply(ms distribution.ManifestService) error { + if ms, ok := ms.(*manifests); ok { + ms.etags[o.tag] = fmt.Sprintf(`"%s"`, o.etag) + return nil + } + return fmt.Errorf("etag options is a client-only option") +} + +// ReturnContentDigest allows a client to set a the content digest on +// a successful request from the 'Docker-Content-Digest' header. This +// returned digest is represents the digest which the registry uses +// to refer to the content and can be used to delete the content. +func ReturnContentDigest(dgst *digest.Digest) distribution.ManifestServiceOption { + return contentDigestOption{dgst} +} + +type contentDigestOption struct{ digest *digest.Digest } + +func (o contentDigestOption) Apply(ms distribution.ManifestService) error { + return nil +} + +func (ms *manifests) Get(ctx context.Context, dgst digest.Digest, options ...distribution.ManifestServiceOption) (distribution.Manifest, error) { + var ( + digestOrTag string + ref reference.Named + err error + contentDgst *digest.Digest + mediaTypes []string + ) + + for _, option := range options { + switch opt := option.(type) { + case distribution.WithTagOption: + digestOrTag = opt.Tag + ref, err = reference.WithTag(ms.name, opt.Tag) + if err != nil { + return nil, err + } + case contentDigestOption: + contentDgst = opt.digest + case distribution.WithManifestMediaTypesOption: + mediaTypes = opt.MediaTypes + default: + err := option.Apply(ms) + if err != nil { + return nil, err + } + } + } + + if digestOrTag == "" { + digestOrTag = dgst.String() + ref, err = reference.WithDigest(ms.name, dgst) + if err != nil { + return nil, err + } + } + + if len(mediaTypes) == 0 { + mediaTypes = distribution.ManifestMediaTypes() + } + + u, err := ms.ub.BuildManifestURL(ref) + if err != nil { + return nil, err + } + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, u, nil) + if err != nil { + return nil, err + } + + for _, t := range mediaTypes { + req.Header.Add("Accept", t) + } + + if _, ok := ms.etags[digestOrTag]; ok { + req.Header.Set("If-None-Match", ms.etags[digestOrTag]) + } + + resp, err := ms.client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + if resp.StatusCode == http.StatusNotModified { + return nil, distribution.ErrManifestNotModified + } else if SuccessStatus(resp.StatusCode) { + if contentDgst != nil { + dgst, err := digest.Parse(resp.Header.Get("Docker-Content-Digest")) + if err == nil { + *contentDgst = dgst + } + } + mt := resp.Header.Get("Content-Type") + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + m, _, err := distribution.UnmarshalManifest(mt, body) + if err != nil { + return nil, err + } + return m, nil + } + return nil, HandleErrorResponse(resp) +} + +// Put puts a manifest. A tag can be specified using an options parameter which uses some shared state to hold the +// tag name in order to build the correct upload URL. +func (ms *manifests) Put(ctx context.Context, m distribution.Manifest, options ...distribution.ManifestServiceOption) (digest.Digest, error) { + ref := ms.name + var tagged bool + + for _, option := range options { + if opt, ok := option.(distribution.WithTagOption); ok { + var err error + ref, err = reference.WithTag(ref, opt.Tag) + if err != nil { + return "", err + } + tagged = true + } else { + err := option.Apply(ms) + if err != nil { + return "", err + } + } + } + mediaType, p, err := m.Payload() + if err != nil { + return "", err + } + + if !tagged { + // generate a canonical digest and Put by digest + _, d, err := distribution.UnmarshalManifest(mediaType, p) + if err != nil { + return "", err + } + ref, err = reference.WithDigest(ref, d.Digest) + if err != nil { + return "", err + } + } + + manifestURL, err := ms.ub.BuildManifestURL(ref) + if err != nil { + return "", err + } + + putRequest, err := http.NewRequestWithContext(ctx, http.MethodPut, manifestURL, bytes.NewReader(p)) + if err != nil { + return "", err + } + + putRequest.Header.Set("Content-Type", mediaType) + + resp, err := ms.client.Do(putRequest) + if err != nil { + return "", err + } + defer resp.Body.Close() + + if SuccessStatus(resp.StatusCode) { + dgstHeader := resp.Header.Get("Docker-Content-Digest") + dgst, err := digest.Parse(dgstHeader) + if err != nil { + return "", err + } + + return dgst, nil + } + + return "", HandleErrorResponse(resp) +} + +func (ms *manifests) Delete(ctx context.Context, dgst digest.Digest) error { + ref, err := reference.WithDigest(ms.name, dgst) + if err != nil { + return err + } + u, err := ms.ub.BuildManifestURL(ref) + if err != nil { + return err + } + req, err := http.NewRequestWithContext(ctx, http.MethodDelete, u, nil) + if err != nil { + return err + } + + resp, err := ms.client.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + if SuccessStatus(resp.StatusCode) { + return nil + } + return HandleErrorResponse(resp) +} + +// todo(richardscothern): Restore interface and implementation with merge of #1050 +/*func (ms *manifests) Enumerate(ctx context.Context, manifests []distribution.Manifest, last distribution.Manifest) (n int, err error) { + panic("not supported") +}*/ + +type blobs struct { + name reference.Named + ub *v2.URLBuilder + client *http.Client + + statter distribution.BlobDescriptorService + distribution.BlobDeleter +} + +func sanitizeLocation(location, base string) (string, error) { + baseURL, err := url.Parse(base) + if err != nil { + return "", err + } + + locationURL, err := url.Parse(location) + if err != nil { + return "", err + } + + return baseURL.ResolveReference(locationURL).String(), nil +} + +func (bs *blobs) Stat(ctx context.Context, dgst digest.Digest) (distribution.Descriptor, error) { + return bs.statter.Stat(ctx, dgst) +} + +func (bs *blobs) Get(ctx context.Context, dgst digest.Digest) ([]byte, error) { + reader, err := bs.Open(ctx, dgst) + if err != nil { + return nil, err + } + defer reader.Close() + + return io.ReadAll(reader) +} + +func (bs *blobs) Open(ctx context.Context, dgst digest.Digest) (io.ReadSeekCloser, error) { + ref, err := reference.WithDigest(bs.name, dgst) + if err != nil { + return nil, err + } + blobURL, err := bs.ub.BuildBlobURL(ref) + if err != nil { + return nil, err + } + + return transport.NewHTTPReadSeeker(ctx, bs.client, blobURL, func(resp *http.Response) error { + if resp.StatusCode == http.StatusNotFound { + return distribution.ErrBlobUnknown + } + return HandleErrorResponse(resp) + }), nil +} + +func (bs *blobs) ServeBlob(ctx context.Context, w http.ResponseWriter, r *http.Request, dgst digest.Digest) error { + desc, err := bs.statter.Stat(ctx, dgst) + if err != nil { + return err + } + + w.Header().Set("Content-Length", strconv.FormatInt(desc.Size, 10)) + w.Header().Set("Content-Type", desc.MediaType) + w.Header().Set("Docker-Content-Digest", dgst.String()) + w.Header().Set("Etag", dgst.String()) + + if r.Method == http.MethodHead { + return nil + } + + blob, err := bs.Open(ctx, dgst) + if err != nil { + return err + } + defer blob.Close() + + _, err = io.CopyN(w, blob, desc.Size) + return err +} + +func (bs *blobs) Put(ctx context.Context, mediaType string, p []byte) (distribution.Descriptor, error) { + writer, err := bs.Create(ctx) + if err != nil { + return distribution.Descriptor{}, err + } + dgstr := digest.Canonical.Digester() + n, err := io.Copy(writer, io.TeeReader(bytes.NewReader(p), dgstr.Hash())) + if err != nil { + return distribution.Descriptor{}, err + } + if n < int64(len(p)) { + return distribution.Descriptor{}, fmt.Errorf("short copy: wrote %d of %d", n, len(p)) + } + + desc := distribution.Descriptor{ + MediaType: mediaType, + Size: int64(len(p)), + Digest: dgstr.Digest(), + } + + return writer.Commit(ctx, desc) +} + +type optionFunc func(interface{}) error + +func (f optionFunc) Apply(v interface{}) error { + return f(v) +} + +// WithMountFrom returns a BlobCreateOption which designates that the blob should be +// mounted from the given canonical reference. +func WithMountFrom(ref reference.Canonical) distribution.BlobCreateOption { + return optionFunc(func(v interface{}) error { + opts, ok := v.(*distribution.CreateOptions) + if !ok { + return fmt.Errorf("unexpected options type: %T", v) + } + + opts.Mount.ShouldMount = true + opts.Mount.From = ref + + return nil + }) +} + +func (bs *blobs) Create(ctx context.Context, options ...distribution.BlobCreateOption) (distribution.BlobWriter, error) { + var opts distribution.CreateOptions + + for _, option := range options { + err := option.Apply(&opts) + if err != nil { + return nil, err + } + } + + var values []url.Values + + if opts.Mount.ShouldMount { + values = append(values, url.Values{"from": {opts.Mount.From.Name()}, "mount": {opts.Mount.From.Digest().String()}}) + } + + u, err := bs.ub.BuildBlobUploadURL(bs.name, values...) + if err != nil { + return nil, err + } + + req, err := http.NewRequestWithContext(ctx, http.MethodPost, u, nil) + if err != nil { + return nil, err + } + + resp, err := bs.client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + switch resp.StatusCode { + case http.StatusCreated: + desc, err := bs.statter.Stat(ctx, opts.Mount.From.Digest()) + if err != nil { + return nil, err + } + return nil, distribution.ErrBlobMounted{From: opts.Mount.From, Descriptor: desc} + case http.StatusAccepted: + // TODO(dmcgowan): Check for invalid UUID + uuid := resp.Header.Get("Docker-Upload-UUID") + if uuid == "" { + // uuid is expected to be the last path element + _, uuid = path.Split(resp.Header.Get("Location")) + } + if uuid == "" { + return nil, errors.New("cannot retrieve docker upload UUID") + } + + location, err := sanitizeLocation(resp.Header.Get("Location"), u) + if err != nil { + return nil, err + } + + return &httpBlobUpload{ + ctx: ctx, + statter: bs.statter, + client: bs.client, + uuid: uuid, + startedAt: time.Now(), + location: location, + }, nil + default: + return nil, HandleErrorResponse(resp) + } +} + +func (bs *blobs) Resume(ctx context.Context, id string) (distribution.BlobWriter, error) { + location, err := bs.ub.BuildBlobUploadChunkURL(bs.name, id) + if err != nil { + return nil, err + } + + return &httpBlobUpload{ + ctx: ctx, + statter: bs.statter, + client: bs.client, + uuid: id, + startedAt: time.Now(), + location: location, + }, nil +} + +func (bs *blobs) Delete(ctx context.Context, dgst digest.Digest) error { + return bs.statter.Clear(ctx, dgst) +} + +type blobStatter struct { + name reference.Named + ub *v2.URLBuilder + client *http.Client +} + +func (bs *blobStatter) Stat(ctx context.Context, dgst digest.Digest) (distribution.Descriptor, error) { + ref, err := reference.WithDigest(bs.name, dgst) + if err != nil { + return distribution.Descriptor{}, err + } + u, err := bs.ub.BuildBlobURL(ref) + if err != nil { + return distribution.Descriptor{}, err + } + + req, err := http.NewRequestWithContext(ctx, http.MethodHead, u, nil) + if err != nil { + return distribution.Descriptor{}, err + } + resp, err := bs.client.Do(req) + if err != nil { + return distribution.Descriptor{}, err + } + defer resp.Body.Close() + + if SuccessStatus(resp.StatusCode) { + lengthHeader := resp.Header.Get("Content-Length") + if lengthHeader == "" { + return distribution.Descriptor{}, fmt.Errorf("missing content-length header for request: %s", u) + } + + length, err := strconv.ParseInt(lengthHeader, 10, 64) + if err != nil { + return distribution.Descriptor{}, fmt.Errorf("error parsing content-length: %v", err) + } + + return distribution.Descriptor{ + MediaType: resp.Header.Get("Content-Type"), + Size: length, + Digest: dgst, + }, nil + } else if resp.StatusCode == http.StatusNotFound { + return distribution.Descriptor{}, distribution.ErrBlobUnknown + } + return distribution.Descriptor{}, HandleErrorResponse(resp) +} + +func buildCatalogValues(maxEntries int, last string) url.Values { + values := url.Values{} + + if maxEntries > 0 { + values.Add("n", strconv.Itoa(maxEntries)) + } + + if last != "" { + values.Add("last", last) + } + + return values +} + +func (bs *blobStatter) Clear(ctx context.Context, dgst digest.Digest) error { + ref, err := reference.WithDigest(bs.name, dgst) + if err != nil { + return err + } + blobURL, err := bs.ub.BuildBlobURL(ref) + if err != nil { + return err + } + + req, err := http.NewRequestWithContext(ctx, http.MethodDelete, blobURL, nil) + if err != nil { + return err + } + + resp, err := bs.client.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + if SuccessStatus(resp.StatusCode) { + return nil + } + return HandleErrorResponse(resp) +} + +func (bs *blobStatter) SetDescriptor(ctx context.Context, dgst digest.Digest, desc distribution.Descriptor) error { + return nil +} diff --git a/vendor/github.com/openshift/library-go/pkg/image/distribution/client/transport/http_reader.go b/vendor/github.com/openshift/library-go/pkg/image/distribution/client/transport/http_reader.go new file mode 100644 index 0000000000..71671158ca --- /dev/null +++ b/vendor/github.com/openshift/library-go/pkg/image/distribution/client/transport/http_reader.go @@ -0,0 +1,253 @@ +package transport + +import ( + "context" + "errors" + "fmt" + "io" + "net/http" + "regexp" + "strconv" +) + +var ( + contentRangeRegexp = regexp.MustCompile(`bytes ([0-9]+)-([0-9]+)/([0-9]+|\\*)`) + + // ErrWrongCodeForByteRange is returned if the client sends a request + // with a Range header but the server returns a 2xx or 3xx code other + // than 206 Partial Content. + ErrWrongCodeForByteRange = errors.New("expected HTTP 206 from byte range request") +) + +// ReadSeekCloser combines io.ReadSeeker with io.Closer. +// +// Deprecated: use [io.ReadSeekCloser]. +type ReadSeekCloser = io.ReadSeekCloser + +// NewHTTPReadSeeker handles reading from an HTTP endpoint using a GET +// request. When seeking and starting a read from a non-zero offset +// the a "Range" header will be added which sets the offset. +// +// TODO(dmcgowan): Move this into a separate utility package +func NewHTTPReadSeeker(ctx context.Context, client *http.Client, url string, errorHandler func(*http.Response) error) *HTTPReadSeeker { + return &HTTPReadSeeker{ + ctx: ctx, + client: client, + url: url, + errorHandler: errorHandler, + } +} + +// HTTPReadSeeker implements an [io.ReadSeekCloser]. +type HTTPReadSeeker struct { + ctx context.Context + client *http.Client + url string + + // errorHandler creates an error from an unsuccessful HTTP response. + // This allows the error to be created with the HTTP response body + // without leaking the body through a returned error. + errorHandler func(*http.Response) error + + size int64 + + // rc is the remote read closer. + rc io.ReadCloser + // readerOffset tracks the offset as of the last read. + readerOffset int64 + // seekOffset allows Seek to override the offset. Seek changes + // seekOffset instead of changing readOffset directly so that + // connection resets can be delayed and possibly avoided if the + // seek is undone (i.e. seeking to the end and then back to the + // beginning). + seekOffset int64 + err error +} + +func (hrs *HTTPReadSeeker) Read(p []byte) (n int, err error) { + if hrs.err != nil { + return 0, hrs.err + } + + // If we sought to a different position, we need to reset the + // connection. This logic is here instead of Seek so that if + // a seek is undone before the next read, the connection doesn't + // need to be closed and reopened. A common example of this is + // seeking to the end to determine the length, and then seeking + // back to the original position. + if hrs.readerOffset != hrs.seekOffset { + hrs.reset() + } + + hrs.readerOffset = hrs.seekOffset + + rd, err := hrs.reader() + if err != nil { + return 0, err + } + + n, err = rd.Read(p) + hrs.seekOffset += int64(n) + hrs.readerOffset += int64(n) + + return n, err +} + +func (hrs *HTTPReadSeeker) Seek(offset int64, whence int) (int64, error) { + if hrs.err != nil { + return 0, hrs.err + } + + lastReaderOffset := hrs.readerOffset + + if whence == io.SeekStart && hrs.rc == nil { + // If no request has been made yet, and we are seeking to an + // absolute position, set the read offset as well to avoid an + // unnecessary request. + hrs.readerOffset = offset + } + + _, err := hrs.reader() + if err != nil { + hrs.readerOffset = lastReaderOffset + return 0, err + } + + newOffset := hrs.seekOffset + + switch whence { + case io.SeekCurrent: + newOffset += offset + case io.SeekEnd: + if hrs.size < 0 { + return 0, errors.New("content length not known") + } + newOffset = hrs.size + offset + case io.SeekStart: + newOffset = offset + } + + if newOffset < 0 { + err = errors.New("cannot seek to negative position") + } else { + hrs.seekOffset = newOffset + } + + return hrs.seekOffset, err +} + +func (hrs *HTTPReadSeeker) Close() error { + if hrs.err != nil { + return hrs.err + } + + // close and release reader chain + if hrs.rc != nil { + hrs.rc.Close() + } + + hrs.rc = nil + + hrs.err = errors.New("httpLayer: closed") + + return nil +} + +func (hrs *HTTPReadSeeker) reset() { + if hrs.err != nil { + return + } + if hrs.rc != nil { + hrs.rc.Close() + hrs.rc = nil + } +} + +func (hrs *HTTPReadSeeker) reader() (io.Reader, error) { + if hrs.err != nil { + return nil, hrs.err + } + + if hrs.rc != nil { + return hrs.rc, nil + } + + req, err := http.NewRequestWithContext(hrs.ctx, http.MethodGet, hrs.url, nil) + if err != nil { + return nil, err + } + + if hrs.readerOffset > 0 { + // If we are at different offset, issue a range request from there. + req.Header.Add("Range", fmt.Sprintf("bytes=%d-", hrs.readerOffset)) + // TODO: get context in here + // context.GetLogger(hrs.context).Infof("Range: %s", req.Header.Get("Range")) + } + + resp, err := hrs.client.Do(req) + if err != nil { + return nil, err + } + + // Normally would use client.SuccessStatus, but that would be a cyclic + // import + if resp.StatusCode >= 200 && resp.StatusCode <= 399 { + if hrs.readerOffset > 0 { + if resp.StatusCode != http.StatusPartialContent { + return nil, ErrWrongCodeForByteRange + } + + contentRange := resp.Header.Get("Content-Range") + if contentRange == "" { + return nil, errors.New("no Content-Range header found in HTTP 206 response") + } + + submatches := contentRangeRegexp.FindStringSubmatch(contentRange) + if len(submatches) < 4 { + return nil, fmt.Errorf("could not parse Content-Range header: %s", contentRange) + } + + startByte, err := strconv.ParseUint(submatches[1], 10, 64) + if err != nil { + return nil, fmt.Errorf("could not parse start of range in Content-Range header: %s", contentRange) + } + + if startByte != uint64(hrs.readerOffset) { + return nil, fmt.Errorf("received Content-Range starting at offset %d instead of requested %d", startByte, hrs.readerOffset) + } + + endByte, err := strconv.ParseUint(submatches[2], 10, 64) + if err != nil { + return nil, fmt.Errorf("could not parse end of range in Content-Range header: %s", contentRange) + } + + if submatches[3] == "*" { + hrs.size = -1 + } else { + size, err := strconv.ParseUint(submatches[3], 10, 64) + if err != nil { + return nil, fmt.Errorf("could not parse total size in Content-Range header: %s", contentRange) + } + + if endByte+1 != size { + return nil, fmt.Errorf("range in Content-Range stops before the end of the content: %s", contentRange) + } + + hrs.size = int64(size) + } + } else if resp.StatusCode == http.StatusOK { + hrs.size = resp.ContentLength + } else { + hrs.size = -1 + } + hrs.rc = resp.Body + } else { + defer resp.Body.Close() + if hrs.errorHandler != nil { + return nil, hrs.errorHandler(resp) + } + return nil, fmt.Errorf("unexpected status resolving reader: %v", resp.Status) + } + + return hrs.rc, nil +} diff --git a/vendor/github.com/openshift/library-go/pkg/image/distribution/client/transport/transport.go b/vendor/github.com/openshift/library-go/pkg/image/distribution/client/transport/transport.go new file mode 100644 index 0000000000..ee23829f3a --- /dev/null +++ b/vendor/github.com/openshift/library-go/pkg/image/distribution/client/transport/transport.go @@ -0,0 +1,155 @@ +package transport + +import ( + "io" + "net/http" + "sync" +) + +func identityTransportWrapper(rt http.RoundTripper) http.RoundTripper { + return rt +} + +// DefaultTransportWrapper allows a user to wrap every generated transport +var DefaultTransportWrapper = identityTransportWrapper + +// RequestModifier represents an object which will do an inplace +// modification of an HTTP request. +type RequestModifier interface { + ModifyRequest(*http.Request) error +} + +type headerModifier http.Header + +// NewHeaderRequestModifier returns a new RequestModifier which will +// add the given headers to a request. +func NewHeaderRequestModifier(header http.Header) RequestModifier { + return headerModifier(header) +} + +func (h headerModifier) ModifyRequest(req *http.Request) error { + for k, s := range http.Header(h) { + req.Header[k] = append(req.Header[k], s...) + } + + return nil +} + +// NewTransport creates a new transport which will apply modifiers to +// the request on a RoundTrip call. +func NewTransport(base http.RoundTripper, modifiers ...RequestModifier) http.RoundTripper { + return DefaultTransportWrapper( + &transport{ + Modifiers: modifiers, + Base: base, + }) +} + +// transport is an http.RoundTripper that makes HTTP requests after +// copying and modifying the request +type transport struct { + Modifiers []RequestModifier + Base http.RoundTripper + + mu sync.Mutex // guards modReq + modReq map[*http.Request]*http.Request // original -> modified +} + +// RoundTrip authorizes and authenticates the request with an +// access token. If no token exists or token is expired, +// tries to refresh/fetch a new token. +func (t *transport) RoundTrip(req *http.Request) (*http.Response, error) { + req2 := cloneRequest(req) + for _, modifier := range t.Modifiers { + if err := modifier.ModifyRequest(req2); err != nil { + return nil, err + } + } + + t.setModReq(req, req2) + res, err := t.base().RoundTrip(req2) + if err != nil { + t.setModReq(req, nil) + return nil, err + } + res.Body = &onEOFReader{ + rc: res.Body, + fn: func() { t.setModReq(req, nil) }, + } + return res, nil +} + +// CancelRequest cancels an in-flight request by closing its connection. +func (t *transport) CancelRequest(req *http.Request) { + type canceler interface { + CancelRequest(*http.Request) + } + if cr, ok := t.base().(canceler); ok { + t.mu.Lock() + modReq := t.modReq[req] + delete(t.modReq, req) + t.mu.Unlock() + cr.CancelRequest(modReq) + } +} + +func (t *transport) base() http.RoundTripper { + if t.Base != nil { + return t.Base + } + return http.DefaultTransport +} + +func (t *transport) setModReq(orig, mod *http.Request) { + t.mu.Lock() + defer t.mu.Unlock() + if t.modReq == nil { + t.modReq = make(map[*http.Request]*http.Request) + } + if mod == nil { + delete(t.modReq, orig) + } else { + t.modReq[orig] = mod + } +} + +// cloneRequest returns a clone of the provided *http.Request. +// The clone is a shallow copy of the struct and its Header map. +func cloneRequest(r *http.Request) *http.Request { + // shallow copy of the struct + r2 := new(http.Request) + *r2 = *r + // deep copy of the Header + r2.Header = make(http.Header, len(r.Header)) + for k, s := range r.Header { + r2.Header[k] = append([]string(nil), s...) + } + + return r2 +} + +type onEOFReader struct { + rc io.ReadCloser + fn func() +} + +func (r *onEOFReader) Read(p []byte) (n int, err error) { + n, err = r.rc.Read(p) + if err == io.EOF { + r.runFunc() + } + return +} + +func (r *onEOFReader) Close() error { + err := r.rc.Close() + r.runFunc() + return err +} + +func (r *onEOFReader) runFunc() { + if fn := r.fn; fn != nil { + fn() + r.fn = nil + } +} diff --git a/vendor/github.com/openshift/library-go/pkg/image/distribution/manifest/schema1/config_builder.go b/vendor/github.com/openshift/library-go/pkg/image/distribution/manifest/schema1/config_builder.go new file mode 100644 index 0000000000..77fdfee996 --- /dev/null +++ b/vendor/github.com/openshift/library-go/pkg/image/distribution/manifest/schema1/config_builder.go @@ -0,0 +1,303 @@ +package schema1 + +import ( + "context" + "crypto/sha512" + "encoding/json" + "errors" + "fmt" + "time" + + "github.com/distribution/distribution/v3" + "github.com/distribution/distribution/v3/manifest" + "github.com/distribution/distribution/v3/reference" + "github.com/docker/libtrust" + "github.com/opencontainers/go-digest" +) + +type diffID digest.Digest + +// gzippedEmptyTar is a gzip-compressed version of an empty tar file +// (1024 NULL bytes) +var gzippedEmptyTar = []byte{ + 31, 139, 8, 0, 0, 9, 110, 136, 0, 255, 98, 24, 5, 163, 96, 20, 140, 88, + 0, 8, 0, 0, 255, 255, 46, 175, 181, 239, 0, 4, 0, 0, +} + +// digestSHA256GzippedEmptyTar is the canonical sha256 digest of +// gzippedEmptyTar +const digestSHA256GzippedEmptyTar = digest.Digest("sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4") + +// configManifestBuilder is a type for constructing manifests from an image +// configuration and generic descriptors. +type configManifestBuilder struct { + // bs is a BlobService used to create empty layer tars in the + // blob store if necessary. + bs distribution.BlobService + // pk is the libtrust private key used to sign the final manifest. + pk libtrust.PrivateKey + // configJSON is configuration supplied when the ManifestBuilder was + // created. + configJSON []byte + // ref contains the name and optional tag provided to NewConfigManifestBuilder. + ref reference.Named + // descriptors is the set of descriptors referencing the layers. + descriptors []distribution.Descriptor + // emptyTarDigest is set to a valid digest if an empty tar has been + // put in the blob store; otherwise it is empty. + emptyTarDigest digest.Digest +} + +// NewConfigManifestBuilder is used to build new manifests for the current +// schema version from an image configuration and a set of descriptors. +// It takes a BlobService so that it can add an empty tar to the blob store +// if the resulting manifest needs empty layers. +// +// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015, +// use Docker Image Manifest v2, Schema 2, or the OCI Image Specification v1. +// This package should not be used for purposes other than backward compatibility. +func NewConfigManifestBuilder(bs distribution.BlobService, pk libtrust.PrivateKey, ref reference.Named, configJSON []byte) distribution.ManifestBuilder { + return &configManifestBuilder{ + bs: bs, + pk: pk, + configJSON: configJSON, + ref: ref, + } +} + +// Build produces a final manifest from the given references. +// +// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015. +// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification. +func (mb *configManifestBuilder) Build(ctx context.Context) (m distribution.Manifest, err error) { + type imageRootFS struct { + Type string `json:"type"` + DiffIDs []diffID `json:"diff_ids,omitempty"` + BaseLayer string `json:"base_layer,omitempty"` + } + + type imageHistory struct { + Created time.Time `json:"created"` + Author string `json:"author,omitempty"` + CreatedBy string `json:"created_by,omitempty"` + Comment string `json:"comment,omitempty"` + EmptyLayer bool `json:"empty_layer,omitempty"` + } + + type imageConfig struct { + RootFS *imageRootFS `json:"rootfs,omitempty"` + History []imageHistory `json:"history,omitempty"` + Architecture string `json:"architecture,omitempty"` + } + + var img imageConfig + + if err := json.Unmarshal(mb.configJSON, &img); err != nil { + return nil, err + } + + if len(img.History) == 0 { + return nil, errors.New("empty history when trying to create schema1 manifest") + } + + if len(img.RootFS.DiffIDs) != len(mb.descriptors) { + return nil, fmt.Errorf("number of descriptors and number of layers in rootfs must match: len(%v) != len(%v)", img.RootFS.DiffIDs, mb.descriptors) + } + + // Generate IDs for each layer + // For non-top-level layers, create fake V1Compatibility strings that + // fit the format and don't collide with anything else, but don't + // result in runnable images on their own. + type v1Compatibility struct { + ID string `json:"id"` + Parent string `json:"parent,omitempty"` + Comment string `json:"comment,omitempty"` + Created time.Time `json:"created"` + ContainerConfig struct { + Cmd []string + } `json:"container_config,omitempty"` + Author string `json:"author,omitempty"` + ThrowAway bool `json:"throwaway,omitempty"` + } + + fsLayerList := make([]FSLayer, len(img.History)) + history := make([]History, len(img.History)) + + parent := "" + layerCounter := 0 + for i, h := range img.History[:len(img.History)-1] { + var blobsum digest.Digest + if h.EmptyLayer { + if blobsum, err = mb.emptyTar(ctx); err != nil { + return nil, err + } + } else { + if len(img.RootFS.DiffIDs) <= layerCounter { + return nil, errors.New("too many non-empty layers in History section") + } + blobsum = mb.descriptors[layerCounter].Digest + layerCounter++ + } + + v1ID := digest.FromBytes([]byte(blobsum.Encoded() + " " + parent)).Encoded() + + if i == 0 && img.RootFS.BaseLayer != "" { + // windows-only baselayer setup + baseID := sha512.Sum384([]byte(img.RootFS.BaseLayer)) + parent = fmt.Sprintf("%x", baseID[:32]) + } + + v1Compat := v1Compatibility{ + ID: v1ID, + Parent: parent, + Comment: h.Comment, + Created: h.Created, + Author: h.Author, + } + v1Compat.ContainerConfig.Cmd = []string{img.History[i].CreatedBy} + if h.EmptyLayer { + v1Compat.ThrowAway = true + } + jsonBytes, err := json.Marshal(&v1Compat) + if err != nil { + return nil, err + } + + reversedIndex := len(img.History) - i - 1 + history[reversedIndex].V1Compatibility = string(jsonBytes) + fsLayerList[reversedIndex] = FSLayer{BlobSum: blobsum} + + parent = v1ID + } + + latestHistory := img.History[len(img.History)-1] + + var blobsum digest.Digest + if latestHistory.EmptyLayer { + if blobsum, err = mb.emptyTar(ctx); err != nil { + return nil, err + } + } else { + if len(img.RootFS.DiffIDs) <= layerCounter { + return nil, errors.New("too many non-empty layers in History section") + } + blobsum = mb.descriptors[layerCounter].Digest + } + + fsLayerList[0] = FSLayer{BlobSum: blobsum} + dgst := digest.FromBytes([]byte(blobsum.Encoded() + " " + parent + " " + string(mb.configJSON))) + + // Top-level v1compatibility string should be a modified version of the + // image config. + transformedConfig, err := MakeV1ConfigFromConfig(mb.configJSON, dgst.Encoded(), parent, latestHistory.EmptyLayer) + if err != nil { + return nil, err + } + + history[0].V1Compatibility = string(transformedConfig) + + tag := "" + if tagged, isTagged := mb.ref.(reference.Tagged); isTagged { + tag = tagged.Tag() + } + + mfst := Manifest{ + Versioned: manifest.Versioned{ + SchemaVersion: 1, + }, + Name: mb.ref.Name(), + Tag: tag, + Architecture: img.Architecture, + FSLayers: fsLayerList, + History: history, + } + + return Sign(&mfst, mb.pk) +} + +// emptyTar pushes a compressed empty tar to the blob store if one doesn't +// already exist, and returns its blobsum. +func (mb *configManifestBuilder) emptyTar(ctx context.Context) (digest.Digest, error) { + if mb.emptyTarDigest != "" { + // Already put an empty tar + return mb.emptyTarDigest, nil + } + + descriptor, err := mb.bs.Stat(ctx, digestSHA256GzippedEmptyTar) + switch err { + case nil: + mb.emptyTarDigest = descriptor.Digest + return descriptor.Digest, nil + case distribution.ErrBlobUnknown: + // nop + default: + return "", err + } + + // Add gzipped empty tar to the blob store + descriptor, err = mb.bs.Put(ctx, "", gzippedEmptyTar) + if err != nil { + return "", err + } + + mb.emptyTarDigest = descriptor.Digest + + return descriptor.Digest, nil +} + +// AppendReference adds a reference to the current ManifestBuilder. +// +// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015. +// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification. +func (mb *configManifestBuilder) AppendReference(d distribution.Describable) error { + descriptor := d.Descriptor() + + if err := descriptor.Digest.Validate(); err != nil { + return err + } + + mb.descriptors = append(mb.descriptors, descriptor) + return nil +} + +// References returns the current references added to this builder. +// +// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015. +// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification. +func (mb *configManifestBuilder) References() []distribution.Descriptor { + return mb.descriptors +} + +// MakeV1ConfigFromConfig creates an legacy V1 image config from image config JSON. +// +// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015. +// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification. +func MakeV1ConfigFromConfig(configJSON []byte, v1ID, parentV1ID string, throwaway bool) ([]byte, error) { + // Top-level v1compatibility string should be a modified version of the + // image config. + var configAsMap map[string]*json.RawMessage + if err := json.Unmarshal(configJSON, &configAsMap); err != nil { + return nil, err + } + + // Delete fields that didn't exist in old manifest + delete(configAsMap, "rootfs") + delete(configAsMap, "history") + configAsMap["id"] = rawJSON(v1ID) + if parentV1ID != "" { + configAsMap["parent"] = rawJSON(parentV1ID) + } + if throwaway { + configAsMap["throwaway"] = rawJSON(true) + } + + return json.Marshal(configAsMap) +} + +func rawJSON(value interface{}) *json.RawMessage { + jsonval, err := json.Marshal(value) + if err != nil { + return nil + } + return (*json.RawMessage)(&jsonval) +} diff --git a/vendor/github.com/openshift/library-go/pkg/image/distribution/manifest/schema1/doc.go b/vendor/github.com/openshift/library-go/pkg/image/distribution/manifest/schema1/doc.go new file mode 100644 index 0000000000..7e912de607 --- /dev/null +++ b/vendor/github.com/openshift/library-go/pkg/image/distribution/manifest/schema1/doc.go @@ -0,0 +1,5 @@ +// Package schema1 is a copy from "github.com/distribution/distribution/v3/manifest/schema1" +// This manifest type has been directly removed from distribution/distribution repository. +// However, we still observe some old images that are in this manifest type. At some point, it should be safe +// to entirely remove this schema from here as well. +package schema1 diff --git a/vendor/github.com/openshift/library-go/pkg/image/distribution/manifest/schema1/manifest.go b/vendor/github.com/openshift/library-go/pkg/image/distribution/manifest/schema1/manifest.go new file mode 100644 index 0000000000..4faee15d80 --- /dev/null +++ b/vendor/github.com/openshift/library-go/pkg/image/distribution/manifest/schema1/manifest.go @@ -0,0 +1,226 @@ +// Package schema1 provides definitions for the deprecated Docker Image +// Manifest v2, Schema 1 specification. +// +// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015. +// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification. This package should not be used for purposes +// other than backward compatibility. +package schema1 + +import ( + "encoding/json" + "fmt" + + "github.com/distribution/distribution/v3" + "github.com/distribution/distribution/v3/manifest" + "github.com/docker/libtrust" + "github.com/opencontainers/go-digest" +) + +// MediaTypeManifest specifies the mediaType for the current version. Note +// that for schema version 1, the the media is optionally "application/json". +// +// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015. +// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification. +const MediaTypeManifest = "application/vnd.docker.distribution.manifest.v1+json" + +// MediaTypeSignedManifest specifies the mediatype for current SignedManifest version +// +// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015. +// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification. +const MediaTypeSignedManifest = "application/vnd.docker.distribution.manifest.v1+prettyjws" + +// MediaTypeManifestLayer specifies the media type for manifest layers +// +// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015. +// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification. +const MediaTypeManifestLayer = "application/vnd.docker.container.image.rootfs.diff+x-gtar" + +// SchemaVersion provides a pre-initialized version structure for this +// packages version of the manifest. +// +// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015. +// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification. +var SchemaVersion = manifest.Versioned{ + SchemaVersion: 1, +} + +func init() { + schema1Func := func(b []byte) (distribution.Manifest, distribution.Descriptor, error) { + sm := new(SignedManifest) + err := sm.UnmarshalJSON(b) + if err != nil { + return nil, distribution.Descriptor{}, err + } + + desc := distribution.Descriptor{ + MediaType: MediaTypeSignedManifest, + Digest: digest.FromBytes(sm.Canonical), + Size: int64(len(sm.Canonical)), + } + return sm, desc, err + } + err := distribution.RegisterManifestSchema(MediaTypeSignedManifest, schema1Func) + if err != nil { + panic(fmt.Sprintf("Unable to register manifest: %s", err)) + } + err = distribution.RegisterManifestSchema("", schema1Func) + if err != nil { + panic(fmt.Sprintf("Unable to register manifest: %s", err)) + } + err = distribution.RegisterManifestSchema("application/json", schema1Func) + if err != nil { + panic(fmt.Sprintf("Unable to register manifest: %s", err)) + } +} + +// FSLayer is a container struct for BlobSums defined in an image manifest. +// +// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015. +// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification. +type FSLayer struct { + // BlobSum is the tarsum of the referenced filesystem image layer + BlobSum digest.Digest `json:"blobSum"` +} + +// History stores unstructured v1 compatibility information. +// +// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015. +// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification. +type History struct { + // V1Compatibility is the raw v1 compatibility information + V1Compatibility string `json:"v1Compatibility"` +} + +// Manifest provides the base accessible fields for working with V2 image +// format in the registry. +// +// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015. +// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification. +type Manifest struct { + manifest.Versioned + + // Name is the name of the image's repository + Name string `json:"name"` + + // Tag is the tag of the image specified by this manifest + Tag string `json:"tag"` + + // Architecture is the host architecture on which this image is intended to + // run + Architecture string `json:"architecture"` + + // FSLayers is a list of filesystem layer blobSums contained in this image + FSLayers []FSLayer `json:"fsLayers"` + + // History is a list of unstructured historical data for v1 compatibility + History []History `json:"history"` +} + +// SignedManifest provides an envelope for a signed image manifest, including +// the format sensitive raw bytes. +// +// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015. +// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification. +type SignedManifest struct { + Manifest + + // Canonical is the canonical byte representation of the ImageManifest, + // without any attached signatures. The manifest byte + // representation cannot change or it will have to be re-signed. + Canonical []byte `json:"-"` + + // all contains the byte representation of the Manifest including signatures + // and is returned by Payload() + all []byte +} + +// UnmarshalJSON populates a new SignedManifest struct from JSON data. +// +// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015. +// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification. +func (sm *SignedManifest) UnmarshalJSON(b []byte) error { + sm.all = make([]byte, len(b)) + // store manifest and signatures in all + copy(sm.all, b) + + jsig, err := libtrust.ParsePrettySignature(b, "signatures") + if err != nil { + return err + } + + // Resolve the payload in the manifest. + bytes, err := jsig.Payload() + if err != nil { + return err + } + + // sm.Canonical stores the canonical manifest JSON + sm.Canonical = make([]byte, len(bytes)) + copy(sm.Canonical, bytes) + + // Unmarshal canonical JSON into Manifest object + var mfst Manifest + if err := json.Unmarshal(sm.Canonical, &mfst); err != nil { + return err + } + + sm.Manifest = mfst + + return nil +} + +// References returns the descriptors of this manifests references. +// +// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015. +// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification. +func (sm SignedManifest) References() []distribution.Descriptor { + dependencies := make([]distribution.Descriptor, len(sm.FSLayers)) + for i, fsLayer := range sm.FSLayers { + dependencies[i] = distribution.Descriptor{ + MediaType: "application/vnd.docker.container.image.rootfs.diff+x-gtar", + Digest: fsLayer.BlobSum, + } + } + + return dependencies +} + +// MarshalJSON returns the contents of raw. If Raw is nil, marshals the inner +// contents. Applications requiring a marshaled signed manifest should simply +// use Raw directly, since the the content produced by json.Marshal will be +// compacted and will fail signature checks. +// +// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015. +// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification. +func (sm *SignedManifest) MarshalJSON() ([]byte, error) { + if len(sm.all) > 0 { + return sm.all, nil + } + + // If the raw data is not available, just dump the inner content. + return json.Marshal(&sm.Manifest) +} + +// Payload returns the signed content of the signed manifest. +// +// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015. +// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification. +func (sm SignedManifest) Payload() (string, []byte, error) { + return MediaTypeSignedManifest, sm.all, nil +} + +// Signatures returns the signatures as provided by +// (*libtrust.JSONSignature).Signatures. The byte slices are opaque jws +// signatures. +// +// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015. +// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification. +func (sm *SignedManifest) Signatures() ([][]byte, error) { + jsig, err := libtrust.ParsePrettySignature(sm.all, "signatures") + if err != nil { + return nil, err + } + + // Resolve the payload in the manifest. + return jsig.Signatures() +} diff --git a/vendor/github.com/openshift/library-go/pkg/image/distribution/manifest/schema1/reference_builder.go b/vendor/github.com/openshift/library-go/pkg/image/distribution/manifest/schema1/reference_builder.go new file mode 100644 index 0000000000..13713b5270 --- /dev/null +++ b/vendor/github.com/openshift/library-go/pkg/image/distribution/manifest/schema1/reference_builder.go @@ -0,0 +1,112 @@ +package schema1 + +import ( + "context" + "errors" + "fmt" + + "github.com/distribution/distribution/v3" + "github.com/distribution/distribution/v3/manifest" + "github.com/distribution/distribution/v3/reference" + "github.com/docker/libtrust" + "github.com/opencontainers/go-digest" +) + +// referenceManifestBuilder is a type for constructing manifests from schema1 +// dependencies. +type referenceManifestBuilder struct { + Manifest + pk libtrust.PrivateKey +} + +// NewReferenceManifestBuilder is used to build new manifests for the current +// schema version using schema1 dependencies. +// +// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015. +// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification. +func NewReferenceManifestBuilder(pk libtrust.PrivateKey, ref reference.Named, architecture string) distribution.ManifestBuilder { + tag := "" + if tagged, isTagged := ref.(reference.Tagged); isTagged { + tag = tagged.Tag() + } + + return &referenceManifestBuilder{ + Manifest: Manifest{ + Versioned: manifest.Versioned{ + SchemaVersion: 1, + }, + Name: ref.Name(), + Tag: tag, + Architecture: architecture, + }, + pk: pk, + } +} + +func (mb *referenceManifestBuilder) Build(ctx context.Context) (distribution.Manifest, error) { + m := mb.Manifest + if len(m.FSLayers) == 0 { + return nil, errors.New("cannot build manifest with zero layers or history") + } + + m.FSLayers = make([]FSLayer, len(mb.Manifest.FSLayers)) + m.History = make([]History, len(mb.Manifest.History)) + copy(m.FSLayers, mb.Manifest.FSLayers) + copy(m.History, mb.Manifest.History) + + return Sign(&m, mb.pk) +} + +// AppendReference adds a reference to the current ManifestBuilder. +// +// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015. +// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification. +func (mb *referenceManifestBuilder) AppendReference(d distribution.Describable) error { + r, ok := d.(Reference) + if !ok { + return fmt.Errorf("unable to add non-reference type to v1 builder") + } + + // Entries need to be prepended + mb.Manifest.FSLayers = append([]FSLayer{{BlobSum: r.Digest}}, mb.Manifest.FSLayers...) + mb.Manifest.History = append([]History{r.History}, mb.Manifest.History...) + return nil +} + +// References returns the current references added to this builder. +// +// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015. +// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification. +func (mb *referenceManifestBuilder) References() []distribution.Descriptor { + refs := make([]distribution.Descriptor, len(mb.Manifest.FSLayers)) + for i := range mb.Manifest.FSLayers { + layerDigest := mb.Manifest.FSLayers[i].BlobSum + history := mb.Manifest.History[i] + ref := Reference{layerDigest, 0, history} + refs[i] = ref.Descriptor() + } + return refs +} + +// Reference describes a Manifest v2, schema version 1 dependency. +// An FSLayer associated with a history entry. +// +// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015. +// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification. +type Reference struct { + Digest digest.Digest + Size int64 // if we know it, set it for the descriptor. + History History +} + +// Descriptor describes a reference. +// +// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015. +// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification. +func (r Reference) Descriptor() distribution.Descriptor { + return distribution.Descriptor{ + MediaType: MediaTypeManifestLayer, + Digest: r.Digest, + Size: r.Size, + } +} diff --git a/vendor/github.com/openshift/library-go/pkg/image/distribution/manifest/schema1/sign.go b/vendor/github.com/openshift/library-go/pkg/image/distribution/manifest/schema1/sign.go new file mode 100644 index 0000000000..920fdb2dcc --- /dev/null +++ b/vendor/github.com/openshift/library-go/pkg/image/distribution/manifest/schema1/sign.go @@ -0,0 +1,74 @@ +package schema1 + +import ( + "crypto/x509" + "encoding/json" + + "github.com/docker/libtrust" +) + +// Sign signs the manifest with the provided private key, returning a +// SignedManifest. This typically won't be used within the registry, except +// for testing. +// +// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015. +// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification. +func Sign(m *Manifest, pk libtrust.PrivateKey) (*SignedManifest, error) { + p, err := json.MarshalIndent(m, "", " ") + if err != nil { + return nil, err + } + + js, err := libtrust.NewJSONSignature(p) + if err != nil { + return nil, err + } + + if err := js.Sign(pk); err != nil { + return nil, err + } + + pretty, err := js.PrettySignature("signatures") + if err != nil { + return nil, err + } + + return &SignedManifest{ + Manifest: *m, + all: pretty, + Canonical: p, + }, nil +} + +// SignWithChain signs the manifest with the given private key and x509 chain. +// The public key of the first element in the chain must be the public key +// corresponding with the sign key. +// +// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015. +// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification. +func SignWithChain(m *Manifest, key libtrust.PrivateKey, chain []*x509.Certificate) (*SignedManifest, error) { + p, err := json.MarshalIndent(m, "", " ") + if err != nil { + return nil, err + } + + js, err := libtrust.NewJSONSignature(p) + if err != nil { + return nil, err + } + + if err := js.SignWithChain(key, chain); err != nil { + return nil, err + } + + pretty, err := js.PrettySignature("signatures") + if err != nil { + return nil, err + } + + return &SignedManifest{ + Manifest: *m, + all: pretty, + Canonical: p, + }, nil +} diff --git a/vendor/github.com/openshift/library-go/pkg/image/distribution/manifest/schema1/verify.go b/vendor/github.com/openshift/library-go/pkg/image/distribution/manifest/schema1/verify.go new file mode 100644 index 0000000000..5d20cc5bb4 --- /dev/null +++ b/vendor/github.com/openshift/library-go/pkg/image/distribution/manifest/schema1/verify.go @@ -0,0 +1,38 @@ +package schema1 + +import ( + "crypto/x509" + + "github.com/docker/libtrust" + "github.com/sirupsen/logrus" +) + +// Verify verifies the signature of the signed manifest returning the public +// keys used during signing. +// +// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015. +// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification. +func Verify(sm *SignedManifest) ([]libtrust.PublicKey, error) { + js, err := libtrust.ParsePrettySignature(sm.all, "signatures") + if err != nil { + logrus.WithField("err", err).Debugf("(*SignedManifest).Verify") + return nil, err + } + + return js.Verify() +} + +// VerifyChains verifies the signature of the signed manifest against the +// certificate pool returning the list of verified chains. Signatures without +// an x509 chain are not checked. +// +// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015. +// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification. +func VerifyChains(sm *SignedManifest, ca *x509.CertPool) ([][]*x509.Certificate, error) { + js, err := libtrust.ParsePrettySignature(sm.all, "signatures") + if err != nil { + return nil, err + } + + return js.VerifyChains(ca) +} diff --git a/vendor/github.com/openshift/library-go/pkg/image/registryclient/client.go b/vendor/github.com/openshift/library-go/pkg/image/registryclient/client.go index c7f961c724..21a9c85021 100644 --- a/vendor/github.com/openshift/library-go/pkg/image/registryclient/client.go +++ b/vendor/github.com/openshift/library-go/pkg/image/registryclient/client.go @@ -13,20 +13,20 @@ import ( "sync" "time" + registryclient "github.com/openshift/library-go/pkg/image/distribution/client" + "github.com/openshift/library-go/pkg/image/distribution/client/auth" + "github.com/openshift/library-go/pkg/image/distribution/client/auth/challenge" + "github.com/openshift/library-go/pkg/image/distribution/client/transport" "golang.org/x/time/rate" "k8s.io/klog/v2" "github.com/distribution/distribution/v3" - "github.com/distribution/distribution/v3/manifest/schema1" "github.com/distribution/distribution/v3/reference" "github.com/distribution/distribution/v3/registry/api/errcode" - registryclient "github.com/distribution/distribution/v3/registry/client" - "github.com/distribution/distribution/v3/registry/client/auth" - "github.com/distribution/distribution/v3/registry/client/auth/challenge" - "github.com/distribution/distribution/v3/registry/client/transport" "github.com/opencontainers/go-digest" + "github.com/openshift/library-go/pkg/image/distribution/manifest/schema1" imagereference "github.com/openshift/library-go/pkg/image/reference" ) diff --git a/vendor/github.com/openshift/library-go/pkg/image/registryclient/client_mirrored.go b/vendor/github.com/openshift/library-go/pkg/image/registryclient/client_mirrored.go index de8bf77735..3ca1f64482 100644 --- a/vendor/github.com/openshift/library-go/pkg/image/registryclient/client_mirrored.go +++ b/vendor/github.com/openshift/library-go/pkg/image/registryclient/client_mirrored.go @@ -11,9 +11,9 @@ import ( "github.com/distribution/distribution/v3" "github.com/distribution/distribution/v3/registry/api/errcode" - "github.com/distribution/distribution/v3/registry/client" - "github.com/distribution/distribution/v3/registry/client/auth" "github.com/opencontainers/go-digest" + "github.com/openshift/library-go/pkg/image/distribution/client" + "github.com/openshift/library-go/pkg/image/distribution/client/auth" "github.com/openshift/library-go/pkg/image/reference" "k8s.io/klog/v2" diff --git a/vendor/github.com/openshift/library-go/pkg/image/registryclient/credentials.go b/vendor/github.com/openshift/library-go/pkg/image/registryclient/credentials.go index 6ca8e49eda..2f0e83b831 100644 --- a/vendor/github.com/openshift/library-go/pkg/image/registryclient/credentials.go +++ b/vendor/github.com/openshift/library-go/pkg/image/registryclient/credentials.go @@ -4,7 +4,7 @@ import ( "net/url" "sync" - "github.com/distribution/distribution/v3/registry/client/auth" + "github.com/openshift/library-go/pkg/image/distribution/client/auth" ) var ( diff --git a/vendor/modules.txt b/vendor/modules.txt index 8845067098..d5ad9cd74b 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -284,7 +284,6 @@ github.com/distribution/distribution/v3/reference github.com/distribution/distribution/v3/registry/api/errcode github.com/distribution/distribution/v3/registry/api/v2 github.com/distribution/distribution/v3/registry/client -github.com/distribution/distribution/v3/registry/client/auth github.com/distribution/distribution/v3/registry/client/auth/challenge github.com/distribution/distribution/v3/registry/client/transport github.com/distribution/distribution/v3/registry/storage/cache @@ -861,7 +860,7 @@ github.com/openshift/client-go/user/clientset/versioned/fake github.com/openshift/client-go/user/clientset/versioned/scheme github.com/openshift/client-go/user/clientset/versioned/typed/user/v1 github.com/openshift/client-go/user/clientset/versioned/typed/user/v1/fake -# github.com/openshift/library-go v0.0.0-20251222131241-289839b3ffe8 +# github.com/openshift/library-go v0.0.0-20251222131241-289839b3ffe8 => github.com/ardaguclu/library-go v0.0.0-20260105092109-3fc6249cdc6d ## explicit; go 1.24.0 github.com/openshift/library-go/pkg/apiserver/jsonpatch github.com/openshift/library-go/pkg/apps/appsserialization @@ -881,6 +880,11 @@ github.com/openshift/library-go/pkg/controller/factory github.com/openshift/library-go/pkg/crypto github.com/openshift/library-go/pkg/features github.com/openshift/library-go/pkg/git +github.com/openshift/library-go/pkg/image/distribution/client +github.com/openshift/library-go/pkg/image/distribution/client/auth +github.com/openshift/library-go/pkg/image/distribution/client/auth/challenge +github.com/openshift/library-go/pkg/image/distribution/client/transport +github.com/openshift/library-go/pkg/image/distribution/manifest/schema1 github.com/openshift/library-go/pkg/image/dockerv1client github.com/openshift/library-go/pkg/image/imageutil github.com/openshift/library-go/pkg/image/internal/digest @@ -1951,3 +1955,4 @@ sigs.k8s.io/yaml sigs.k8s.io/yaml/goyaml.v3 sigs.k8s.io/yaml/kyaml # github.com/apcera/gssapi => github.com/openshift/gssapi v0.0.0-20161010215902-5fb4217df13b +# github.com/openshift/library-go => github.com/ardaguclu/library-go v0.0.0-20260105092109-3fc6249cdc6d From 59df0fb932f7e7e6a1fd17d038625656f0d5f9ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arda=20G=C3=BC=C3=A7l=C3=BC?= Date: Mon, 5 Jan 2026 12:57:02 +0300 Subject: [PATCH 2/2] Update schema1 changes --- pkg/cli/image/manifest/manifest.go | 2 +- pkg/cli/image/mirror/mirror.go | 2 +- pkg/helpers/image/test/util.go | 2 +- .../v3/manifest/schema1/config_builder.go | 303 ------------------ .../v3/manifest/schema1/manifest.go | 226 ------------- .../v3/manifest/schema1/reference_builder.go | 112 ------- .../distribution/v3/manifest/schema1/sign.go | 74 ----- .../v3/manifest/schema1/verify.go | 38 --- vendor/modules.txt | 1 - 9 files changed, 3 insertions(+), 757 deletions(-) delete mode 100644 vendor/github.com/distribution/distribution/v3/manifest/schema1/config_builder.go delete mode 100644 vendor/github.com/distribution/distribution/v3/manifest/schema1/manifest.go delete mode 100644 vendor/github.com/distribution/distribution/v3/manifest/schema1/reference_builder.go delete mode 100644 vendor/github.com/distribution/distribution/v3/manifest/schema1/sign.go delete mode 100644 vendor/github.com/distribution/distribution/v3/manifest/schema1/verify.go diff --git a/pkg/cli/image/manifest/manifest.go b/pkg/cli/image/manifest/manifest.go index 4e71cb11c0..8357195f09 100644 --- a/pkg/cli/image/manifest/manifest.go +++ b/pkg/cli/image/manifest/manifest.go @@ -10,12 +10,12 @@ import ( "runtime" "sync" + "github.com/openshift/library-go/pkg/image/distribution/manifest/schema1" "github.com/spf13/pflag" "github.com/distribution/distribution/v3" "github.com/distribution/distribution/v3/manifest/manifestlist" "github.com/distribution/distribution/v3/manifest/ocischema" - "github.com/distribution/distribution/v3/manifest/schema1" "github.com/distribution/distribution/v3/manifest/schema2" "github.com/distribution/distribution/v3/reference" "github.com/distribution/distribution/v3/registry/api/errcode" diff --git a/pkg/cli/image/mirror/mirror.go b/pkg/cli/image/mirror/mirror.go index 82ef39d951..55b3fa85c9 100644 --- a/pkg/cli/image/mirror/mirror.go +++ b/pkg/cli/image/mirror/mirror.go @@ -12,12 +12,12 @@ import ( "github.com/distribution/distribution/v3" "github.com/distribution/distribution/v3/manifest/manifestlist" "github.com/distribution/distribution/v3/manifest/ocischema" - "github.com/distribution/distribution/v3/manifest/schema1" "github.com/distribution/distribution/v3/manifest/schema2" "github.com/distribution/distribution/v3/reference" "github.com/distribution/distribution/v3/registry/client" units "github.com/docker/go-units" godigest "github.com/opencontainers/go-digest" + "github.com/openshift/library-go/pkg/image/distribution/manifest/schema1" "github.com/spf13/cobra" "k8s.io/apimachinery/pkg/util/sets" diff --git a/pkg/helpers/image/test/util.go b/pkg/helpers/image/test/util.go index 8675bc324c..98be9c19d6 100644 --- a/pkg/helpers/image/test/util.go +++ b/pkg/helpers/image/test/util.go @@ -4,9 +4,9 @@ import ( "fmt" "time" - "github.com/distribution/distribution/v3/manifest/schema1" "github.com/distribution/distribution/v3/manifest/schema2" imagespecv1 "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/openshift/library-go/pkg/image/distribution/manifest/schema1" kappsv1 "k8s.io/api/apps/v1" batchv1 "k8s.io/api/batch/v1" diff --git a/vendor/github.com/distribution/distribution/v3/manifest/schema1/config_builder.go b/vendor/github.com/distribution/distribution/v3/manifest/schema1/config_builder.go deleted file mode 100644 index 77fdfee996..0000000000 --- a/vendor/github.com/distribution/distribution/v3/manifest/schema1/config_builder.go +++ /dev/null @@ -1,303 +0,0 @@ -package schema1 - -import ( - "context" - "crypto/sha512" - "encoding/json" - "errors" - "fmt" - "time" - - "github.com/distribution/distribution/v3" - "github.com/distribution/distribution/v3/manifest" - "github.com/distribution/distribution/v3/reference" - "github.com/docker/libtrust" - "github.com/opencontainers/go-digest" -) - -type diffID digest.Digest - -// gzippedEmptyTar is a gzip-compressed version of an empty tar file -// (1024 NULL bytes) -var gzippedEmptyTar = []byte{ - 31, 139, 8, 0, 0, 9, 110, 136, 0, 255, 98, 24, 5, 163, 96, 20, 140, 88, - 0, 8, 0, 0, 255, 255, 46, 175, 181, 239, 0, 4, 0, 0, -} - -// digestSHA256GzippedEmptyTar is the canonical sha256 digest of -// gzippedEmptyTar -const digestSHA256GzippedEmptyTar = digest.Digest("sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4") - -// configManifestBuilder is a type for constructing manifests from an image -// configuration and generic descriptors. -type configManifestBuilder struct { - // bs is a BlobService used to create empty layer tars in the - // blob store if necessary. - bs distribution.BlobService - // pk is the libtrust private key used to sign the final manifest. - pk libtrust.PrivateKey - // configJSON is configuration supplied when the ManifestBuilder was - // created. - configJSON []byte - // ref contains the name and optional tag provided to NewConfigManifestBuilder. - ref reference.Named - // descriptors is the set of descriptors referencing the layers. - descriptors []distribution.Descriptor - // emptyTarDigest is set to a valid digest if an empty tar has been - // put in the blob store; otherwise it is empty. - emptyTarDigest digest.Digest -} - -// NewConfigManifestBuilder is used to build new manifests for the current -// schema version from an image configuration and a set of descriptors. -// It takes a BlobService so that it can add an empty tar to the blob store -// if the resulting manifest needs empty layers. -// -// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015, -// use Docker Image Manifest v2, Schema 2, or the OCI Image Specification v1. -// This package should not be used for purposes other than backward compatibility. -func NewConfigManifestBuilder(bs distribution.BlobService, pk libtrust.PrivateKey, ref reference.Named, configJSON []byte) distribution.ManifestBuilder { - return &configManifestBuilder{ - bs: bs, - pk: pk, - configJSON: configJSON, - ref: ref, - } -} - -// Build produces a final manifest from the given references. -// -// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015. -// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification. -func (mb *configManifestBuilder) Build(ctx context.Context) (m distribution.Manifest, err error) { - type imageRootFS struct { - Type string `json:"type"` - DiffIDs []diffID `json:"diff_ids,omitempty"` - BaseLayer string `json:"base_layer,omitempty"` - } - - type imageHistory struct { - Created time.Time `json:"created"` - Author string `json:"author,omitempty"` - CreatedBy string `json:"created_by,omitempty"` - Comment string `json:"comment,omitempty"` - EmptyLayer bool `json:"empty_layer,omitempty"` - } - - type imageConfig struct { - RootFS *imageRootFS `json:"rootfs,omitempty"` - History []imageHistory `json:"history,omitempty"` - Architecture string `json:"architecture,omitempty"` - } - - var img imageConfig - - if err := json.Unmarshal(mb.configJSON, &img); err != nil { - return nil, err - } - - if len(img.History) == 0 { - return nil, errors.New("empty history when trying to create schema1 manifest") - } - - if len(img.RootFS.DiffIDs) != len(mb.descriptors) { - return nil, fmt.Errorf("number of descriptors and number of layers in rootfs must match: len(%v) != len(%v)", img.RootFS.DiffIDs, mb.descriptors) - } - - // Generate IDs for each layer - // For non-top-level layers, create fake V1Compatibility strings that - // fit the format and don't collide with anything else, but don't - // result in runnable images on their own. - type v1Compatibility struct { - ID string `json:"id"` - Parent string `json:"parent,omitempty"` - Comment string `json:"comment,omitempty"` - Created time.Time `json:"created"` - ContainerConfig struct { - Cmd []string - } `json:"container_config,omitempty"` - Author string `json:"author,omitempty"` - ThrowAway bool `json:"throwaway,omitempty"` - } - - fsLayerList := make([]FSLayer, len(img.History)) - history := make([]History, len(img.History)) - - parent := "" - layerCounter := 0 - for i, h := range img.History[:len(img.History)-1] { - var blobsum digest.Digest - if h.EmptyLayer { - if blobsum, err = mb.emptyTar(ctx); err != nil { - return nil, err - } - } else { - if len(img.RootFS.DiffIDs) <= layerCounter { - return nil, errors.New("too many non-empty layers in History section") - } - blobsum = mb.descriptors[layerCounter].Digest - layerCounter++ - } - - v1ID := digest.FromBytes([]byte(blobsum.Encoded() + " " + parent)).Encoded() - - if i == 0 && img.RootFS.BaseLayer != "" { - // windows-only baselayer setup - baseID := sha512.Sum384([]byte(img.RootFS.BaseLayer)) - parent = fmt.Sprintf("%x", baseID[:32]) - } - - v1Compat := v1Compatibility{ - ID: v1ID, - Parent: parent, - Comment: h.Comment, - Created: h.Created, - Author: h.Author, - } - v1Compat.ContainerConfig.Cmd = []string{img.History[i].CreatedBy} - if h.EmptyLayer { - v1Compat.ThrowAway = true - } - jsonBytes, err := json.Marshal(&v1Compat) - if err != nil { - return nil, err - } - - reversedIndex := len(img.History) - i - 1 - history[reversedIndex].V1Compatibility = string(jsonBytes) - fsLayerList[reversedIndex] = FSLayer{BlobSum: blobsum} - - parent = v1ID - } - - latestHistory := img.History[len(img.History)-1] - - var blobsum digest.Digest - if latestHistory.EmptyLayer { - if blobsum, err = mb.emptyTar(ctx); err != nil { - return nil, err - } - } else { - if len(img.RootFS.DiffIDs) <= layerCounter { - return nil, errors.New("too many non-empty layers in History section") - } - blobsum = mb.descriptors[layerCounter].Digest - } - - fsLayerList[0] = FSLayer{BlobSum: blobsum} - dgst := digest.FromBytes([]byte(blobsum.Encoded() + " " + parent + " " + string(mb.configJSON))) - - // Top-level v1compatibility string should be a modified version of the - // image config. - transformedConfig, err := MakeV1ConfigFromConfig(mb.configJSON, dgst.Encoded(), parent, latestHistory.EmptyLayer) - if err != nil { - return nil, err - } - - history[0].V1Compatibility = string(transformedConfig) - - tag := "" - if tagged, isTagged := mb.ref.(reference.Tagged); isTagged { - tag = tagged.Tag() - } - - mfst := Manifest{ - Versioned: manifest.Versioned{ - SchemaVersion: 1, - }, - Name: mb.ref.Name(), - Tag: tag, - Architecture: img.Architecture, - FSLayers: fsLayerList, - History: history, - } - - return Sign(&mfst, mb.pk) -} - -// emptyTar pushes a compressed empty tar to the blob store if one doesn't -// already exist, and returns its blobsum. -func (mb *configManifestBuilder) emptyTar(ctx context.Context) (digest.Digest, error) { - if mb.emptyTarDigest != "" { - // Already put an empty tar - return mb.emptyTarDigest, nil - } - - descriptor, err := mb.bs.Stat(ctx, digestSHA256GzippedEmptyTar) - switch err { - case nil: - mb.emptyTarDigest = descriptor.Digest - return descriptor.Digest, nil - case distribution.ErrBlobUnknown: - // nop - default: - return "", err - } - - // Add gzipped empty tar to the blob store - descriptor, err = mb.bs.Put(ctx, "", gzippedEmptyTar) - if err != nil { - return "", err - } - - mb.emptyTarDigest = descriptor.Digest - - return descriptor.Digest, nil -} - -// AppendReference adds a reference to the current ManifestBuilder. -// -// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015. -// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification. -func (mb *configManifestBuilder) AppendReference(d distribution.Describable) error { - descriptor := d.Descriptor() - - if err := descriptor.Digest.Validate(); err != nil { - return err - } - - mb.descriptors = append(mb.descriptors, descriptor) - return nil -} - -// References returns the current references added to this builder. -// -// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015. -// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification. -func (mb *configManifestBuilder) References() []distribution.Descriptor { - return mb.descriptors -} - -// MakeV1ConfigFromConfig creates an legacy V1 image config from image config JSON. -// -// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015. -// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification. -func MakeV1ConfigFromConfig(configJSON []byte, v1ID, parentV1ID string, throwaway bool) ([]byte, error) { - // Top-level v1compatibility string should be a modified version of the - // image config. - var configAsMap map[string]*json.RawMessage - if err := json.Unmarshal(configJSON, &configAsMap); err != nil { - return nil, err - } - - // Delete fields that didn't exist in old manifest - delete(configAsMap, "rootfs") - delete(configAsMap, "history") - configAsMap["id"] = rawJSON(v1ID) - if parentV1ID != "" { - configAsMap["parent"] = rawJSON(parentV1ID) - } - if throwaway { - configAsMap["throwaway"] = rawJSON(true) - } - - return json.Marshal(configAsMap) -} - -func rawJSON(value interface{}) *json.RawMessage { - jsonval, err := json.Marshal(value) - if err != nil { - return nil - } - return (*json.RawMessage)(&jsonval) -} diff --git a/vendor/github.com/distribution/distribution/v3/manifest/schema1/manifest.go b/vendor/github.com/distribution/distribution/v3/manifest/schema1/manifest.go deleted file mode 100644 index 4faee15d80..0000000000 --- a/vendor/github.com/distribution/distribution/v3/manifest/schema1/manifest.go +++ /dev/null @@ -1,226 +0,0 @@ -// Package schema1 provides definitions for the deprecated Docker Image -// Manifest v2, Schema 1 specification. -// -// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015. -// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification. This package should not be used for purposes -// other than backward compatibility. -package schema1 - -import ( - "encoding/json" - "fmt" - - "github.com/distribution/distribution/v3" - "github.com/distribution/distribution/v3/manifest" - "github.com/docker/libtrust" - "github.com/opencontainers/go-digest" -) - -// MediaTypeManifest specifies the mediaType for the current version. Note -// that for schema version 1, the the media is optionally "application/json". -// -// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015. -// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification. -const MediaTypeManifest = "application/vnd.docker.distribution.manifest.v1+json" - -// MediaTypeSignedManifest specifies the mediatype for current SignedManifest version -// -// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015. -// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification. -const MediaTypeSignedManifest = "application/vnd.docker.distribution.manifest.v1+prettyjws" - -// MediaTypeManifestLayer specifies the media type for manifest layers -// -// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015. -// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification. -const MediaTypeManifestLayer = "application/vnd.docker.container.image.rootfs.diff+x-gtar" - -// SchemaVersion provides a pre-initialized version structure for this -// packages version of the manifest. -// -// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015. -// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification. -var SchemaVersion = manifest.Versioned{ - SchemaVersion: 1, -} - -func init() { - schema1Func := func(b []byte) (distribution.Manifest, distribution.Descriptor, error) { - sm := new(SignedManifest) - err := sm.UnmarshalJSON(b) - if err != nil { - return nil, distribution.Descriptor{}, err - } - - desc := distribution.Descriptor{ - MediaType: MediaTypeSignedManifest, - Digest: digest.FromBytes(sm.Canonical), - Size: int64(len(sm.Canonical)), - } - return sm, desc, err - } - err := distribution.RegisterManifestSchema(MediaTypeSignedManifest, schema1Func) - if err != nil { - panic(fmt.Sprintf("Unable to register manifest: %s", err)) - } - err = distribution.RegisterManifestSchema("", schema1Func) - if err != nil { - panic(fmt.Sprintf("Unable to register manifest: %s", err)) - } - err = distribution.RegisterManifestSchema("application/json", schema1Func) - if err != nil { - panic(fmt.Sprintf("Unable to register manifest: %s", err)) - } -} - -// FSLayer is a container struct for BlobSums defined in an image manifest. -// -// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015. -// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification. -type FSLayer struct { - // BlobSum is the tarsum of the referenced filesystem image layer - BlobSum digest.Digest `json:"blobSum"` -} - -// History stores unstructured v1 compatibility information. -// -// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015. -// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification. -type History struct { - // V1Compatibility is the raw v1 compatibility information - V1Compatibility string `json:"v1Compatibility"` -} - -// Manifest provides the base accessible fields for working with V2 image -// format in the registry. -// -// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015. -// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification. -type Manifest struct { - manifest.Versioned - - // Name is the name of the image's repository - Name string `json:"name"` - - // Tag is the tag of the image specified by this manifest - Tag string `json:"tag"` - - // Architecture is the host architecture on which this image is intended to - // run - Architecture string `json:"architecture"` - - // FSLayers is a list of filesystem layer blobSums contained in this image - FSLayers []FSLayer `json:"fsLayers"` - - // History is a list of unstructured historical data for v1 compatibility - History []History `json:"history"` -} - -// SignedManifest provides an envelope for a signed image manifest, including -// the format sensitive raw bytes. -// -// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015. -// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification. -type SignedManifest struct { - Manifest - - // Canonical is the canonical byte representation of the ImageManifest, - // without any attached signatures. The manifest byte - // representation cannot change or it will have to be re-signed. - Canonical []byte `json:"-"` - - // all contains the byte representation of the Manifest including signatures - // and is returned by Payload() - all []byte -} - -// UnmarshalJSON populates a new SignedManifest struct from JSON data. -// -// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015. -// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification. -func (sm *SignedManifest) UnmarshalJSON(b []byte) error { - sm.all = make([]byte, len(b)) - // store manifest and signatures in all - copy(sm.all, b) - - jsig, err := libtrust.ParsePrettySignature(b, "signatures") - if err != nil { - return err - } - - // Resolve the payload in the manifest. - bytes, err := jsig.Payload() - if err != nil { - return err - } - - // sm.Canonical stores the canonical manifest JSON - sm.Canonical = make([]byte, len(bytes)) - copy(sm.Canonical, bytes) - - // Unmarshal canonical JSON into Manifest object - var mfst Manifest - if err := json.Unmarshal(sm.Canonical, &mfst); err != nil { - return err - } - - sm.Manifest = mfst - - return nil -} - -// References returns the descriptors of this manifests references. -// -// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015. -// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification. -func (sm SignedManifest) References() []distribution.Descriptor { - dependencies := make([]distribution.Descriptor, len(sm.FSLayers)) - for i, fsLayer := range sm.FSLayers { - dependencies[i] = distribution.Descriptor{ - MediaType: "application/vnd.docker.container.image.rootfs.diff+x-gtar", - Digest: fsLayer.BlobSum, - } - } - - return dependencies -} - -// MarshalJSON returns the contents of raw. If Raw is nil, marshals the inner -// contents. Applications requiring a marshaled signed manifest should simply -// use Raw directly, since the the content produced by json.Marshal will be -// compacted and will fail signature checks. -// -// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015. -// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification. -func (sm *SignedManifest) MarshalJSON() ([]byte, error) { - if len(sm.all) > 0 { - return sm.all, nil - } - - // If the raw data is not available, just dump the inner content. - return json.Marshal(&sm.Manifest) -} - -// Payload returns the signed content of the signed manifest. -// -// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015. -// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification. -func (sm SignedManifest) Payload() (string, []byte, error) { - return MediaTypeSignedManifest, sm.all, nil -} - -// Signatures returns the signatures as provided by -// (*libtrust.JSONSignature).Signatures. The byte slices are opaque jws -// signatures. -// -// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015. -// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification. -func (sm *SignedManifest) Signatures() ([][]byte, error) { - jsig, err := libtrust.ParsePrettySignature(sm.all, "signatures") - if err != nil { - return nil, err - } - - // Resolve the payload in the manifest. - return jsig.Signatures() -} diff --git a/vendor/github.com/distribution/distribution/v3/manifest/schema1/reference_builder.go b/vendor/github.com/distribution/distribution/v3/manifest/schema1/reference_builder.go deleted file mode 100644 index 13713b5270..0000000000 --- a/vendor/github.com/distribution/distribution/v3/manifest/schema1/reference_builder.go +++ /dev/null @@ -1,112 +0,0 @@ -package schema1 - -import ( - "context" - "errors" - "fmt" - - "github.com/distribution/distribution/v3" - "github.com/distribution/distribution/v3/manifest" - "github.com/distribution/distribution/v3/reference" - "github.com/docker/libtrust" - "github.com/opencontainers/go-digest" -) - -// referenceManifestBuilder is a type for constructing manifests from schema1 -// dependencies. -type referenceManifestBuilder struct { - Manifest - pk libtrust.PrivateKey -} - -// NewReferenceManifestBuilder is used to build new manifests for the current -// schema version using schema1 dependencies. -// -// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015. -// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification. -func NewReferenceManifestBuilder(pk libtrust.PrivateKey, ref reference.Named, architecture string) distribution.ManifestBuilder { - tag := "" - if tagged, isTagged := ref.(reference.Tagged); isTagged { - tag = tagged.Tag() - } - - return &referenceManifestBuilder{ - Manifest: Manifest{ - Versioned: manifest.Versioned{ - SchemaVersion: 1, - }, - Name: ref.Name(), - Tag: tag, - Architecture: architecture, - }, - pk: pk, - } -} - -func (mb *referenceManifestBuilder) Build(ctx context.Context) (distribution.Manifest, error) { - m := mb.Manifest - if len(m.FSLayers) == 0 { - return nil, errors.New("cannot build manifest with zero layers or history") - } - - m.FSLayers = make([]FSLayer, len(mb.Manifest.FSLayers)) - m.History = make([]History, len(mb.Manifest.History)) - copy(m.FSLayers, mb.Manifest.FSLayers) - copy(m.History, mb.Manifest.History) - - return Sign(&m, mb.pk) -} - -// AppendReference adds a reference to the current ManifestBuilder. -// -// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015. -// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification. -func (mb *referenceManifestBuilder) AppendReference(d distribution.Describable) error { - r, ok := d.(Reference) - if !ok { - return fmt.Errorf("unable to add non-reference type to v1 builder") - } - - // Entries need to be prepended - mb.Manifest.FSLayers = append([]FSLayer{{BlobSum: r.Digest}}, mb.Manifest.FSLayers...) - mb.Manifest.History = append([]History{r.History}, mb.Manifest.History...) - return nil -} - -// References returns the current references added to this builder. -// -// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015. -// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification. -func (mb *referenceManifestBuilder) References() []distribution.Descriptor { - refs := make([]distribution.Descriptor, len(mb.Manifest.FSLayers)) - for i := range mb.Manifest.FSLayers { - layerDigest := mb.Manifest.FSLayers[i].BlobSum - history := mb.Manifest.History[i] - ref := Reference{layerDigest, 0, history} - refs[i] = ref.Descriptor() - } - return refs -} - -// Reference describes a Manifest v2, schema version 1 dependency. -// An FSLayer associated with a history entry. -// -// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015. -// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification. -type Reference struct { - Digest digest.Digest - Size int64 // if we know it, set it for the descriptor. - History History -} - -// Descriptor describes a reference. -// -// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015. -// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification. -func (r Reference) Descriptor() distribution.Descriptor { - return distribution.Descriptor{ - MediaType: MediaTypeManifestLayer, - Digest: r.Digest, - Size: r.Size, - } -} diff --git a/vendor/github.com/distribution/distribution/v3/manifest/schema1/sign.go b/vendor/github.com/distribution/distribution/v3/manifest/schema1/sign.go deleted file mode 100644 index 920fdb2dcc..0000000000 --- a/vendor/github.com/distribution/distribution/v3/manifest/schema1/sign.go +++ /dev/null @@ -1,74 +0,0 @@ -package schema1 - -import ( - "crypto/x509" - "encoding/json" - - "github.com/docker/libtrust" -) - -// Sign signs the manifest with the provided private key, returning a -// SignedManifest. This typically won't be used within the registry, except -// for testing. -// -// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015. -// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification. -func Sign(m *Manifest, pk libtrust.PrivateKey) (*SignedManifest, error) { - p, err := json.MarshalIndent(m, "", " ") - if err != nil { - return nil, err - } - - js, err := libtrust.NewJSONSignature(p) - if err != nil { - return nil, err - } - - if err := js.Sign(pk); err != nil { - return nil, err - } - - pretty, err := js.PrettySignature("signatures") - if err != nil { - return nil, err - } - - return &SignedManifest{ - Manifest: *m, - all: pretty, - Canonical: p, - }, nil -} - -// SignWithChain signs the manifest with the given private key and x509 chain. -// The public key of the first element in the chain must be the public key -// corresponding with the sign key. -// -// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015. -// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification. -func SignWithChain(m *Manifest, key libtrust.PrivateKey, chain []*x509.Certificate) (*SignedManifest, error) { - p, err := json.MarshalIndent(m, "", " ") - if err != nil { - return nil, err - } - - js, err := libtrust.NewJSONSignature(p) - if err != nil { - return nil, err - } - - if err := js.SignWithChain(key, chain); err != nil { - return nil, err - } - - pretty, err := js.PrettySignature("signatures") - if err != nil { - return nil, err - } - - return &SignedManifest{ - Manifest: *m, - all: pretty, - Canonical: p, - }, nil -} diff --git a/vendor/github.com/distribution/distribution/v3/manifest/schema1/verify.go b/vendor/github.com/distribution/distribution/v3/manifest/schema1/verify.go deleted file mode 100644 index 5d20cc5bb4..0000000000 --- a/vendor/github.com/distribution/distribution/v3/manifest/schema1/verify.go +++ /dev/null @@ -1,38 +0,0 @@ -package schema1 - -import ( - "crypto/x509" - - "github.com/docker/libtrust" - "github.com/sirupsen/logrus" -) - -// Verify verifies the signature of the signed manifest returning the public -// keys used during signing. -// -// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015. -// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification. -func Verify(sm *SignedManifest) ([]libtrust.PublicKey, error) { - js, err := libtrust.ParsePrettySignature(sm.all, "signatures") - if err != nil { - logrus.WithField("err", err).Debugf("(*SignedManifest).Verify") - return nil, err - } - - return js.Verify() -} - -// VerifyChains verifies the signature of the signed manifest against the -// certificate pool returning the list of verified chains. Signatures without -// an x509 chain are not checked. -// -// Deprecated: Docker Image Manifest v2, Schema 1 is deprecated since 2015. -// Use Docker Image Manifest v2, Schema 2, or the OCI Image Specification. -func VerifyChains(sm *SignedManifest, ca *x509.CertPool) ([][]*x509.Certificate, error) { - js, err := libtrust.ParsePrettySignature(sm.all, "signatures") - if err != nil { - return nil, err - } - - return js.VerifyChains(ca) -} diff --git a/vendor/modules.txt b/vendor/modules.txt index d5ad9cd74b..c1a7f29ec7 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -277,7 +277,6 @@ github.com/distribution/distribution/v3/context github.com/distribution/distribution/v3/manifest github.com/distribution/distribution/v3/manifest/manifestlist github.com/distribution/distribution/v3/manifest/ocischema -github.com/distribution/distribution/v3/manifest/schema1 github.com/distribution/distribution/v3/manifest/schema2 github.com/distribution/distribution/v3/metrics github.com/distribution/distribution/v3/reference