chore(deps): bump github.com/google/go-containerregistry (#2924)

Bumps [github.com/google/go-containerregistry](https://github.com/google/go-containerregistry) from 0.15.2 to 0.17.0.
- [Release notes](https://github.com/google/go-containerregistry/releases)
- [Changelog](https://github.com/google/go-containerregistry/blob/main/.goreleaser.yml)
- [Commits](https://github.com/google/go-containerregistry/compare/v0.15.2...v0.17.0)

---
updated-dependencies:
- dependency-name: github.com/google/go-containerregistry
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
This commit is contained in:
dependabot[bot] 2024-01-05 10:41:22 -08:00 committed by GitHub
parent 4636916c77
commit a8afc79f5e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 395 additions and 117 deletions

2
go.mod
View File

@ -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

4
go.sum
View File

@ -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=

View File

@ -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 (

View File

@ -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))

View File

@ -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
}

View File

@ -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")
}

View File

@ -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

View File

@ -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

View File

@ -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
}

View File

@ -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 (

View File

@ -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 <token>, 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
}

View File

@ -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,

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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"))
}

2
vendor/modules.txt vendored
View File

@ -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