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:
parent
4636916c77
commit
a8afc79f5e
2
go.mod
2
go.mod
|
|
@ -22,7 +22,7 @@ require (
|
||||||
github.com/go-git/go-git/v5 v5.11.0
|
github.com/go-git/go-git/v5 v5.11.0
|
||||||
github.com/golang/mock v1.6.0
|
github.com/golang/mock v1.6.0
|
||||||
github.com/google/go-cmp v0.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/go-github v17.0.0+incompatible
|
||||||
github.com/google/slowjam v1.1.0
|
github.com/google/slowjam v1.1.0
|
||||||
github.com/karrick/godirwalk v1.16.1
|
github.com/karrick/godirwalk v1.16.1
|
||||||
|
|
|
||||||
4
go.sum
4
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.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 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
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.17.0 h1:5p+zYs/R4VGHkhyvgWurWrpJ2hW4Vv9fQI+GzdcwXLk=
|
||||||
github.com/google/go-containerregistry v0.15.2/go.mod h1:wWK+LnOv4jXMM23IT/F1wdYftGWGr47Is8CG+pmHK1Q=
|
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 h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY=
|
||||||
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
|
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=
|
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ type defaultKeychain struct {
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// DefaultKeychain implements Keychain by interpreting the docker config file.
|
// DefaultKeychain implements Keychain by interpreting the docker config file.
|
||||||
DefaultKeychain = RefreshingKeychain(&defaultKeychain{}, 5*time.Minute)
|
DefaultKeychain = &defaultKeychain{}
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,24 @@ func Write(tag name.Tag, img v1.Image, options ...Option) (string, error) {
|
||||||
return "", err
|
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()
|
pr, pw := io.Pipe()
|
||||||
go func() {
|
go func() {
|
||||||
pw.CloseWithError(tarball.Write(tag, img, pw))
|
pw.CloseWithError(tarball.Write(tag, img, pw))
|
||||||
|
|
|
||||||
137
vendor/github.com/google/go-containerregistry/pkg/v1/layout/gc.go
generated
vendored
Normal file
137
vendor/github.com/google/go-containerregistry/pkg/v1/layout/gc.go
generated
vendored
Normal 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
|
||||||
|
}
|
||||||
|
|
@ -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 {
|
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 {
|
if hash.Hex == "" && renamer == nil {
|
||||||
panic("writeBlob called an invalid hash and no renamer")
|
panic("writeBlob called an invalid hash and no renamer")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -402,7 +402,9 @@ func Time(img v1.Image, t time.Time) (v1.Image, error) {
|
||||||
historyIdx++
|
historyIdx++
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
addendums[addendumIdx].Layer = newLayer
|
if addendumIdx < len(addendums) {
|
||||||
|
addendums[addendumIdx].Layer = newLayer
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// add all leftover History entries
|
// add all leftover History entries
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ package remote
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/google/go-containerregistry/pkg/logs"
|
"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.
|
// ErrSchema1 indicates that we received a schema1 manifest from the registry.
|
||||||
// This library doesn't have plans to support this legacy image format:
|
// This library doesn't have plans to support this legacy image format:
|
||||||
// https://github.com/google/go-containerregistry/issues/377
|
// https://github.com/google/go-containerregistry/issues/377
|
||||||
type ErrSchema1 struct {
|
var ErrSchema1 = errors.New("see https://github.com/google/go-containerregistry/issues/377")
|
||||||
schema string
|
|
||||||
}
|
|
||||||
|
|
||||||
// newErrSchema1 returns an ErrSchema1 with the unexpected MediaType.
|
// newErrSchema1 returns an ErrSchema1 with the unexpected MediaType.
|
||||||
func newErrSchema1(schema types.MediaType) error {
|
func newErrSchema1(schema types.MediaType) error {
|
||||||
return &ErrSchema1{
|
return fmt.Errorf("unsupported MediaType: %q, %w", schema, 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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Descriptor provides access to metadata about remote artifact and accessors
|
// Descriptor provides access to metadata about remote artifact and accessors
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,12 @@ import (
|
||||||
"github.com/google/go-containerregistry/pkg/v1/types"
|
"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.
|
// fetcher implements methods for reading from a registry.
|
||||||
type fetcher struct {
|
type fetcher struct {
|
||||||
target resource
|
target resource
|
||||||
|
|
@ -130,7 +136,7 @@ func (f *fetcher) fetchManifest(ctx context.Context, ref name.Reference, accepta
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
manifest, err := io.ReadAll(resp.Body)
|
manifest, err := io.ReadAll(io.LimitReader(resp.Body, manifestLimit))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,8 @@ var defaultRetryStatusCodes = []int{
|
||||||
http.StatusBadGateway,
|
http.StatusBadGateway,
|
||||||
http.StatusServiceUnavailable,
|
http.StatusServiceUnavailable,
|
||||||
http.StatusGatewayTimeout,
|
http.StatusGatewayTimeout,
|
||||||
499,
|
499, // nginx-specific, client closed request
|
||||||
|
522, // Cloudflare-specific, connection timeout
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
|
||||||
137
vendor/github.com/google/go-containerregistry/pkg/v1/remote/transport/bearer.go
generated
vendored
137
vendor/github.com/google/go-containerregistry/pkg/v1/remote/transport/bearer.go
generated
vendored
|
|
@ -32,6 +32,71 @@ import (
|
||||||
"github.com/google/go-containerregistry/pkg/name"
|
"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 {
|
type bearerTransport struct {
|
||||||
// Wrapped by bearerTransport.
|
// Wrapped by bearerTransport.
|
||||||
inner http.RoundTripper
|
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
|
// we are redirected, only set it when the authorization header matches
|
||||||
// the registry with which we are interacting.
|
// the registry with which we are interacting.
|
||||||
// In case of redirect http.Client can use an empty Host, check URL too.
|
// 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)
|
hdr := fmt.Sprintf("Bearer %s", bt.bearer.RegistryToken)
|
||||||
in.Header.Set("Authorization", hdr)
|
in.Header.Set("Authorization", hdr)
|
||||||
}
|
}
|
||||||
|
|
@ -135,7 +200,36 @@ func (bt *bearerTransport) refresh(ctx context.Context) error {
|
||||||
return nil
|
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 auth.IdentityToken != "" {
|
||||||
// If the secret being stored is an identity token,
|
// If the secret being stored is an identity token,
|
||||||
// the Username should be set to <token>, which indicates
|
// 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)
|
content, err = bt.refreshBasic(ctx)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Some registries don't have "token" in the response. See #54.
|
var response Token
|
||||||
type tokenResponse struct {
|
|
||||||
Token string `json:"token"`
|
|
||||||
AccessToken string `json:"access_token"`
|
|
||||||
RefreshToken string `json:"refresh_token"`
|
|
||||||
// TODO: handle expiry?
|
|
||||||
}
|
|
||||||
|
|
||||||
var response tokenResponse
|
|
||||||
if err := json.Unmarshal(content, &response); err != nil {
|
if err := json.Unmarshal(content, &response); err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Some registries set access_token instead of token.
|
if response.Token == "" && response.AccessToken == "" {
|
||||||
if response.AccessToken != "" {
|
return &response, fmt.Errorf("no token in bearer response:\n%s", content)
|
||||||
response.Token = response.AccessToken
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find a token to turn into a Bearer authenticator
|
return &response, nil
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
canonicalHeaderHost := canonicalAddress(in.Host, scheme)
|
||||||
canonicalURLHost := canonicalAddress(in.URL.Host, scheme)
|
canonicalURLHost := canonicalAddress(in.URL.Host, scheme)
|
||||||
canonicalRegistryHost := canonicalAddress(reg.RegistryStr(), scheme)
|
canonicalRegistryHost := canonicalAddress(host, scheme)
|
||||||
return canonicalHeaderHost == canonicalRegistryHost || canonicalURLHost == canonicalRegistryHost
|
return canonicalHeaderHost == canonicalRegistryHost || canonicalURLHost == canonicalRegistryHost
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,33 +28,22 @@ import (
|
||||||
"github.com/google/go-containerregistry/pkg/name"
|
"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.
|
// 300ms is the default fallback period for go's DNS dialer but we could make this configurable.
|
||||||
var fallbackDelay = 300 * time.Millisecond
|
var fallbackDelay = 300 * time.Millisecond
|
||||||
|
|
||||||
type pingResp struct {
|
type Challenge struct {
|
||||||
challenge challenge
|
Scheme string
|
||||||
|
|
||||||
// Following the challenge there are often key/value pairs
|
// Following the challenge there are often key/value pairs
|
||||||
// e.g. Bearer service="gcr.io",realm="https://auth.gcr.io/v36/tokenz"
|
// 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.
|
// Whether we had to use http to complete the Ping.
|
||||||
scheme string
|
Insecure bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c challenge) Canonical() challenge {
|
// Ping does a GET /v2/ against the registry and returns the response.
|
||||||
return challenge(strings.ToLower(string(c)))
|
func Ping(ctx context.Context, reg name.Registry, t http.RoundTripper) (*Challenge, error) {
|
||||||
}
|
|
||||||
|
|
||||||
func ping(ctx context.Context, reg name.Registry, t http.RoundTripper) (*pingResp, error) {
|
|
||||||
// This first attempts to use "https" for every request, falling back to http
|
// 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
|
// if the registry matches our localhost heuristic or if it is intentionally
|
||||||
// set to insecure via name.NewInsecureRegistry.
|
// 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)
|
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}
|
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)
|
req, err := http.NewRequest(http.MethodGet, url, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -86,27 +75,28 @@ func pingSingle(ctx context.Context, reg name.Registry, t http.RoundTripper, sch
|
||||||
resp.Body.Close()
|
resp.Body.Close()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
insecure := scheme == "http"
|
||||||
|
|
||||||
switch resp.StatusCode {
|
switch resp.StatusCode {
|
||||||
case http.StatusOK:
|
case http.StatusOK:
|
||||||
// If we get a 200, then no authentication is needed.
|
// If we get a 200, then no authentication is needed.
|
||||||
return &pingResp{
|
return &Challenge{
|
||||||
challenge: anonymous,
|
Insecure: insecure,
|
||||||
scheme: scheme,
|
|
||||||
}, nil
|
}, nil
|
||||||
case http.StatusUnauthorized:
|
case http.StatusUnauthorized:
|
||||||
if challenges := authchallenge.ResponseChallenges(resp); len(challenges) != 0 {
|
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.
|
// If we hit more than one, let's try to find one that we know how to handle.
|
||||||
wac := pickFromMultipleChallenges(challenges)
|
wac := pickFromMultipleChallenges(challenges)
|
||||||
return &pingResp{
|
return &Challenge{
|
||||||
challenge: challenge(wac.Scheme).Canonical(),
|
Scheme: wac.Scheme,
|
||||||
parameters: wac.Parameters,
|
Parameters: wac.Parameters,
|
||||||
scheme: scheme,
|
Insecure: insecure,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
// Otherwise, just return the challenge without parameters.
|
// Otherwise, just return the challenge without parameters.
|
||||||
return &pingResp{
|
return &Challenge{
|
||||||
challenge: challenge(resp.Header.Get("WWW-Authenticate")).Canonical(),
|
Scheme: resp.Header.Get("WWW-Authenticate"),
|
||||||
scheme: scheme,
|
Insecure: insecure,
|
||||||
}, nil
|
}, nil
|
||||||
default:
|
default:
|
||||||
return nil, CheckError(resp, http.StatusOK, http.StatusUnauthorized)
|
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.
|
// 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{})
|
returned := make(chan struct{})
|
||||||
defer close(returned)
|
defer close(returned)
|
||||||
|
|
||||||
type pingResult struct {
|
type pingResult struct {
|
||||||
*pingResp
|
*Challenge
|
||||||
error
|
error
|
||||||
primary bool
|
primary bool
|
||||||
done 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) {
|
startRacer := func(ctx context.Context, scheme string) {
|
||||||
pr, err := pingSingle(ctx, reg, t, scheme)
|
pr, err := pingSingle(ctx, reg, t, scheme)
|
||||||
select {
|
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:
|
case <-returned:
|
||||||
if pr != nil {
|
if pr != nil {
|
||||||
logs.Debug.Printf("%s lost race", scheme)
|
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:
|
case res := <-results:
|
||||||
if res.error == nil {
|
if res.error == nil {
|
||||||
return res.pingResp, nil
|
return res.Challenge, nil
|
||||||
}
|
}
|
||||||
if res.primary {
|
if res.primary {
|
||||||
primary = res
|
primary = res
|
||||||
|
|
@ -164,7 +154,7 @@ func pingParallel(ctx context.Context, reg name.Registry, t http.RoundTripper, s
|
||||||
fallback = res
|
fallback = res
|
||||||
}
|
}
|
||||||
if primary.done && fallback.done {
|
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() {
|
if res.primary && fallbackTimer.Stop() {
|
||||||
// Primary failed and we haven't started the fallback,
|
// Primary failed and we haven't started the fallback,
|
||||||
|
|
|
||||||
2
vendor/github.com/google/go-containerregistry/pkg/v1/remote/transport/schemer.go
generated
vendored
2
vendor/github.com/google/go-containerregistry/pkg/v1/remote/transport/schemer.go
generated
vendored
|
|
@ -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
|
// 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,
|
// 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.
|
// 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
|
in.URL.Scheme = st.scheme
|
||||||
}
|
}
|
||||||
return st.inner.RoundTrip(in)
|
return st.inner.RoundTrip(in)
|
||||||
|
|
|
||||||
47
vendor/github.com/google/go-containerregistry/pkg/v1/remote/transport/transport.go
generated
vendored
47
vendor/github.com/google/go-containerregistry/pkg/v1/remote/transport/transport.go
generated
vendored
|
|
@ -16,8 +16,8 @@ package transport
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/google/go-containerregistry/pkg/authn"
|
"github.com/google/go-containerregistry/pkg/authn"
|
||||||
"github.com/google/go-containerregistry/pkg/name"
|
"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
|
// First we ping the registry to determine the parameters of the authentication handshake
|
||||||
// (if one is even necessary).
|
// (if one is even necessary).
|
||||||
pr, err := ping(ctx, reg, t)
|
pr, err := Ping(ctx, reg, t)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -69,39 +69,32 @@ func NewWithContext(ctx context.Context, reg name.Registry, auth authn.Authentic
|
||||||
t = NewUserAgent(t, "")
|
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.
|
// Wrap t in a transport that selects the appropriate scheme based on the ping response.
|
||||||
t = &schemeTransport{
|
t = &schemeTransport{
|
||||||
scheme: pr.scheme,
|
scheme: scheme,
|
||||||
registry: reg,
|
registry: reg,
|
||||||
inner: t,
|
inner: t,
|
||||||
}
|
}
|
||||||
|
|
||||||
switch pr.challenge.Canonical() {
|
if strings.ToLower(pr.Scheme) != "bearer" {
|
||||||
case anonymous, basic:
|
|
||||||
return &Wrapper{&basicTransport{inner: t, auth: auth, target: reg.RegistryStr()}}, nil
|
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
|
// Wrapper results in *not* wrapping supplied transport with additional logic such as retries, useragent and debug logging
|
||||||
|
|
|
||||||
|
|
@ -210,7 +210,7 @@ func (w *writer) initiateUpload(ctx context.Context, from, mount, origin string)
|
||||||
req.Header.Set("Content-Type", "application/json")
|
req.Header.Set("Content-Type", "application/json")
|
||||||
resp, err := w.client.Do(req.WithContext(ctx))
|
resp, err := w.client.Do(req.WithContext(ctx))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if origin != "" && origin != w.repo.RegistryStr() {
|
if from != "" {
|
||||||
// https://github.com/google/go-containerregistry/issues/1679
|
// https://github.com/google/go-containerregistry/issues/1679
|
||||||
logs.Warn.Printf("retrying without mount: %v", err)
|
logs.Warn.Printf("retrying without mount: %v", err)
|
||||||
return w.initiateUpload(ctx, "", "", "")
|
return w.initiateUpload(ctx, "", "", "")
|
||||||
|
|
@ -220,7 +220,7 @@ func (w *writer) initiateUpload(ctx context.Context, from, mount, origin string)
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
if err := transport.CheckError(resp, http.StatusCreated, http.StatusAccepted); err != nil {
|
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
|
// https://github.com/google/go-containerregistry/issues/1404
|
||||||
logs.Warn.Printf("retrying without mount: %v", err)
|
logs.Warn.Printf("retrying without mount: %v", err)
|
||||||
return w.initiateUpload(ctx, "", "", "")
|
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 {
|
if _, ok := layer.(*stream.Layer); !ok {
|
||||||
// We can't retry streaming layers.
|
// We can't retry streaming layers.
|
||||||
req.GetBody = getBody
|
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")
|
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 {
|
if err := w.maybeUpdateScopes(ctx, ml); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
from = ml.Reference.Context().RepositoryStr()
|
from = ml.Reference.Context().RepositoryStr()
|
||||||
origin = ml.Reference.Context().RegistryStr()
|
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)
|
location, mounted, err := w.initiateUpload(ctx, from, mount, origin)
|
||||||
|
|
|
||||||
|
|
@ -79,6 +79,9 @@ func validateChildren(idx v1.ImageIndex, opt ...Option) error {
|
||||||
if err := validateMediaType(img, desc.MediaType); err != nil {
|
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))
|
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:
|
default:
|
||||||
// Workaround for #819.
|
// Workaround for #819.
|
||||||
if wl, ok := idx.(withLayer); ok {
|
if wl, ok := idx.(withLayer); ok {
|
||||||
|
|
@ -173,3 +176,54 @@ func validateIndexManifest(idx v1.ImageIndex) error {
|
||||||
|
|
||||||
return nil
|
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"))
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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/flags
|
||||||
github.com/google/go-cmp/cmp/internal/function
|
github.com/google/go-cmp/cmp/internal/function
|
||||||
github.com/google/go-cmp/cmp/internal/value
|
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
|
## explicit; go 1.18
|
||||||
github.com/google/go-containerregistry/internal/and
|
github.com/google/go-containerregistry/internal/and
|
||||||
github.com/google/go-containerregistry/internal/compression
|
github.com/google/go-containerregistry/internal/compression
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue