Bump github.com/google/go-containerregistry from 0.15.2 to 0.16.1

Bumps [github.com/google/go-containerregistry](https://github.com/google/go-containerregistry) from 0.15.2 to 0.16.1.
- [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.16.1)

---
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>
This commit is contained in:
dependabot[bot]
2023-11-14 16:41:07 -05:00
committed by Jason Hall
parent cfc13deeb6
commit a271b54fe5
37 changed files with 510 additions and 226 deletions
+2 -2
View File
@@ -9,7 +9,7 @@ require (
github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936
github.com/go-training/helloworld v0.0.0-20200225145412-ba5f4379d78b
github.com/google/go-cmp v0.6.0
github.com/google/go-containerregistry v0.15.2
github.com/google/go-containerregistry v0.16.1
github.com/opencontainers/image-spec v1.1.0-rc5
github.com/sigstore/cosign/v2 v2.1.1
github.com/spf13/cobra v1.8.0
@@ -56,7 +56,7 @@ require (
github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect
github.com/dimchansky/utfbom v1.1.1 // indirect
github.com/docker/cli v23.0.5+incompatible // indirect
github.com/docker/cli v24.0.0+incompatible // indirect
github.com/docker/distribution v2.8.2+incompatible // indirect
github.com/docker/docker-credential-helpers v0.7.0 // indirect
github.com/docker/go-connections v0.4.0 // indirect
+4 -4
View File
@@ -152,8 +152,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U=
github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=
github.com/docker/cli v23.0.5+incompatible h1:ufWmAOuD3Vmr7JP2G5K3cyuNC4YZWiAsuDEvFVVDafE=
github.com/docker/cli v23.0.5+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/cli v24.0.0+incompatible h1:0+1VshNwBQzQAx9lOl+OYCTCEAD8fKs/qeXMx3O0wqM=
github.com/docker/cli v24.0.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8=
github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig1seQen0cKYlM=
@@ -312,8 +312,8 @@ github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
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.16.1 h1:rUEt426sR6nyrL3gt+18ibRcvYpKYdpsa5ZW7MA08dQ=
github.com/google/go-containerregistry v0.16.1/go.mod h1:u0qB2l7mvtWVR5kNcbFIhFY1hLbf8eeGapA+vbFDCtQ=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
-1
View File
@@ -37,7 +37,6 @@ type ConfigFile struct {
PruneFilters []string `json:"pruneFilters,omitempty"`
Proxies map[string]ProxyConfig `json:"proxies,omitempty"`
Experimental string `json:"experimental,omitempty"`
StackOrchestrator string `json:"stackOrchestrator,omitempty"` // Deprecated: swarm is now the default orchestrator, and this option is ignored.
CurrentContext string `json:"currentContext,omitempty"`
CLIPluginsExtraDirs []string `json:"cliPluginsExtraDirs,omitempty"`
Plugins map[string]map[string]string `json:"plugins,omitempty"`
+2 -2
View File
@@ -45,7 +45,7 @@ If the base image is a Windows base image (i.e., its config.OS is "windows"),
the contents of the tarballs will be modified to be suitable for a Windows
container image.`,
Args: cobra.NoArgs,
RunE: func(_ *cobra.Command, args []string) error {
RunE: func(cmd *cobra.Command, args []string) error {
var base v1.Image
var err error
@@ -103,7 +103,7 @@ container image.`,
if err != nil {
return fmt.Errorf("digest: %w", err)
}
fmt.Println(ref.Context().Digest(d.String()))
fmt.Fprintln(cmd.OutOrStdout(), ref.Context().Digest(d.String()))
}
return nil
},
+72 -1
View File
@@ -28,6 +28,7 @@ import (
"github.com/google/go-containerregistry/pkg/authn"
"github.com/google/go-containerregistry/pkg/crane"
"github.com/google/go-containerregistry/pkg/name"
"github.com/google/go-containerregistry/pkg/v1/remote/transport"
"github.com/spf13/cobra"
)
@@ -39,7 +40,77 @@ func NewCmdAuth(options []crane.Option, argv ...string) *cobra.Command {
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, _ []string) error { return cmd.Usage() },
}
cmd.AddCommand(NewCmdAuthGet(options, argv...), NewCmdAuthLogin(argv...), NewCmdAuthLogout(argv...))
cmd.AddCommand(NewCmdAuthGet(options, argv...), NewCmdAuthLogin(argv...), NewCmdAuthLogout(argv...), NewCmdAuthToken(options))
return cmd
}
func NewCmdAuthToken(options []crane.Option) *cobra.Command {
var (
header bool
push bool
mounts []string
)
cmd := &cobra.Command{
Use: "token REPO",
Short: "Retrieves a token for a remote repo",
Example: `# If you wanted to mount a blob from debian to ubuntu.
$ curl -H "$(crane auth token -H --push --mount debian ubuntu)" ...
# To get the raw list tags response
$ curl -H "$(crane auth token -H ubuntu)" https://index.docker.io/v2/library/ubuntu/tags/list
`,
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
repo, err := name.NewRepository(args[0])
if err != nil {
return err
}
o := crane.GetOptions(options...)
t := transport.NewLogger(o.Transport)
pr, err := transport.Ping(cmd.Context(), repo.Registry, t)
if err != nil {
return err
}
auth, err := o.Keychain.Resolve(repo)
if err != nil {
return err
}
scopes := []string{repo.Scope(transport.PullScope)}
if push {
scopes[0] = repo.Scope(transport.PushScope)
}
for _, m := range mounts {
mr, err := name.NewRepository(m)
if err != nil {
return err
}
scopes = append(scopes, mr.Scope(transport.PullScope))
}
tr, err := transport.Exchange(cmd.Context(), repo.Registry, auth, t, scopes, pr)
if err != nil {
return err
}
if header {
fmt.Fprintf(cmd.OutOrStdout(), "Authorization: Bearer %s", tr.Token)
return nil
}
if err := json.NewEncoder(os.Stdout).Encode(tr); err != nil {
return err
}
return nil
},
}
cmd.Flags().StringSliceVarP(&mounts, "mount", "m", []string{}, "Scopes to mount from")
cmd.Flags().BoolVarP(&header, "header", "H", false, "Output in header format")
cmd.Flags().BoolVar(&push, "push", false, "Request push scopes")
return cmd
}
+5 -4
View File
@@ -17,6 +17,7 @@ package cmd
import (
"context"
"fmt"
"io"
"path"
"github.com/google/go-containerregistry/pkg/crane"
@@ -35,7 +36,7 @@ func NewCmdCatalog(options *[]crane.Option, _ ...string) *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) error {
o := crane.GetOptions(*options...)
return catalog(cmd.Context(), args[0], fullRef, o)
return catalog(cmd.Context(), cmd.OutOrStdout(), args[0], fullRef, o)
},
}
cmd.Flags().BoolVar(&fullRef, "full-ref", false, "(Optional) if true, print the full image reference")
@@ -43,7 +44,7 @@ func NewCmdCatalog(options *[]crane.Option, _ ...string) *cobra.Command {
return cmd
}
func catalog(ctx context.Context, src string, fullRef bool, o crane.Options) error {
func catalog(ctx context.Context, w io.Writer, src string, fullRef bool, o crane.Options) error {
reg, err := name.NewRegistry(src, o.Name...)
if err != nil {
return fmt.Errorf("parsing reg %q: %w", src, err)
@@ -66,9 +67,9 @@ func catalog(ctx context.Context, src string, fullRef bool, o crane.Options) err
}
for _, repo := range repos.Repos {
if fullRef {
fmt.Println(path.Join(src, repo))
fmt.Fprintln(w, path.Join(src, repo))
} else {
fmt.Println(repo)
fmt.Fprintln(w, repo)
}
}
}
+2 -2
View File
@@ -27,12 +27,12 @@ func NewCmdConfig(options *[]crane.Option) *cobra.Command {
Use: "config IMAGE",
Short: "Get the config of an image",
Args: cobra.ExactArgs(1),
RunE: func(_ *cobra.Command, args []string) error {
RunE: func(cmd *cobra.Command, args []string) error {
cfg, err := crane.Config(args[0], *options...)
if err != nil {
return fmt.Errorf("fetching config: %w", err)
}
fmt.Print(string(cfg))
fmt.Fprint(cmd.OutOrStdout(), string(cfg))
return nil
},
}
+2 -2
View File
@@ -51,9 +51,9 @@ func NewCmdDigest(options *[]crane.Option) *cobra.Command {
if err != nil {
return err
}
fmt.Println(ref.Context().Digest(digest))
fmt.Fprintln(cmd.OutOrStdout(), ref.Context().Digest(digest))
} else {
fmt.Println(digest)
fmt.Fprintln(cmd.OutOrStdout(), digest)
}
return nil
},
+12 -1
View File
@@ -70,10 +70,21 @@ func NewCmdExport(options *[]crane.Option) *cobra.Command {
return fmt.Errorf("reading tarball from stdin: %w", err)
}
} else {
img, err = crane.Pull(src, *options...)
desc, err := crane.Get(src, *options...)
if err != nil {
return fmt.Errorf("pulling %s: %w", src, err)
}
if desc.MediaType.IsSchema1() {
img, err = desc.Schema1()
if err != nil {
return fmt.Errorf("pulling schema 1 image %s: %w", src, err)
}
} else {
img, err = desc.Image()
if err != nil {
return fmt.Errorf("pulling Image %s: %w", src, err)
}
}
}
return crane.Export(img, f)
+1 -1
View File
@@ -83,7 +83,7 @@ func NewCmdFlatten(options *[]crane.Option) *cobra.Command {
if err := push(flat, newRef, o); err != nil {
log.Fatalf("pushing %s: %v", newRef, err)
}
fmt.Println(repo.Digest(digest.String()))
fmt.Fprintln(cmd.OutOrStdout(), repo.Digest(digest.String()))
},
}
flattenCmd.Flags().StringVarP(&dst, "tag", "t", "", "New tag to apply to flattened image. If not provided, push by digest to the original image repository.")
+25 -11
View File
@@ -62,18 +62,25 @@ func NewCmdIndexFilter(options *[]crane.Option) *cobra.Command {
# Same as above, but in-place
crane index filter example.com/hello-world:some-tag --platform linux`,
Args: cobra.ExactArgs(1),
RunE: func(_ *cobra.Command, args []string) error {
RunE: func(cmd *cobra.Command, args []string) error {
o := crane.GetOptions(*options...)
baseRef := args[0]
ref, err := name.ParseReference(baseRef)
ref, err := name.ParseReference(baseRef, o.Name...)
if err != nil {
return err
}
base, err := remote.Index(ref, o.Remote...)
desc, err := remote.Get(ref, o.Remote...)
if err != nil {
return fmt.Errorf("pulling %s: %w", baseRef, err)
}
if !desc.MediaType.IsIndex() {
return fmt.Errorf("expected %s to be an index, got %q", baseRef, desc.MediaType)
}
base, err := desc.ImageIndex()
if err != nil {
return nil
}
idx := filterIndex(base, platforms.platforms)
@@ -83,7 +90,7 @@ func NewCmdIndexFilter(options *[]crane.Option) *cobra.Command {
}
if newTag != "" {
ref, err = name.ParseReference(newTag)
ref, err = name.ParseReference(newTag, o.Name...)
if err != nil {
return fmt.Errorf("parsing reference %s: %w", newTag, err)
}
@@ -96,7 +103,7 @@ func NewCmdIndexFilter(options *[]crane.Option) *cobra.Command {
if err := remote.WriteIndex(ref, idx, o.Remote...); err != nil {
return fmt.Errorf("pushing image %s: %w", newTag, err)
}
fmt.Println(ref.Context().Digest(digest.String()))
fmt.Fprintln(cmd.OutOrStdout(), ref.Context().Digest(digest.String()))
return nil
},
}
@@ -126,7 +133,7 @@ The platform for appended manifests is inferred from the config file or omitted
# Create an index from scratch for etcd.
crane index append -m registry.k8s.io/etcd-amd64:3.4.9 -m registry.k8s.io/etcd-arm64:3.4.9 -t example.com/etcd`,
Args: cobra.MaximumNArgs(1),
RunE: func(_ *cobra.Command, args []string) error {
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 1 {
baseRef = args[0]
}
@@ -149,20 +156,27 @@ The platform for appended manifests is inferred from the config file or omitted
base = mutate.IndexMediaType(base, types.DockerManifestList)
}
} else {
ref, err = name.ParseReference(baseRef)
ref, err = name.ParseReference(baseRef, o.Name...)
if err != nil {
return err
}
base, err = remote.Index(ref, o.Remote...)
desc, err := remote.Get(ref, o.Remote...)
if err != nil {
return fmt.Errorf("pulling %s: %w", baseRef, err)
}
if !desc.MediaType.IsIndex() {
return fmt.Errorf("expected %s to be an index, got %q", baseRef, desc.MediaType)
}
base, err = desc.ImageIndex()
if err != nil {
return err
}
}
adds := make([]mutate.IndexAddendum, 0, len(newManifests))
for _, m := range newManifests {
ref, err := name.ParseReference(m)
ref, err := name.ParseReference(m, o.Name...)
if err != nil {
return err
}
@@ -240,7 +254,7 @@ The platform for appended manifests is inferred from the config file or omitted
}
if newTag != "" {
ref, err = name.ParseReference(newTag)
ref, err = name.ParseReference(newTag, o.Name...)
if err != nil {
return fmt.Errorf("parsing reference %s: %w", newTag, err)
}
@@ -253,7 +267,7 @@ The platform for appended manifests is inferred from the config file or omitted
if err := remote.WriteIndex(ref, idx, o.Remote...); err != nil {
return fmt.Errorf("pushing image %s: %w", newTag, err)
}
fmt.Println(ref.Context().Digest(digest.String()))
fmt.Fprintln(cmd.OutOrStdout(), ref.Context().Digest(digest.String()))
return nil
},
}
+5 -4
View File
@@ -17,6 +17,7 @@ package cmd
import (
"context"
"fmt"
"io"
"strings"
"github.com/google/go-containerregistry/pkg/crane"
@@ -35,7 +36,7 @@ func NewCmdList(options *[]crane.Option) *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) error {
o := crane.GetOptions(*options...)
return list(cmd.Context(), args[0], fullRef, omitDigestTags, o)
return list(cmd.Context(), cmd.OutOrStdout(), args[0], fullRef, omitDigestTags, o)
},
}
cmd.Flags().BoolVar(&fullRef, "full-ref", false, "(Optional) if true, print the full image reference")
@@ -43,7 +44,7 @@ func NewCmdList(options *[]crane.Option) *cobra.Command {
return cmd
}
func list(ctx context.Context, src string, fullRef, omitDigestTags bool, o crane.Options) error {
func list(ctx context.Context, w io.Writer, src string, fullRef, omitDigestTags bool, o crane.Options) error {
repo, err := name.NewRepository(src, o.Name...)
if err != nil {
return fmt.Errorf("parsing repo %q: %w", src, err)
@@ -70,9 +71,9 @@ func list(ctx context.Context, src string, fullRef, omitDigestTags bool, o crane
}
if fullRef {
fmt.Println(repo.Tag(tag))
fmt.Fprintln(w, repo.Tag(tag))
} else {
fmt.Println(tag)
fmt.Fprintln(w, tag)
}
}
}
+2 -2
View File
@@ -27,13 +27,13 @@ func NewCmdManifest(options *[]crane.Option) *cobra.Command {
Use: "manifest IMAGE",
Short: "Get the manifest of an image",
Args: cobra.ExactArgs(1),
RunE: func(_ *cobra.Command, args []string) error {
RunE: func(cmd *cobra.Command, args []string) error {
src := args[0]
manifest, err := crane.Manifest(src, *options...)
if err != nil {
return fmt.Errorf("fetching manifest %s: %w", src, err)
}
fmt.Print(string(manifest))
fmt.Fprint(cmd.OutOrStdout(), string(manifest))
return nil
},
}
+2 -2
View File
@@ -44,7 +44,7 @@ func NewCmdMutate(options *[]crane.Option) *cobra.Command {
Use: "mutate",
Short: "Modify image labels and annotations. The container must be pushed to a registry, and the manifest is updated there.",
Args: cobra.ExactArgs(1),
RunE: func(_ *cobra.Command, args []string) error {
RunE: func(c *cobra.Command, args []string) error {
// Pull image and get config.
ref := args[0]
@@ -166,7 +166,7 @@ func NewCmdMutate(options *[]crane.Option) *cobra.Command {
if err := crane.Push(img, newRef, *options...); err != nil {
return fmt.Errorf("pushing %s: %w", newRef, err)
}
fmt.Println(r.Context().Digest(digest.String()))
fmt.Fprintln(c.OutOrStdout(), r.Context().Digest(digest.String()))
}
return nil
},
+2 -2
View File
@@ -36,7 +36,7 @@ func NewCmdPush(options *[]crane.Option) *cobra.Command {
Short: "Push local image contents to a remote registry",
Long: `If the PATH is a directory, it will be read as an OCI image layout. Otherwise, PATH is assumed to be a docker-style tarball.`,
Args: cobra.ExactArgs(2),
RunE: func(_ *cobra.Command, args []string) error {
RunE: func(cmd *cobra.Command, args []string) error {
path, tag := args[0], args[1]
img, err := loadImage(path, index)
@@ -75,7 +75,7 @@ func NewCmdPush(options *[]crane.Option) *cobra.Command {
}
// Print the digest of the pushed image to stdout to facilitate command composition.
fmt.Println(digest)
fmt.Fprintln(cmd.OutOrStdout(), digest)
return nil
},
+1 -1
View File
@@ -123,7 +123,7 @@ func NewCmdRebase(options *[]crane.Option) *cobra.Command {
log.Fatalf("pushing %s: %v", rebased, err)
}
fmt.Println(r.Context().Digest(rebasedDigest.String()))
fmt.Fprintln(cmd.OutOrStdout(), r.Context().Digest(rebasedDigest.String()))
return nil
},
}
+15 -4
View File
@@ -37,10 +37,11 @@ func newCmdRegistry() *cobra.Command {
}
func newCmdServe() *cobra.Command {
return &cobra.Command{
var disk bool
cmd := &cobra.Command{
Use: "serve",
Short: "Serve an in-memory registry implementation",
Long: `This sub-command serves an in-memory registry implementation on port :8080 (or $PORT)
Long: `This sub-command serves an in-memory registry implementation on an automatically chosen port (or $PORT)
The command blocks while the server accepts pushes and pulls.
@@ -53,16 +54,23 @@ Contents are only stored in memory, and when the process exits, pushed data is l
if port == "" {
port = "0"
}
listener, err := net.Listen("tcp", "localhost:"+port)
listener, err := net.Listen("tcp", ":"+port)
if err != nil {
log.Fatalln(err)
}
porti := listener.Addr().(*net.TCPAddr).Port
port = fmt.Sprintf("%d", porti)
bh := registry.NewInMemoryBlobHandler()
if disk {
tmp := os.TempDir()
log.Printf("storing blobs in %s", tmp)
bh = registry.NewDiskBlobHandler(tmp)
}
s := &http.Server{
ReadHeaderTimeout: 5 * time.Second, // prevent slowloris, quiet linter
Handler: registry.New(),
Handler: registry.New(registry.WithBlobHandler(bh)),
}
log.Printf("serving on port %s", port)
@@ -81,4 +89,7 @@ Contents are only stored in memory, and when the process exits, pushed data is l
return nil
},
}
cmd.Flags().BoolVar(&disk, "blobs-to-disk", false, "Store blobs on disk")
cmd.Flags().MarkHidden("blobs-to-disk")
return cmd
}
+3 -3
View File
@@ -35,7 +35,7 @@ func NewCmdValidate(options *[]crane.Option) *cobra.Command {
Use: "validate",
Short: "Validate that an image is well-formed",
Args: cobra.ExactArgs(0),
RunE: func(_ *cobra.Command, args []string) error {
RunE: func(cmd *cobra.Command, args []string) error {
for flag, maker := range map[string]func(string, ...crane.Option) (v1.Image, error){
tarballPath: makeTarball,
remoteRef: crane.Pull,
@@ -53,10 +53,10 @@ func NewCmdValidate(options *[]crane.Option) *cobra.Command {
opt = append(opt, validate.Fast)
}
if err := validate.Image(img, opt...); err != nil {
fmt.Printf("FAIL: %s: %v\n", flag, err)
fmt.Fprintf(cmd.OutOrStdout(), "FAIL: %s: %v\n", flag, err)
return err
}
fmt.Printf("PASS: %s\n", flag)
fmt.Fprintf(cmd.OutOrStdout(), "PASS: %s\n", flag)
}
return nil
},
+3 -3
View File
@@ -45,11 +45,11 @@ func NewCmdVersion() *cobra.Command {
This could be an arbitrary string, if specified via -ldflags.
This could also be the go module version, if built with go modules (often "(devel)").`,
Args: cobra.NoArgs,
Run: func(_ *cobra.Command, _ []string) {
Run: func(cmd *cobra.Command, _ []string) {
if Version == "" {
fmt.Println("could not determine build information")
fmt.Fprintln(cmd.OutOrStdout(), "could not determine build information")
} else {
fmt.Println(Version)
fmt.Fprintln(cmd.OutOrStdout(), Version)
}
},
}
+5
View File
@@ -44,6 +44,11 @@ func getManifest(r string, opt ...Option) (*remote.Descriptor, error) {
return remote.Get(ref, o.Remote...)
}
// Get calls remote.Get and returns an uninterpreted response.
func Get(r string, opt ...Option) (*remote.Descriptor, error) {
return getManifest(r, opt...)
}
// Head performs a HEAD request for a manifest and returns a content descriptor
// based on the registry's response.
func Head(r string, opt ...Option) (*v1.Descriptor, error) {
+9 -7
View File
@@ -27,13 +27,13 @@ import (
// Options hold the options that crane uses when calling other packages.
type Options struct {
Name []name.Option
Remote []remote.Option
Platform *v1.Platform
Keychain authn.Keychain
Name []name.Option
Remote []remote.Option
Platform *v1.Platform
Keychain authn.Keychain
Transport http.RoundTripper
auth authn.Authenticator
transport http.RoundTripper
insecure bool
jobs int
noclobber bool
@@ -64,13 +64,15 @@ func makeOptions(opts ...Option) Options {
// Allow for untrusted certificates if the user
// passed Insecure but no custom transport.
if opt.insecure && opt.transport == nil {
if opt.insecure && opt.Transport == nil {
transport := remote.DefaultTransport.(*http.Transport).Clone()
transport.TLSClientConfig = &tls.Config{
InsecureSkipVerify: true, //nolint: gosec
}
WithTransport(transport)(&opt)
} else if opt.Transport == nil {
opt.Transport = remote.DefaultTransport
}
return opt
@@ -85,7 +87,7 @@ type Option func(*Options)
func WithTransport(t http.RoundTripper) Option {
return func(o *Options) {
o.Remote = append(o.Remote, remote.WithTransport(t))
o.transport = t
o.Transport = t
}
}
+16 -14
View File
@@ -48,24 +48,24 @@ func isBlob(req *http.Request) bool {
elem[len(elem)-2] == "uploads")
}
// blobHandler represents a minimal blob storage backend, capable of serving
// BlobHandler represents a minimal blob storage backend, capable of serving
// blob contents.
type blobHandler interface {
type BlobHandler interface {
// Get gets the blob contents, or errNotFound if the blob wasn't found.
Get(ctx context.Context, repo string, h v1.Hash) (io.ReadCloser, error)
}
// blobStatHandler is an extension interface representing a blob storage
// BlobStatHandler is an extension interface representing a blob storage
// backend that can serve metadata about blobs.
type blobStatHandler interface {
type BlobStatHandler interface {
// Stat returns the size of the blob, or errNotFound if the blob wasn't
// found, or redirectError if the blob can be found elsewhere.
Stat(ctx context.Context, repo string, h v1.Hash) (int64, error)
}
// blobPutHandler is an extension interface representing a blob storage backend
// BlobPutHandler is an extension interface representing a blob storage backend
// that can write blob contents.
type blobPutHandler interface {
type BlobPutHandler interface {
// Put puts the blob contents.
//
// The contents will be verified against the expected size and digest
@@ -75,9 +75,9 @@ type blobPutHandler interface {
Put(ctx context.Context, repo string, h v1.Hash, rc io.ReadCloser) error
}
// blobDeleteHandler is an extension interface representing a blob storage
// BlobDeleteHandler is an extension interface representing a blob storage
// backend that can delete blob contents.
type blobDeleteHandler interface {
type BlobDeleteHandler interface {
// Delete the blob contents.
Delete(ctx context.Context, repo string, h v1.Hash) error
}
@@ -103,6 +103,8 @@ type memHandler struct {
lock sync.Mutex
}
func NewInMemoryBlobHandler() BlobHandler { return &memHandler{m: map[string][]byte{}} }
func (m *memHandler) Stat(_ context.Context, _ string, h v1.Hash) (int64, error) {
m.lock.Lock()
defer m.lock.Unlock()
@@ -149,7 +151,7 @@ func (m *memHandler) Delete(_ context.Context, _ string, h v1.Hash) error {
// blobs
type blobs struct {
blobHandler blobHandler
blobHandler BlobHandler
// Each upload gets a unique id that writes occur to until finalized.
uploads map[string][]byte
@@ -190,7 +192,7 @@ func (b *blobs) handle(resp http.ResponseWriter, req *http.Request) *regError {
}
var size int64
if bsh, ok := b.blobHandler.(blobStatHandler); ok {
if bsh, ok := b.blobHandler.(BlobStatHandler); ok {
size, err = bsh.Stat(req.Context(), repo, h)
if errors.Is(err, errNotFound) {
return regErrBlobUnknown
@@ -238,7 +240,7 @@ func (b *blobs) handle(resp http.ResponseWriter, req *http.Request) *regError {
var size int64
var r io.Reader
if bsh, ok := b.blobHandler.(blobStatHandler); ok {
if bsh, ok := b.blobHandler.(BlobStatHandler); ok {
size, err = bsh.Stat(req.Context(), repo, h)
if errors.Is(err, errNotFound) {
return regErrBlobUnknown
@@ -292,7 +294,7 @@ func (b *blobs) handle(resp http.ResponseWriter, req *http.Request) *regError {
return nil
case http.MethodPost:
bph, ok := b.blobHandler.(blobPutHandler)
bph, ok := b.blobHandler.(BlobPutHandler)
if !ok {
return regErrUnsupported
}
@@ -393,7 +395,7 @@ func (b *blobs) handle(resp http.ResponseWriter, req *http.Request) *regError {
return nil
case http.MethodPut:
bph, ok := b.blobHandler.(blobPutHandler)
bph, ok := b.blobHandler.(BlobPutHandler)
if !ok {
return regErrUnsupported
}
@@ -454,7 +456,7 @@ func (b *blobs) handle(resp http.ResponseWriter, req *http.Request) *regError {
return nil
case http.MethodDelete:
bdh, ok := b.blobHandler.(blobDeleteHandler)
bdh, ok := b.blobHandler.(BlobDeleteHandler)
if !ok {
return regErrUnsupported
}
@@ -0,0 +1,65 @@
// Copyright 2023 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.
package registry
import (
"context"
"errors"
"io"
"os"
"path/filepath"
v1 "github.com/google/go-containerregistry/pkg/v1"
)
type diskHandler struct {
dir string
}
func NewDiskBlobHandler(dir string) BlobHandler { return &diskHandler{dir: dir} }
func (m *diskHandler) Stat(_ context.Context, _ string, h v1.Hash) (int64, error) {
fi, err := os.Stat(filepath.Join(m.dir, h.String()))
if errors.Is(err, os.ErrNotExist) {
return 0, errNotFound
} else if err != nil {
return 0, err
}
return fi.Size(), nil
}
func (m *diskHandler) Get(_ context.Context, _ string, h v1.Hash) (io.ReadCloser, error) {
return os.Open(filepath.Join(m.dir, h.String()))
}
func (m *diskHandler) Put(_ context.Context, _ string, h v1.Hash, rc io.ReadCloser) error {
// Put the temp file in the same directory to avoid cross-device problems
// during the os.Rename. The filenames cannot conflict.
f, err := os.CreateTemp(m.dir, "upload-*")
if err != nil {
return err
}
if err := func() error {
defer f.Close()
_, err := io.Copy(f, rc)
return err
}(); err != nil {
return err
}
return os.Rename(f.Name(), filepath.Join(m.dir, h.String()))
}
func (m *diskHandler) Delete(_ context.Context, _ string, h v1.Hash) error {
return os.Remove(filepath.Join(m.dir, h.String()))
}
+53 -39
View File
@@ -47,7 +47,7 @@ type manifest struct {
type manifests struct {
// maps repo -> manifest tag/digest -> manifest
manifests map[string]map[string]manifest
lock sync.Mutex
lock sync.RWMutex
log *log.Logger
}
@@ -99,8 +99,8 @@ func (m *manifests) handle(resp http.ResponseWriter, req *http.Request) *regErro
switch req.Method {
case http.MethodGet:
m.lock.Lock()
defer m.lock.Unlock()
m.lock.RLock()
defer m.lock.RUnlock()
c, ok := m.manifests[repo]
if !ok {
@@ -118,6 +118,7 @@ func (m *manifests) handle(resp http.ResponseWriter, req *http.Request) *regErro
Message: "Unknown manifest",
}
}
h, _, _ := v1.SHA256(bytes.NewReader(m.blob))
resp.Header().Set("Docker-Content-Digest", h.String())
resp.Header().Set("Content-Type", m.contentType)
@@ -127,8 +128,9 @@ func (m *manifests) handle(resp http.ResponseWriter, req *http.Request) *regErro
return nil
case http.MethodHead:
m.lock.Lock()
defer m.lock.Unlock()
m.lock.RLock()
defer m.lock.RUnlock()
if _, ok := m.manifests[repo]; !ok {
return &regError{
Status: http.StatusNotFound,
@@ -144,6 +146,7 @@ func (m *manifests) handle(resp http.ResponseWriter, req *http.Request) *regErro
Message: "Unknown manifest",
}
}
h, _, _ := v1.SHA256(bytes.NewReader(m.blob))
resp.Header().Set("Docker-Content-Digest", h.String())
resp.Header().Set("Content-Type", m.contentType)
@@ -152,11 +155,6 @@ func (m *manifests) handle(resp http.ResponseWriter, req *http.Request) *regErro
return nil
case http.MethodPut:
m.lock.Lock()
defer m.lock.Unlock()
if _, ok := m.manifests[repo]; !ok {
m.manifests[repo] = map[string]manifest{}
}
b := &bytes.Buffer{}
io.Copy(b, req.Body)
h, _, _ := v1.SHA256(bytes.NewReader(b.Bytes()))
@@ -171,37 +169,52 @@ func (m *manifests) handle(resp http.ResponseWriter, req *http.Request) *regErro
// This isn't strictly required by the registry API, but some
// registries require this.
if types.MediaType(mf.contentType).IsIndex() {
im, err := v1.ParseIndexManifest(b)
if err != nil {
return &regError{
Status: http.StatusBadRequest,
Code: "MANIFEST_INVALID",
Message: err.Error(),
}
}
for _, desc := range im.Manifests {
if !desc.MediaType.IsDistributable() {
continue
}
if desc.MediaType.IsIndex() || desc.MediaType.IsImage() {
if _, found := m.manifests[repo][desc.Digest.String()]; !found {
return &regError{
Status: http.StatusNotFound,
Code: "MANIFEST_UNKNOWN",
Message: fmt.Sprintf("Sub-manifest %q not found", desc.Digest),
}
if err := func() *regError {
m.lock.RLock()
defer m.lock.RUnlock()
im, err := v1.ParseIndexManifest(b)
if err != nil {
return &regError{
Status: http.StatusBadRequest,
Code: "MANIFEST_INVALID",
Message: err.Error(),
}
} else {
// TODO: Probably want to do an existence check for blobs.
m.log.Printf("TODO: Check blobs for %q", desc.Digest)
}
for _, desc := range im.Manifests {
if !desc.MediaType.IsDistributable() {
continue
}
if desc.MediaType.IsIndex() || desc.MediaType.IsImage() {
if _, found := m.manifests[repo][desc.Digest.String()]; !found {
return &regError{
Status: http.StatusNotFound,
Code: "MANIFEST_UNKNOWN",
Message: fmt.Sprintf("Sub-manifest %q not found", desc.Digest),
}
}
} else {
// TODO: Probably want to do an existence check for blobs.
m.log.Printf("TODO: Check blobs for %q", desc.Digest)
}
}
return nil
}(); err != nil {
return err
}
}
m.lock.Lock()
defer m.lock.Unlock()
if _, ok := m.manifests[repo]; !ok {
m.manifests[repo] = make(map[string]manifest, 2)
}
// Allow future references by target (tag) and immutable digest.
// See https://docs.docker.com/engine/reference/commandline/pull/#pull-an-image-by-digest-immutable-identifier.
m.manifests[repo][target] = mf
m.manifests[repo][digest] = mf
m.manifests[repo][target] = mf
resp.Header().Set("Docker-Content-Digest", digest)
resp.WriteHeader(http.StatusCreated)
return nil
@@ -245,8 +258,8 @@ func (m *manifests) handleTags(resp http.ResponseWriter, req *http.Request) *reg
repo := strings.Join(elem[1:len(elem)-2], "/")
if req.Method == "GET" {
m.lock.Lock()
defer m.lock.Unlock()
m.lock.RLock()
defer m.lock.RUnlock()
c, ok := m.manifests[repo]
if !ok {
@@ -317,8 +330,8 @@ func (m *manifests) handleCatalog(resp http.ResponseWriter, req *http.Request) *
}
if req.Method == "GET" {
m.lock.Lock()
defer m.lock.Unlock()
m.lock.RLock()
defer m.lock.RUnlock()
var repos []string
countRepos := 0
@@ -375,8 +388,8 @@ func (m *manifests) handleReferrers(resp http.ResponseWriter, req *http.Request)
}
}
m.lock.Lock()
defer m.lock.Unlock()
m.lock.RLock()
defer m.lock.RUnlock()
digestToManifestMap, repoExists := m.manifests[repo]
if !repoExists {
@@ -424,6 +437,7 @@ func (m *manifests) handleReferrers(resp http.ResponseWriter, req *http.Request)
}
msg, _ := json.Marshal(&im)
resp.Header().Set("Content-Length", fmt.Sprint(len(msg)))
resp.Header().Set("Content-Type", string(types.OCIImageIndex))
resp.WriteHeader(http.StatusOK)
io.Copy(resp, bytes.NewReader([]byte(msg)))
return nil
@@ -136,3 +136,9 @@ func WithWarning(prob float64, msg string) Option {
r.warnings[prob] = msg
}
}
func WithBlobHandler(h BlobHandler) Option {
return func(r *registry) {
r.blobs.blobHandler = h
}
}
+18
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))
+1
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")
}
+3 -1
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
+3 -11
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
+7 -1
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
}
+2 -1
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 (
@@ -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
}
@@ -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,
@@ -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)
@@ -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
+10 -2
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, "", "", "")
@@ -360,8 +360,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)
+2 -2
View File
@@ -168,7 +168,7 @@ github.com/cpuguy83/go-md2man/v2/md2man
# github.com/dimchansky/utfbom v1.1.1
## explicit
github.com/dimchansky/utfbom
# github.com/docker/cli v23.0.5+incompatible
# github.com/docker/cli v24.0.0+incompatible
## explicit
github.com/docker/cli/cli/config
github.com/docker/cli/cli/config/configfile
@@ -286,7 +286,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.16.1
## explicit; go 1.18
github.com/google/go-containerregistry/cmd/crane/cmd
github.com/google/go-containerregistry/internal/and