diff --git a/go.mod b/go.mod index c9bd2c41f..4ececa2ab 100644 --- a/go.mod +++ b/go.mod @@ -22,7 +22,7 @@ require ( github.com/go-git/go-git/v5 v5.11.0 github.com/golang/mock v1.6.0 github.com/google/go-cmp v0.6.0 - github.com/google/go-containerregistry v0.15.2 + github.com/google/go-containerregistry v0.17.0 github.com/google/go-github v17.0.0+incompatible github.com/google/slowjam v1.1.0 github.com/karrick/godirwalk v1.16.1 diff --git a/go.sum b/go.sum index d7cd6b470..b764c8dbb 100644 --- a/go.sum +++ b/go.sum @@ -329,8 +329,8 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-containerregistry v0.15.2 h1:MMkSh+tjSdnmJZO7ljvEqV1DjfekB6VUEAZgy3a+TQE= -github.com/google/go-containerregistry v0.15.2/go.mod h1:wWK+LnOv4jXMM23IT/F1wdYftGWGr47Is8CG+pmHK1Q= +github.com/google/go-containerregistry v0.17.0 h1:5p+zYs/R4VGHkhyvgWurWrpJ2hW4Vv9fQI+GzdcwXLk= +github.com/google/go-containerregistry v0.17.0/go.mod h1:u0qB2l7mvtWVR5kNcbFIhFY1hLbf8eeGapA+vbFDCtQ= github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= diff --git a/vendor/github.com/google/go-containerregistry/pkg/authn/keychain.go b/vendor/github.com/google/go-containerregistry/pkg/authn/keychain.go index 4e32500cd..99e0b81c8 100644 --- a/vendor/github.com/google/go-containerregistry/pkg/authn/keychain.go +++ b/vendor/github.com/google/go-containerregistry/pkg/authn/keychain.go @@ -53,7 +53,7 @@ type defaultKeychain struct { var ( // DefaultKeychain implements Keychain by interpreting the docker config file. - DefaultKeychain = RefreshingKeychain(&defaultKeychain{}, 5*time.Minute) + DefaultKeychain = &defaultKeychain{} ) const ( diff --git a/vendor/github.com/google/go-containerregistry/pkg/v1/daemon/write.go b/vendor/github.com/google/go-containerregistry/pkg/v1/daemon/write.go index 48186f662..3ca5b52dd 100644 --- a/vendor/github.com/google/go-containerregistry/pkg/v1/daemon/write.go +++ b/vendor/github.com/google/go-containerregistry/pkg/v1/daemon/write.go @@ -40,6 +40,24 @@ func Write(tag name.Tag, img v1.Image, options ...Option) (string, error) { return "", err } + // If we already have this image by this image ID, we can skip loading it. + id, err := img.ConfigName() + if err != nil { + return "", fmt.Errorf("computing image ID: %w", err) + } + if resp, _, err := o.client.ImageInspectWithRaw(o.ctx, id.String()); err == nil { + want := tag.String() + + // If we already have this tag, we can skip tagging it. + for _, have := range resp.RepoTags { + if have == want { + return "", nil + } + } + + return "", o.client.ImageTag(o.ctx, id.String(), want) + } + pr, pw := io.Pipe() go func() { pw.CloseWithError(tarball.Write(tag, img, pw)) diff --git a/vendor/github.com/google/go-containerregistry/pkg/v1/layout/gc.go b/vendor/github.com/google/go-containerregistry/pkg/v1/layout/gc.go new file mode 100644 index 000000000..5fdb2c05d --- /dev/null +++ b/vendor/github.com/google/go-containerregistry/pkg/v1/layout/gc.go @@ -0,0 +1,137 @@ +// Copyright 2018 Google LLC All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// This is an EXPERIMENTAL package, and may change in arbitrary ways without notice. +package layout + +import ( + "fmt" + "io/fs" + "path/filepath" + "strings" + + v1 "github.com/google/go-containerregistry/pkg/v1" +) + +// GarbageCollect removes unreferenced blobs from the oci-layout +// +// This is an experimental api, and not subject to any stability guarantees +// We may abandon it at any time, without prior notice. +// Deprecated: Use it at your own risk! +func (l Path) GarbageCollect() ([]v1.Hash, error) { + idx, err := l.ImageIndex() + if err != nil { + return nil, err + } + blobsToKeep := map[string]bool{} + if err := l.garbageCollectImageIndex(idx, blobsToKeep); err != nil { + return nil, err + } + blobsDir := l.path("blobs") + removedBlobs := []v1.Hash{} + + err = filepath.WalkDir(blobsDir, func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + + if d.IsDir() { + return nil + } + + rel, err := filepath.Rel(blobsDir, path) + if err != nil { + return err + } + hashString := strings.Replace(rel, "/", ":", 1) + if present := blobsToKeep[hashString]; !present { + h, err := v1.NewHash(hashString) + if err != nil { + return err + } + removedBlobs = append(removedBlobs, h) + } + return nil + }) + + if err != nil { + return nil, err + } + + return removedBlobs, nil +} + +func (l Path) garbageCollectImageIndex(index v1.ImageIndex, blobsToKeep map[string]bool) error { + idxm, err := index.IndexManifest() + if err != nil { + return err + } + + h, err := index.Digest() + if err != nil { + return err + } + + blobsToKeep[h.String()] = true + + for _, descriptor := range idxm.Manifests { + if descriptor.MediaType.IsImage() { + img, err := index.Image(descriptor.Digest) + if err != nil { + return err + } + if err := l.garbageCollectImage(img, blobsToKeep); err != nil { + return err + } + } else if descriptor.MediaType.IsIndex() { + idx, err := index.ImageIndex(descriptor.Digest) + if err != nil { + return err + } + if err := l.garbageCollectImageIndex(idx, blobsToKeep); err != nil { + return err + } + } else { + return fmt.Errorf("gc: unknown media type: %s", descriptor.MediaType) + } + } + return nil +} + +func (l Path) garbageCollectImage(image v1.Image, blobsToKeep map[string]bool) error { + h, err := image.Digest() + if err != nil { + return err + } + blobsToKeep[h.String()] = true + + h, err = image.ConfigName() + if err != nil { + return err + } + blobsToKeep[h.String()] = true + + ls, err := image.Layers() + if err != nil { + return err + } + for _, l := range ls { + h, err := l.Digest() + if err != nil { + return err + } + blobsToKeep[h.String()] = true + } + return nil +} diff --git a/vendor/github.com/google/go-containerregistry/pkg/v1/layout/write.go b/vendor/github.com/google/go-containerregistry/pkg/v1/layout/write.go index 906b12aec..d6e35c391 100644 --- a/vendor/github.com/google/go-containerregistry/pkg/v1/layout/write.go +++ b/vendor/github.com/google/go-containerregistry/pkg/v1/layout/write.go @@ -196,6 +196,7 @@ func (l Path) WriteBlob(hash v1.Hash, r io.ReadCloser) error { } func (l Path) writeBlob(hash v1.Hash, size int64, rc io.ReadCloser, renamer func() (v1.Hash, error)) error { + defer rc.Close() if hash.Hex == "" && renamer == nil { panic("writeBlob called an invalid hash and no renamer") } diff --git a/vendor/github.com/google/go-containerregistry/pkg/v1/mutate/mutate.go b/vendor/github.com/google/go-containerregistry/pkg/v1/mutate/mutate.go index e4a0e5273..1a24b10d7 100644 --- a/vendor/github.com/google/go-containerregistry/pkg/v1/mutate/mutate.go +++ b/vendor/github.com/google/go-containerregistry/pkg/v1/mutate/mutate.go @@ -402,7 +402,9 @@ func Time(img v1.Image, t time.Time) (v1.Image, error) { historyIdx++ break } - addendums[addendumIdx].Layer = newLayer + if addendumIdx < len(addendums) { + addendums[addendumIdx].Layer = newLayer + } } // add all leftover History entries diff --git a/vendor/github.com/google/go-containerregistry/pkg/v1/remote/descriptor.go b/vendor/github.com/google/go-containerregistry/pkg/v1/remote/descriptor.go index 61f28f4c0..fafe910e9 100644 --- a/vendor/github.com/google/go-containerregistry/pkg/v1/remote/descriptor.go +++ b/vendor/github.com/google/go-containerregistry/pkg/v1/remote/descriptor.go @@ -16,6 +16,7 @@ package remote import ( "context" + "errors" "fmt" "github.com/google/go-containerregistry/pkg/logs" @@ -33,20 +34,11 @@ var allManifestMediaTypes = append(append([]types.MediaType{ // ErrSchema1 indicates that we received a schema1 manifest from the registry. // This library doesn't have plans to support this legacy image format: // https://github.com/google/go-containerregistry/issues/377 -type ErrSchema1 struct { - schema string -} +var ErrSchema1 = errors.New("see https://github.com/google/go-containerregistry/issues/377") // newErrSchema1 returns an ErrSchema1 with the unexpected MediaType. func newErrSchema1(schema types.MediaType) error { - return &ErrSchema1{ - schema: string(schema), - } -} - -// Error implements error. -func (e *ErrSchema1) Error() string { - return fmt.Sprintf("unsupported MediaType: %q, see https://github.com/google/go-containerregistry/issues/377", e.schema) + return fmt.Errorf("unsupported MediaType: %q, %w", schema, ErrSchema1) } // Descriptor provides access to metadata about remote artifact and accessors diff --git a/vendor/github.com/google/go-containerregistry/pkg/v1/remote/fetcher.go b/vendor/github.com/google/go-containerregistry/pkg/v1/remote/fetcher.go index b671f836c..4e61002be 100644 --- a/vendor/github.com/google/go-containerregistry/pkg/v1/remote/fetcher.go +++ b/vendor/github.com/google/go-containerregistry/pkg/v1/remote/fetcher.go @@ -32,6 +32,12 @@ import ( "github.com/google/go-containerregistry/pkg/v1/types" ) +const ( + kib = 1024 + mib = 1024 * kib + manifestLimit = 100 * mib +) + // fetcher implements methods for reading from a registry. type fetcher struct { target resource @@ -130,7 +136,7 @@ func (f *fetcher) fetchManifest(ctx context.Context, ref name.Reference, accepta return nil, nil, err } - manifest, err := io.ReadAll(resp.Body) + manifest, err := io.ReadAll(io.LimitReader(resp.Body, manifestLimit)) if err != nil { return nil, nil, err } diff --git a/vendor/github.com/google/go-containerregistry/pkg/v1/remote/options.go b/vendor/github.com/google/go-containerregistry/pkg/v1/remote/options.go index a722c2ca6..99a2bb2eb 100644 --- a/vendor/github.com/google/go-containerregistry/pkg/v1/remote/options.go +++ b/vendor/github.com/google/go-containerregistry/pkg/v1/remote/options.go @@ -96,7 +96,8 @@ var defaultRetryStatusCodes = []int{ http.StatusBadGateway, http.StatusServiceUnavailable, http.StatusGatewayTimeout, - 499, + 499, // nginx-specific, client closed request + 522, // Cloudflare-specific, connection timeout } const ( diff --git a/vendor/github.com/google/go-containerregistry/pkg/v1/remote/transport/bearer.go b/vendor/github.com/google/go-containerregistry/pkg/v1/remote/transport/bearer.go index ea07ff6ab..cb1567496 100644 --- a/vendor/github.com/google/go-containerregistry/pkg/v1/remote/transport/bearer.go +++ b/vendor/github.com/google/go-containerregistry/pkg/v1/remote/transport/bearer.go @@ -32,6 +32,71 @@ import ( "github.com/google/go-containerregistry/pkg/name" ) +type Token struct { + Token string `json:"token"` + AccessToken string `json:"access_token,omitempty"` + RefreshToken string `json:"refresh_token"` + ExpiresIn int `json:"expires_in"` +} + +// Exchange requests a registry Token with the given scopes. +func Exchange(ctx context.Context, reg name.Registry, auth authn.Authenticator, t http.RoundTripper, scopes []string, pr *Challenge) (*Token, error) { + if strings.ToLower(pr.Scheme) != "bearer" { + // TODO: Pretend token for basic? + return nil, fmt.Errorf("challenge scheme %q is not bearer", pr.Scheme) + } + bt, err := fromChallenge(reg, auth, t, pr, scopes...) + if err != nil { + return nil, err + } + authcfg, err := auth.Authorization() + if err != nil { + return nil, err + } + tok, err := bt.Refresh(ctx, authcfg) + if err != nil { + return nil, err + } + return tok, nil +} + +// FromToken returns a transport given a Challenge + Token. +func FromToken(reg name.Registry, auth authn.Authenticator, t http.RoundTripper, pr *Challenge, tok *Token) (http.RoundTripper, error) { + if strings.ToLower(pr.Scheme) != "bearer" { + return &Wrapper{&basicTransport{inner: t, auth: auth, target: reg.RegistryStr()}}, nil + } + bt, err := fromChallenge(reg, auth, t, pr) + if err != nil { + return nil, err + } + if tok.Token != "" { + bt.bearer.RegistryToken = tok.Token + } + return &Wrapper{bt}, nil +} + +func fromChallenge(reg name.Registry, auth authn.Authenticator, t http.RoundTripper, pr *Challenge, scopes ...string) (*bearerTransport, error) { + // We require the realm, which tells us where to send our Basic auth to turn it into Bearer auth. + realm, ok := pr.Parameters["realm"] + if !ok { + return nil, fmt.Errorf("malformed www-authenticate, missing realm: %v", pr.Parameters) + } + service := pr.Parameters["service"] + scheme := "https" + if pr.Insecure { + scheme = "http" + } + return &bearerTransport{ + inner: t, + basic: auth, + realm: realm, + registry: reg, + service: service, + scopes: scopes, + scheme: scheme, + }, nil +} + type bearerTransport struct { // Wrapped by bearerTransport. inner http.RoundTripper @@ -73,7 +138,7 @@ func (bt *bearerTransport) RoundTrip(in *http.Request) (*http.Response, error) { // we are redirected, only set it when the authorization header matches // the registry with which we are interacting. // In case of redirect http.Client can use an empty Host, check URL too. - if matchesHost(bt.registry, in, bt.scheme) { + if matchesHost(bt.registry.RegistryStr(), in, bt.scheme) { hdr := fmt.Sprintf("Bearer %s", bt.bearer.RegistryToken) in.Header.Set("Authorization", hdr) } @@ -135,7 +200,36 @@ func (bt *bearerTransport) refresh(ctx context.Context) error { return nil } - var content []byte + response, err := bt.Refresh(ctx, auth) + if err != nil { + return err + } + + // Some registries set access_token instead of token. See #54. + if response.AccessToken != "" { + response.Token = response.AccessToken + } + + // Find a token to turn into a Bearer authenticator + if response.Token != "" { + bt.bearer.RegistryToken = response.Token + } + + // If we obtained a refresh token from the oauth flow, use that for refresh() now. + if response.RefreshToken != "" { + bt.basic = authn.FromConfig(authn.AuthConfig{ + IdentityToken: response.RefreshToken, + }) + } + + return nil +} + +func (bt *bearerTransport) Refresh(ctx context.Context, auth *authn.AuthConfig) (*Token, error) { + var ( + content []byte + err error + ) if auth.IdentityToken != "" { // If the secret being stored is an identity token, // the Username should be set to , which indicates @@ -152,48 +246,25 @@ func (bt *bearerTransport) refresh(ctx context.Context) error { content, err = bt.refreshBasic(ctx) } if err != nil { - return err + return nil, err } - // Some registries don't have "token" in the response. See #54. - type tokenResponse struct { - Token string `json:"token"` - AccessToken string `json:"access_token"` - RefreshToken string `json:"refresh_token"` - // TODO: handle expiry? - } - - var response tokenResponse + var response Token if err := json.Unmarshal(content, &response); err != nil { - return err + return nil, err } - // Some registries set access_token instead of token. - if response.AccessToken != "" { - response.Token = response.AccessToken + if response.Token == "" && response.AccessToken == "" { + return &response, fmt.Errorf("no token in bearer response:\n%s", content) } - // Find a token to turn into a Bearer authenticator - if response.Token != "" { - bt.bearer.RegistryToken = response.Token - } else { - return fmt.Errorf("no token in bearer response:\n%s", content) - } - - // If we obtained a refresh token from the oauth flow, use that for refresh() now. - if response.RefreshToken != "" { - bt.basic = authn.FromConfig(authn.AuthConfig{ - IdentityToken: response.RefreshToken, - }) - } - - return nil + return &response, nil } -func matchesHost(reg name.Registry, in *http.Request, scheme string) bool { +func matchesHost(host string, in *http.Request, scheme string) bool { canonicalHeaderHost := canonicalAddress(in.Host, scheme) canonicalURLHost := canonicalAddress(in.URL.Host, scheme) - canonicalRegistryHost := canonicalAddress(reg.RegistryStr(), scheme) + canonicalRegistryHost := canonicalAddress(host, scheme) return canonicalHeaderHost == canonicalRegistryHost || canonicalURLHost == canonicalRegistryHost } diff --git a/vendor/github.com/google/go-containerregistry/pkg/v1/remote/transport/ping.go b/vendor/github.com/google/go-containerregistry/pkg/v1/remote/transport/ping.go index d852ef845..799c7ea08 100644 --- a/vendor/github.com/google/go-containerregistry/pkg/v1/remote/transport/ping.go +++ b/vendor/github.com/google/go-containerregistry/pkg/v1/remote/transport/ping.go @@ -28,33 +28,22 @@ import ( "github.com/google/go-containerregistry/pkg/name" ) -type challenge string - -const ( - anonymous challenge = "anonymous" - basic challenge = "basic" - bearer challenge = "bearer" -) - // 300ms is the default fallback period for go's DNS dialer but we could make this configurable. var fallbackDelay = 300 * time.Millisecond -type pingResp struct { - challenge challenge +type Challenge struct { + Scheme string // Following the challenge there are often key/value pairs // e.g. Bearer service="gcr.io",realm="https://auth.gcr.io/v36/tokenz" - parameters map[string]string + Parameters map[string]string - // The registry's scheme to use. Communicates whether we fell back to http. - scheme string + // Whether we had to use http to complete the Ping. + Insecure bool } -func (c challenge) Canonical() challenge { - return challenge(strings.ToLower(string(c))) -} - -func ping(ctx context.Context, reg name.Registry, t http.RoundTripper) (*pingResp, error) { +// Ping does a GET /v2/ against the registry and returns the response. +func Ping(ctx context.Context, reg name.Registry, t http.RoundTripper) (*Challenge, error) { // This first attempts to use "https" for every request, falling back to http // if the registry matches our localhost heuristic or if it is intentionally // set to insecure via name.NewInsecureRegistry. @@ -68,9 +57,9 @@ func ping(ctx context.Context, reg name.Registry, t http.RoundTripper) (*pingRes return pingParallel(ctx, reg, t, schemes) } -func pingSingle(ctx context.Context, reg name.Registry, t http.RoundTripper, scheme string) (*pingResp, error) { +func pingSingle(ctx context.Context, reg name.Registry, t http.RoundTripper, scheme string) (*Challenge, error) { client := http.Client{Transport: t} - url := fmt.Sprintf("%s://%s/v2/", scheme, reg.Name()) + url := fmt.Sprintf("%s://%s/v2/", scheme, reg.RegistryStr()) req, err := http.NewRequest(http.MethodGet, url, nil) if err != nil { return nil, err @@ -86,27 +75,28 @@ func pingSingle(ctx context.Context, reg name.Registry, t http.RoundTripper, sch resp.Body.Close() }() + insecure := scheme == "http" + switch resp.StatusCode { case http.StatusOK: // If we get a 200, then no authentication is needed. - return &pingResp{ - challenge: anonymous, - scheme: scheme, + return &Challenge{ + Insecure: insecure, }, nil case http.StatusUnauthorized: if challenges := authchallenge.ResponseChallenges(resp); len(challenges) != 0 { // If we hit more than one, let's try to find one that we know how to handle. wac := pickFromMultipleChallenges(challenges) - return &pingResp{ - challenge: challenge(wac.Scheme).Canonical(), - parameters: wac.Parameters, - scheme: scheme, + return &Challenge{ + Scheme: wac.Scheme, + Parameters: wac.Parameters, + Insecure: insecure, }, nil } // Otherwise, just return the challenge without parameters. - return &pingResp{ - challenge: challenge(resp.Header.Get("WWW-Authenticate")).Canonical(), - scheme: scheme, + return &Challenge{ + Scheme: resp.Header.Get("WWW-Authenticate"), + Insecure: insecure, }, nil default: return nil, CheckError(resp, http.StatusOK, http.StatusUnauthorized) @@ -114,12 +104,12 @@ func pingSingle(ctx context.Context, reg name.Registry, t http.RoundTripper, sch } // Based on the golang happy eyeballs dialParallel impl in net/dial.go. -func pingParallel(ctx context.Context, reg name.Registry, t http.RoundTripper, schemes []string) (*pingResp, error) { +func pingParallel(ctx context.Context, reg name.Registry, t http.RoundTripper, schemes []string) (*Challenge, error) { returned := make(chan struct{}) defer close(returned) type pingResult struct { - *pingResp + *Challenge error primary bool done bool @@ -130,7 +120,7 @@ func pingParallel(ctx context.Context, reg name.Registry, t http.RoundTripper, s startRacer := func(ctx context.Context, scheme string) { pr, err := pingSingle(ctx, reg, t, scheme) select { - case results <- pingResult{pingResp: pr, error: err, primary: scheme == "https", done: true}: + case results <- pingResult{Challenge: pr, error: err, primary: scheme == "https", done: true}: case <-returned: if pr != nil { logs.Debug.Printf("%s lost race", scheme) @@ -156,7 +146,7 @@ func pingParallel(ctx context.Context, reg name.Registry, t http.RoundTripper, s case res := <-results: if res.error == nil { - return res.pingResp, nil + return res.Challenge, nil } if res.primary { primary = res @@ -164,7 +154,7 @@ func pingParallel(ctx context.Context, reg name.Registry, t http.RoundTripper, s fallback = res } if primary.done && fallback.done { - return nil, multierrs([]error{primary.error, fallback.error}) + return nil, multierrs{primary.error, fallback.error} } if res.primary && fallbackTimer.Stop() { // Primary failed and we haven't started the fallback, diff --git a/vendor/github.com/google/go-containerregistry/pkg/v1/remote/transport/schemer.go b/vendor/github.com/google/go-containerregistry/pkg/v1/remote/transport/schemer.go index d70b6a850..05844db13 100644 --- a/vendor/github.com/google/go-containerregistry/pkg/v1/remote/transport/schemer.go +++ b/vendor/github.com/google/go-containerregistry/pkg/v1/remote/transport/schemer.go @@ -37,7 +37,7 @@ func (st *schemeTransport) RoundTrip(in *http.Request) (*http.Response, error) { // based on which scheme was successful. That is only valid for the // registry server and not e.g. a separate token server or blob storage, // so we should only override the scheme if the host is the registry. - if matchesHost(st.registry, in, st.scheme) { + if matchesHost(st.registry.String(), in, st.scheme) { in.URL.Scheme = st.scheme } return st.inner.RoundTrip(in) diff --git a/vendor/github.com/google/go-containerregistry/pkg/v1/remote/transport/transport.go b/vendor/github.com/google/go-containerregistry/pkg/v1/remote/transport/transport.go index 01fe1fa82..bd539b44f 100644 --- a/vendor/github.com/google/go-containerregistry/pkg/v1/remote/transport/transport.go +++ b/vendor/github.com/google/go-containerregistry/pkg/v1/remote/transport/transport.go @@ -16,8 +16,8 @@ package transport import ( "context" - "fmt" "net/http" + "strings" "github.com/google/go-containerregistry/pkg/authn" "github.com/google/go-containerregistry/pkg/name" @@ -59,7 +59,7 @@ func NewWithContext(ctx context.Context, reg name.Registry, auth authn.Authentic // First we ping the registry to determine the parameters of the authentication handshake // (if one is even necessary). - pr, err := ping(ctx, reg, t) + pr, err := Ping(ctx, reg, t) if err != nil { return nil, err } @@ -69,39 +69,32 @@ func NewWithContext(ctx context.Context, reg name.Registry, auth authn.Authentic t = NewUserAgent(t, "") } + scheme := "https" + if pr.Insecure { + scheme = "http" + } + // Wrap t in a transport that selects the appropriate scheme based on the ping response. t = &schemeTransport{ - scheme: pr.scheme, + scheme: scheme, registry: reg, inner: t, } - switch pr.challenge.Canonical() { - case anonymous, basic: + if strings.ToLower(pr.Scheme) != "bearer" { return &Wrapper{&basicTransport{inner: t, auth: auth, target: reg.RegistryStr()}}, nil - case bearer: - // We require the realm, which tells us where to send our Basic auth to turn it into Bearer auth. - realm, ok := pr.parameters["realm"] - if !ok { - return nil, fmt.Errorf("malformed www-authenticate, missing realm: %v", pr.parameters) - } - service := pr.parameters["service"] - bt := &bearerTransport{ - inner: t, - basic: auth, - realm: realm, - registry: reg, - service: service, - scopes: scopes, - scheme: pr.scheme, - } - if err := bt.refresh(ctx); err != nil { - return nil, err - } - return &Wrapper{bt}, nil - default: - return nil, fmt.Errorf("unrecognized challenge: %s", pr.challenge) } + + bt, err := fromChallenge(reg, auth, t, pr) + if err != nil { + return nil, err + } + bt.scopes = scopes + + if err := bt.refresh(ctx); err != nil { + return nil, err + } + return &Wrapper{bt}, nil } // Wrapper results in *not* wrapping supplied transport with additional logic such as retries, useragent and debug logging diff --git a/vendor/github.com/google/go-containerregistry/pkg/v1/remote/write.go b/vendor/github.com/google/go-containerregistry/pkg/v1/remote/write.go index f4369e2a0..04a3989a6 100644 --- a/vendor/github.com/google/go-containerregistry/pkg/v1/remote/write.go +++ b/vendor/github.com/google/go-containerregistry/pkg/v1/remote/write.go @@ -210,7 +210,7 @@ func (w *writer) initiateUpload(ctx context.Context, from, mount, origin string) req.Header.Set("Content-Type", "application/json") resp, err := w.client.Do(req.WithContext(ctx)) if err != nil { - if origin != "" && origin != w.repo.RegistryStr() { + if from != "" { // https://github.com/google/go-containerregistry/issues/1679 logs.Warn.Printf("retrying without mount: %v", err) return w.initiateUpload(ctx, "", "", "") @@ -220,7 +220,7 @@ func (w *writer) initiateUpload(ctx context.Context, from, mount, origin string) defer resp.Body.Close() if err := transport.CheckError(resp, http.StatusCreated, http.StatusAccepted); err != nil { - if origin != "" && origin != w.repo.RegistryStr() { + if from != "" { // https://github.com/google/go-containerregistry/issues/1404 logs.Warn.Printf("retrying without mount: %v", err) return w.initiateUpload(ctx, "", "", "") @@ -280,6 +280,11 @@ func (w *writer) streamBlob(ctx context.Context, layer v1.Layer, streamLocation if _, ok := layer.(*stream.Layer); !ok { // We can't retry streaming layers. req.GetBody = getBody + + // If we know the size, set it. + if size, err := layer.Size(); err == nil { + req.ContentLength = size + } } req.Header.Set("Content-Type", "application/octet-stream") @@ -360,8 +365,16 @@ func (w *writer) uploadOne(ctx context.Context, l v1.Layer) error { if err := w.maybeUpdateScopes(ctx, ml); err != nil { return err } + from = ml.Reference.Context().RepositoryStr() origin = ml.Reference.Context().RegistryStr() + + // This keeps breaking with DockerHub. + // https://github.com/google/go-containerregistry/issues/1741 + if w.repo.RegistryStr() == name.DefaultRegistry && origin != w.repo.RegistryStr() { + from = "" + origin = "" + } } location, mounted, err := w.initiateUpload(ctx, from, mount, origin) diff --git a/vendor/github.com/google/go-containerregistry/pkg/v1/validate/index.go b/vendor/github.com/google/go-containerregistry/pkg/v1/validate/index.go index 7514dc48a..4d833ae67 100644 --- a/vendor/github.com/google/go-containerregistry/pkg/v1/validate/index.go +++ b/vendor/github.com/google/go-containerregistry/pkg/v1/validate/index.go @@ -79,6 +79,9 @@ func validateChildren(idx v1.ImageIndex, opt ...Option) error { if err := validateMediaType(img, desc.MediaType); err != nil { errs = append(errs, fmt.Sprintf("failed to validate image MediaType[%d](%s): %v", i, desc.Digest, err)) } + if err := validatePlatform(img, desc.Platform); err != nil { + errs = append(errs, fmt.Sprintf("failed to validate image platform[%d](%s): %v", i, desc.Digest, err)) + } default: // Workaround for #819. if wl, ok := idx.(withLayer); ok { @@ -173,3 +176,54 @@ func validateIndexManifest(idx v1.ImageIndex) error { return nil } + +func validatePlatform(img v1.Image, want *v1.Platform) error { + if want == nil { + return nil + } + + cf, err := img.ConfigFile() + if err != nil { + return err + } + + got := cf.Platform() + + if got == nil { + return fmt.Errorf("config file missing platform fields") + } + + if got.Equals(*want) { + return nil + } + + errs := []string{} + + if got.OS != want.OS { + errs = append(errs, fmt.Sprintf("mismatched OS: %s != %s", got.OS, want.OS)) + } + + if got.Architecture != want.Architecture { + errs = append(errs, fmt.Sprintf("mismatched Architecture: %s != %s", got.Architecture, want.Architecture)) + } + + if got.OSVersion != want.OSVersion { + errs = append(errs, fmt.Sprintf("mismatched OSVersion: %s != %s", got.OSVersion, want.OSVersion)) + } + + if got.OSVersion != want.OSVersion { + errs = append(errs, fmt.Sprintf("mismatched OSVersion: %s != %s", got.OSVersion, want.OSVersion)) + } + + if len(errs) == 0 { + // If we got here, some features might be mismatched. Just add those... + if len(got.Features) != 0 || len(want.Features) != 0 { + errs = append(errs, fmt.Sprintf("mismatched Features: %v, %v", got.Features, want.Features)) + } + if len(got.OSFeatures) != 0 || len(want.OSFeatures) != 0 { + errs = append(errs, fmt.Sprintf("mismatched OSFeatures: %v, %v", got.OSFeatures, want.OSFeatures)) + } + } + + return errors.New(strings.Join(errs, "\n")) +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 301259b67..ff8ca1bfa 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -681,7 +681,7 @@ github.com/google/go-cmp/cmp/internal/diff github.com/google/go-cmp/cmp/internal/flags github.com/google/go-cmp/cmp/internal/function github.com/google/go-cmp/cmp/internal/value -# github.com/google/go-containerregistry v0.15.2 +# github.com/google/go-containerregistry v0.17.0 ## explicit; go 1.18 github.com/google/go-containerregistry/internal/and github.com/google/go-containerregistry/internal/compression