diff --git a/go.mod b/go.mod index d83ebf4033..41196d256f 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index e3d64ba3bb..8df1bfe046 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/vendor/github.com/docker/cli/cli/config/configfile/file.go b/vendor/github.com/docker/cli/cli/config/configfile/file.go index 609a88c278..5db7f8b827 100644 --- a/vendor/github.com/docker/cli/cli/config/configfile/file.go +++ b/vendor/github.com/docker/cli/cli/config/configfile/file.go @@ -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"` diff --git a/vendor/github.com/google/go-containerregistry/cmd/crane/cmd/append.go b/vendor/github.com/google/go-containerregistry/cmd/crane/cmd/append.go index 9eb2acc122..3555a7864e 100644 --- a/vendor/github.com/google/go-containerregistry/cmd/crane/cmd/append.go +++ b/vendor/github.com/google/go-containerregistry/cmd/crane/cmd/append.go @@ -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 }, diff --git a/vendor/github.com/google/go-containerregistry/cmd/crane/cmd/auth.go b/vendor/github.com/google/go-containerregistry/cmd/crane/cmd/auth.go index 5586e822ce..a01e6235e9 100644 --- a/vendor/github.com/google/go-containerregistry/cmd/crane/cmd/auth.go +++ b/vendor/github.com/google/go-containerregistry/cmd/crane/cmd/auth.go @@ -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 } diff --git a/vendor/github.com/google/go-containerregistry/cmd/crane/cmd/catalog.go b/vendor/github.com/google/go-containerregistry/cmd/crane/cmd/catalog.go index e7dc914680..f17941d2df 100644 --- a/vendor/github.com/google/go-containerregistry/cmd/crane/cmd/catalog.go +++ b/vendor/github.com/google/go-containerregistry/cmd/crane/cmd/catalog.go @@ -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) } } } diff --git a/vendor/github.com/google/go-containerregistry/cmd/crane/cmd/config.go b/vendor/github.com/google/go-containerregistry/cmd/crane/cmd/config.go index ed2a3fb148..bb45b51394 100644 --- a/vendor/github.com/google/go-containerregistry/cmd/crane/cmd/config.go +++ b/vendor/github.com/google/go-containerregistry/cmd/crane/cmd/config.go @@ -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 }, } diff --git a/vendor/github.com/google/go-containerregistry/cmd/crane/cmd/digest.go b/vendor/github.com/google/go-containerregistry/cmd/crane/cmd/digest.go index 2060bbc35b..c0a4fe1228 100644 --- a/vendor/github.com/google/go-containerregistry/cmd/crane/cmd/digest.go +++ b/vendor/github.com/google/go-containerregistry/cmd/crane/cmd/digest.go @@ -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 }, diff --git a/vendor/github.com/google/go-containerregistry/cmd/crane/cmd/export.go b/vendor/github.com/google/go-containerregistry/cmd/crane/cmd/export.go index 70b58c134a..497eb8e097 100644 --- a/vendor/github.com/google/go-containerregistry/cmd/crane/cmd/export.go +++ b/vendor/github.com/google/go-containerregistry/cmd/crane/cmd/export.go @@ -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) diff --git a/vendor/github.com/google/go-containerregistry/cmd/crane/cmd/flatten.go b/vendor/github.com/google/go-containerregistry/cmd/crane/cmd/flatten.go index f25de05d85..ea642e7917 100644 --- a/vendor/github.com/google/go-containerregistry/cmd/crane/cmd/flatten.go +++ b/vendor/github.com/google/go-containerregistry/cmd/crane/cmd/flatten.go @@ -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.") diff --git a/vendor/github.com/google/go-containerregistry/cmd/crane/cmd/index.go b/vendor/github.com/google/go-containerregistry/cmd/crane/cmd/index.go index 8d4b425dab..64e0984091 100644 --- a/vendor/github.com/google/go-containerregistry/cmd/crane/cmd/index.go +++ b/vendor/github.com/google/go-containerregistry/cmd/crane/cmd/index.go @@ -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 }, } diff --git a/vendor/github.com/google/go-containerregistry/cmd/crane/cmd/list.go b/vendor/github.com/google/go-containerregistry/cmd/crane/cmd/list.go index eade12fc0f..76d71a3dc3 100644 --- a/vendor/github.com/google/go-containerregistry/cmd/crane/cmd/list.go +++ b/vendor/github.com/google/go-containerregistry/cmd/crane/cmd/list.go @@ -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) } } } diff --git a/vendor/github.com/google/go-containerregistry/cmd/crane/cmd/manifest.go b/vendor/github.com/google/go-containerregistry/cmd/crane/cmd/manifest.go index d9ef7fd8fc..45510bc1d2 100644 --- a/vendor/github.com/google/go-containerregistry/cmd/crane/cmd/manifest.go +++ b/vendor/github.com/google/go-containerregistry/cmd/crane/cmd/manifest.go @@ -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 }, } diff --git a/vendor/github.com/google/go-containerregistry/cmd/crane/cmd/mutate.go b/vendor/github.com/google/go-containerregistry/cmd/crane/cmd/mutate.go index 9f7a3eaf1c..db484e5c78 100644 --- a/vendor/github.com/google/go-containerregistry/cmd/crane/cmd/mutate.go +++ b/vendor/github.com/google/go-containerregistry/cmd/crane/cmd/mutate.go @@ -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 }, diff --git a/vendor/github.com/google/go-containerregistry/cmd/crane/cmd/push.go b/vendor/github.com/google/go-containerregistry/cmd/crane/cmd/push.go index 111d536b85..cfd9f98b13 100644 --- a/vendor/github.com/google/go-containerregistry/cmd/crane/cmd/push.go +++ b/vendor/github.com/google/go-containerregistry/cmd/crane/cmd/push.go @@ -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 }, diff --git a/vendor/github.com/google/go-containerregistry/cmd/crane/cmd/rebase.go b/vendor/github.com/google/go-containerregistry/cmd/crane/cmd/rebase.go index 43f21b5336..5545452899 100644 --- a/vendor/github.com/google/go-containerregistry/cmd/crane/cmd/rebase.go +++ b/vendor/github.com/google/go-containerregistry/cmd/crane/cmd/rebase.go @@ -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 }, } diff --git a/vendor/github.com/google/go-containerregistry/cmd/crane/cmd/serve.go b/vendor/github.com/google/go-containerregistry/cmd/crane/cmd/serve.go index 5b11153ebf..61d031b393 100644 --- a/vendor/github.com/google/go-containerregistry/cmd/crane/cmd/serve.go +++ b/vendor/github.com/google/go-containerregistry/cmd/crane/cmd/serve.go @@ -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 } diff --git a/vendor/github.com/google/go-containerregistry/cmd/crane/cmd/validate.go b/vendor/github.com/google/go-containerregistry/cmd/crane/cmd/validate.go index 4a4acbd95a..d5736bd253 100644 --- a/vendor/github.com/google/go-containerregistry/cmd/crane/cmd/validate.go +++ b/vendor/github.com/google/go-containerregistry/cmd/crane/cmd/validate.go @@ -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 }, diff --git a/vendor/github.com/google/go-containerregistry/cmd/crane/cmd/version.go b/vendor/github.com/google/go-containerregistry/cmd/crane/cmd/version.go index b906a5de47..e15bff1618 100644 --- a/vendor/github.com/google/go-containerregistry/cmd/crane/cmd/version.go +++ b/vendor/github.com/google/go-containerregistry/cmd/crane/cmd/version.go @@ -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) } }, } diff --git a/vendor/github.com/google/go-containerregistry/pkg/crane/get.go b/vendor/github.com/google/go-containerregistry/pkg/crane/get.go index 1f12f01d0d..98a2e8933e 100644 --- a/vendor/github.com/google/go-containerregistry/pkg/crane/get.go +++ b/vendor/github.com/google/go-containerregistry/pkg/crane/get.go @@ -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) { diff --git a/vendor/github.com/google/go-containerregistry/pkg/crane/options.go b/vendor/github.com/google/go-containerregistry/pkg/crane/options.go index e3b7e238f0..d9d4417619 100644 --- a/vendor/github.com/google/go-containerregistry/pkg/crane/options.go +++ b/vendor/github.com/google/go-containerregistry/pkg/crane/options.go @@ -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 } } diff --git a/vendor/github.com/google/go-containerregistry/pkg/registry/blobs.go b/vendor/github.com/google/go-containerregistry/pkg/registry/blobs.go index 4bf2c65e50..8386ffdf9e 100644 --- a/vendor/github.com/google/go-containerregistry/pkg/registry/blobs.go +++ b/vendor/github.com/google/go-containerregistry/pkg/registry/blobs.go @@ -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 } diff --git a/vendor/github.com/google/go-containerregistry/pkg/registry/blobs_disk.go b/vendor/github.com/google/go-containerregistry/pkg/registry/blobs_disk.go new file mode 100644 index 0000000000..dc86bec351 --- /dev/null +++ b/vendor/github.com/google/go-containerregistry/pkg/registry/blobs_disk.go @@ -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())) +} diff --git a/vendor/github.com/google/go-containerregistry/pkg/registry/manifest.go b/vendor/github.com/google/go-containerregistry/pkg/registry/manifest.go index cd788f7baf..db8a8dc690 100644 --- a/vendor/github.com/google/go-containerregistry/pkg/registry/manifest.go +++ b/vendor/github.com/google/go-containerregistry/pkg/registry/manifest.go @@ -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 ®Error{ 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 ®Error{ - Status: http.StatusBadRequest, - Code: "MANIFEST_INVALID", - Message: err.Error(), - } - } - for _, desc := range im.Manifests { - if !desc.MediaType.IsDistributable() { - continue + if err := func() *regError { + m.lock.RLock() + defer m.lock.RUnlock() + + im, err := v1.ParseIndexManifest(b) + if err != nil { + return ®Error{ + Status: http.StatusBadRequest, + Code: "MANIFEST_INVALID", + Message: err.Error(), + } } - if desc.MediaType.IsIndex() || desc.MediaType.IsImage() { - if _, found := m.manifests[repo][desc.Digest.String()]; !found { - return ®Error{ - Status: http.StatusNotFound, - Code: "MANIFEST_UNKNOWN", - Message: fmt.Sprintf("Sub-manifest %q not found", 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 ®Error{ + 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) } - } 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 diff --git a/vendor/github.com/google/go-containerregistry/pkg/registry/registry.go b/vendor/github.com/google/go-containerregistry/pkg/registry/registry.go index a98d77a93e..2f8fd1127b 100644 --- a/vendor/github.com/google/go-containerregistry/pkg/registry/registry.go +++ b/vendor/github.com/google/go-containerregistry/pkg/registry/registry.go @@ -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 + } +} diff --git a/vendor/github.com/google/go-containerregistry/pkg/v1/daemon/write.go b/vendor/github.com/google/go-containerregistry/pkg/v1/daemon/write.go index 48186f6623..3ca5b52dd2 100644 --- a/vendor/github.com/google/go-containerregistry/pkg/v1/daemon/write.go +++ b/vendor/github.com/google/go-containerregistry/pkg/v1/daemon/write.go @@ -40,6 +40,24 @@ func Write(tag name.Tag, img v1.Image, options ...Option) (string, error) { return "", err } + // If we already have this image by this image ID, we can skip loading it. + id, err := img.ConfigName() + if err != nil { + return "", fmt.Errorf("computing image ID: %w", err) + } + if resp, _, err := o.client.ImageInspectWithRaw(o.ctx, id.String()); err == nil { + want := tag.String() + + // If we already have this tag, we can skip tagging it. + for _, have := range resp.RepoTags { + if have == want { + return "", nil + } + } + + return "", o.client.ImageTag(o.ctx, id.String(), want) + } + pr, pw := io.Pipe() go func() { pw.CloseWithError(tarball.Write(tag, img, pw)) diff --git a/vendor/github.com/google/go-containerregistry/pkg/v1/layout/write.go b/vendor/github.com/google/go-containerregistry/pkg/v1/layout/write.go index 906b12aeca..d6e35c3914 100644 --- a/vendor/github.com/google/go-containerregistry/pkg/v1/layout/write.go +++ b/vendor/github.com/google/go-containerregistry/pkg/v1/layout/write.go @@ -196,6 +196,7 @@ func (l Path) WriteBlob(hash v1.Hash, r io.ReadCloser) error { } func (l Path) writeBlob(hash v1.Hash, size int64, rc io.ReadCloser, renamer func() (v1.Hash, error)) error { + defer rc.Close() if hash.Hex == "" && renamer == nil { panic("writeBlob called an invalid hash and no renamer") } diff --git a/vendor/github.com/google/go-containerregistry/pkg/v1/mutate/mutate.go b/vendor/github.com/google/go-containerregistry/pkg/v1/mutate/mutate.go index e4a0e5273e..1a24b10d76 100644 --- a/vendor/github.com/google/go-containerregistry/pkg/v1/mutate/mutate.go +++ b/vendor/github.com/google/go-containerregistry/pkg/v1/mutate/mutate.go @@ -402,7 +402,9 @@ func Time(img v1.Image, t time.Time) (v1.Image, error) { historyIdx++ break } - addendums[addendumIdx].Layer = newLayer + if addendumIdx < len(addendums) { + addendums[addendumIdx].Layer = newLayer + } } // add all leftover History entries diff --git a/vendor/github.com/google/go-containerregistry/pkg/v1/remote/descriptor.go b/vendor/github.com/google/go-containerregistry/pkg/v1/remote/descriptor.go index 61f28f4c04..fafe910e95 100644 --- a/vendor/github.com/google/go-containerregistry/pkg/v1/remote/descriptor.go +++ b/vendor/github.com/google/go-containerregistry/pkg/v1/remote/descriptor.go @@ -16,6 +16,7 @@ package remote import ( "context" + "errors" "fmt" "github.com/google/go-containerregistry/pkg/logs" @@ -33,20 +34,11 @@ var allManifestMediaTypes = append(append([]types.MediaType{ // ErrSchema1 indicates that we received a schema1 manifest from the registry. // This library doesn't have plans to support this legacy image format: // https://github.com/google/go-containerregistry/issues/377 -type ErrSchema1 struct { - schema string -} +var ErrSchema1 = errors.New("see https://github.com/google/go-containerregistry/issues/377") // newErrSchema1 returns an ErrSchema1 with the unexpected MediaType. func newErrSchema1(schema types.MediaType) error { - return &ErrSchema1{ - schema: string(schema), - } -} - -// Error implements error. -func (e *ErrSchema1) Error() string { - return fmt.Sprintf("unsupported MediaType: %q, see https://github.com/google/go-containerregistry/issues/377", e.schema) + return fmt.Errorf("unsupported MediaType: %q, %w", schema, ErrSchema1) } // Descriptor provides access to metadata about remote artifact and accessors diff --git a/vendor/github.com/google/go-containerregistry/pkg/v1/remote/fetcher.go b/vendor/github.com/google/go-containerregistry/pkg/v1/remote/fetcher.go index b671f836c9..4e61002bea 100644 --- a/vendor/github.com/google/go-containerregistry/pkg/v1/remote/fetcher.go +++ b/vendor/github.com/google/go-containerregistry/pkg/v1/remote/fetcher.go @@ -32,6 +32,12 @@ import ( "github.com/google/go-containerregistry/pkg/v1/types" ) +const ( + kib = 1024 + mib = 1024 * kib + manifestLimit = 100 * mib +) + // fetcher implements methods for reading from a registry. type fetcher struct { target resource @@ -130,7 +136,7 @@ func (f *fetcher) fetchManifest(ctx context.Context, ref name.Reference, accepta return nil, nil, err } - manifest, err := io.ReadAll(resp.Body) + manifest, err := io.ReadAll(io.LimitReader(resp.Body, manifestLimit)) if err != nil { return nil, nil, err } diff --git a/vendor/github.com/google/go-containerregistry/pkg/v1/remote/options.go b/vendor/github.com/google/go-containerregistry/pkg/v1/remote/options.go index a722c2ca62..99a2bb2eb2 100644 --- a/vendor/github.com/google/go-containerregistry/pkg/v1/remote/options.go +++ b/vendor/github.com/google/go-containerregistry/pkg/v1/remote/options.go @@ -96,7 +96,8 @@ var defaultRetryStatusCodes = []int{ http.StatusBadGateway, http.StatusServiceUnavailable, http.StatusGatewayTimeout, - 499, + 499, // nginx-specific, client closed request + 522, // Cloudflare-specific, connection timeout } const ( diff --git a/vendor/github.com/google/go-containerregistry/pkg/v1/remote/transport/bearer.go b/vendor/github.com/google/go-containerregistry/pkg/v1/remote/transport/bearer.go index ea07ff6abb..cb15674969 100644 --- a/vendor/github.com/google/go-containerregistry/pkg/v1/remote/transport/bearer.go +++ b/vendor/github.com/google/go-containerregistry/pkg/v1/remote/transport/bearer.go @@ -32,6 +32,71 @@ import ( "github.com/google/go-containerregistry/pkg/name" ) +type Token struct { + Token string `json:"token"` + AccessToken string `json:"access_token,omitempty"` + RefreshToken string `json:"refresh_token"` + ExpiresIn int `json:"expires_in"` +} + +// Exchange requests a registry Token with the given scopes. +func Exchange(ctx context.Context, reg name.Registry, auth authn.Authenticator, t http.RoundTripper, scopes []string, pr *Challenge) (*Token, error) { + if strings.ToLower(pr.Scheme) != "bearer" { + // TODO: Pretend token for basic? + return nil, fmt.Errorf("challenge scheme %q is not bearer", pr.Scheme) + } + bt, err := fromChallenge(reg, auth, t, pr, scopes...) + if err != nil { + return nil, err + } + authcfg, err := auth.Authorization() + if err != nil { + return nil, err + } + tok, err := bt.Refresh(ctx, authcfg) + if err != nil { + return nil, err + } + return tok, nil +} + +// FromToken returns a transport given a Challenge + Token. +func FromToken(reg name.Registry, auth authn.Authenticator, t http.RoundTripper, pr *Challenge, tok *Token) (http.RoundTripper, error) { + if strings.ToLower(pr.Scheme) != "bearer" { + return &Wrapper{&basicTransport{inner: t, auth: auth, target: reg.RegistryStr()}}, nil + } + bt, err := fromChallenge(reg, auth, t, pr) + if err != nil { + return nil, err + } + if tok.Token != "" { + bt.bearer.RegistryToken = tok.Token + } + return &Wrapper{bt}, nil +} + +func fromChallenge(reg name.Registry, auth authn.Authenticator, t http.RoundTripper, pr *Challenge, scopes ...string) (*bearerTransport, error) { + // We require the realm, which tells us where to send our Basic auth to turn it into Bearer auth. + realm, ok := pr.Parameters["realm"] + if !ok { + return nil, fmt.Errorf("malformed www-authenticate, missing realm: %v", pr.Parameters) + } + service := pr.Parameters["service"] + scheme := "https" + if pr.Insecure { + scheme = "http" + } + return &bearerTransport{ + inner: t, + basic: auth, + realm: realm, + registry: reg, + service: service, + scopes: scopes, + scheme: scheme, + }, nil +} + type bearerTransport struct { // Wrapped by bearerTransport. inner http.RoundTripper @@ -73,7 +138,7 @@ func (bt *bearerTransport) RoundTrip(in *http.Request) (*http.Response, error) { // we are redirected, only set it when the authorization header matches // the registry with which we are interacting. // In case of redirect http.Client can use an empty Host, check URL too. - if matchesHost(bt.registry, in, bt.scheme) { + if matchesHost(bt.registry.RegistryStr(), in, bt.scheme) { hdr := fmt.Sprintf("Bearer %s", bt.bearer.RegistryToken) in.Header.Set("Authorization", hdr) } @@ -135,7 +200,36 @@ func (bt *bearerTransport) refresh(ctx context.Context) error { return nil } - var content []byte + response, err := bt.Refresh(ctx, auth) + if err != nil { + return err + } + + // Some registries set access_token instead of token. See #54. + if response.AccessToken != "" { + response.Token = response.AccessToken + } + + // Find a token to turn into a Bearer authenticator + if response.Token != "" { + bt.bearer.RegistryToken = response.Token + } + + // If we obtained a refresh token from the oauth flow, use that for refresh() now. + if response.RefreshToken != "" { + bt.basic = authn.FromConfig(authn.AuthConfig{ + IdentityToken: response.RefreshToken, + }) + } + + return nil +} + +func (bt *bearerTransport) Refresh(ctx context.Context, auth *authn.AuthConfig) (*Token, error) { + var ( + content []byte + err error + ) if auth.IdentityToken != "" { // If the secret being stored is an identity token, // the Username should be set to , which indicates @@ -152,48 +246,25 @@ func (bt *bearerTransport) refresh(ctx context.Context) error { content, err = bt.refreshBasic(ctx) } if err != nil { - return err - } - - // 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? + return nil, err } - var response tokenResponse + var response Token if err := json.Unmarshal(content, &response); err != nil { - return err - } - - // Some registries set access_token instead of token. - if response.AccessToken != "" { - response.Token = response.AccessToken - } - - // 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) + return nil, err } - // 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, - }) + if response.Token == "" && response.AccessToken == "" { + return &response, fmt.Errorf("no token in bearer response:\n%s", content) } - return nil + return &response, nil } -func matchesHost(reg name.Registry, in *http.Request, scheme string) bool { +func matchesHost(host string, in *http.Request, scheme string) bool { canonicalHeaderHost := canonicalAddress(in.Host, scheme) canonicalURLHost := canonicalAddress(in.URL.Host, scheme) - canonicalRegistryHost := canonicalAddress(reg.RegistryStr(), scheme) + canonicalRegistryHost := canonicalAddress(host, scheme) return canonicalHeaderHost == canonicalRegistryHost || canonicalURLHost == canonicalRegistryHost } diff --git a/vendor/github.com/google/go-containerregistry/pkg/v1/remote/transport/ping.go b/vendor/github.com/google/go-containerregistry/pkg/v1/remote/transport/ping.go index d852ef8455..799c7ea08b 100644 --- a/vendor/github.com/google/go-containerregistry/pkg/v1/remote/transport/ping.go +++ b/vendor/github.com/google/go-containerregistry/pkg/v1/remote/transport/ping.go @@ -28,33 +28,22 @@ import ( "github.com/google/go-containerregistry/pkg/name" ) -type challenge string - -const ( - anonymous challenge = "anonymous" - basic challenge = "basic" - bearer challenge = "bearer" -) - // 300ms is the default fallback period for go's DNS dialer but we could make this configurable. var fallbackDelay = 300 * time.Millisecond -type pingResp struct { - challenge challenge +type Challenge struct { + Scheme string // Following the challenge there are often key/value pairs // e.g. Bearer service="gcr.io",realm="https://auth.gcr.io/v36/tokenz" - parameters map[string]string + Parameters map[string]string - // The registry's scheme to use. Communicates whether we fell back to http. - scheme string + // Whether we had to use http to complete the Ping. + Insecure bool } -func (c challenge) Canonical() challenge { - return challenge(strings.ToLower(string(c))) -} - -func ping(ctx context.Context, reg name.Registry, t http.RoundTripper) (*pingResp, error) { +// Ping does a GET /v2/ against the registry and returns the response. +func Ping(ctx context.Context, reg name.Registry, t http.RoundTripper) (*Challenge, error) { // This first attempts to use "https" for every request, falling back to http // if the registry matches our localhost heuristic or if it is intentionally // set to insecure via name.NewInsecureRegistry. @@ -68,9 +57,9 @@ func ping(ctx context.Context, reg name.Registry, t http.RoundTripper) (*pingRes return pingParallel(ctx, reg, t, schemes) } -func pingSingle(ctx context.Context, reg name.Registry, t http.RoundTripper, scheme string) (*pingResp, error) { +func pingSingle(ctx context.Context, reg name.Registry, t http.RoundTripper, scheme string) (*Challenge, error) { client := http.Client{Transport: t} - url := fmt.Sprintf("%s://%s/v2/", scheme, reg.Name()) + url := fmt.Sprintf("%s://%s/v2/", scheme, reg.RegistryStr()) req, err := http.NewRequest(http.MethodGet, url, nil) if err != nil { return nil, err @@ -86,27 +75,28 @@ func pingSingle(ctx context.Context, reg name.Registry, t http.RoundTripper, sch resp.Body.Close() }() + insecure := scheme == "http" + switch resp.StatusCode { case http.StatusOK: // If we get a 200, then no authentication is needed. - return &pingResp{ - challenge: anonymous, - scheme: scheme, + return &Challenge{ + Insecure: insecure, }, nil case http.StatusUnauthorized: if challenges := authchallenge.ResponseChallenges(resp); len(challenges) != 0 { // If we hit more than one, let's try to find one that we know how to handle. wac := pickFromMultipleChallenges(challenges) - return &pingResp{ - challenge: challenge(wac.Scheme).Canonical(), - parameters: wac.Parameters, - scheme: scheme, + return &Challenge{ + Scheme: wac.Scheme, + Parameters: wac.Parameters, + Insecure: insecure, }, nil } // Otherwise, just return the challenge without parameters. - return &pingResp{ - challenge: challenge(resp.Header.Get("WWW-Authenticate")).Canonical(), - scheme: scheme, + return &Challenge{ + Scheme: resp.Header.Get("WWW-Authenticate"), + Insecure: insecure, }, nil default: return nil, CheckError(resp, http.StatusOK, http.StatusUnauthorized) @@ -114,12 +104,12 @@ func pingSingle(ctx context.Context, reg name.Registry, t http.RoundTripper, sch } // Based on the golang happy eyeballs dialParallel impl in net/dial.go. -func pingParallel(ctx context.Context, reg name.Registry, t http.RoundTripper, schemes []string) (*pingResp, error) { +func pingParallel(ctx context.Context, reg name.Registry, t http.RoundTripper, schemes []string) (*Challenge, error) { returned := make(chan struct{}) defer close(returned) type pingResult struct { - *pingResp + *Challenge error primary bool done bool @@ -130,7 +120,7 @@ func pingParallel(ctx context.Context, reg name.Registry, t http.RoundTripper, s startRacer := func(ctx context.Context, scheme string) { pr, err := pingSingle(ctx, reg, t, scheme) select { - case results <- pingResult{pingResp: pr, error: err, primary: scheme == "https", done: true}: + case results <- pingResult{Challenge: pr, error: err, primary: scheme == "https", done: true}: case <-returned: if pr != nil { logs.Debug.Printf("%s lost race", scheme) @@ -156,7 +146,7 @@ func pingParallel(ctx context.Context, reg name.Registry, t http.RoundTripper, s case res := <-results: if res.error == nil { - return res.pingResp, nil + return res.Challenge, nil } if res.primary { primary = res @@ -164,7 +154,7 @@ func pingParallel(ctx context.Context, reg name.Registry, t http.RoundTripper, s fallback = res } if primary.done && fallback.done { - return nil, multierrs([]error{primary.error, fallback.error}) + return nil, multierrs{primary.error, fallback.error} } if res.primary && fallbackTimer.Stop() { // Primary failed and we haven't started the fallback, diff --git a/vendor/github.com/google/go-containerregistry/pkg/v1/remote/transport/schemer.go b/vendor/github.com/google/go-containerregistry/pkg/v1/remote/transport/schemer.go index d70b6a850c..05844db136 100644 --- a/vendor/github.com/google/go-containerregistry/pkg/v1/remote/transport/schemer.go +++ b/vendor/github.com/google/go-containerregistry/pkg/v1/remote/transport/schemer.go @@ -37,7 +37,7 @@ func (st *schemeTransport) RoundTrip(in *http.Request) (*http.Response, error) { // based on which scheme was successful. That is only valid for the // registry server and not e.g. a separate token server or blob storage, // so we should only override the scheme if the host is the registry. - if matchesHost(st.registry, in, st.scheme) { + if matchesHost(st.registry.String(), in, st.scheme) { in.URL.Scheme = st.scheme } return st.inner.RoundTrip(in) diff --git a/vendor/github.com/google/go-containerregistry/pkg/v1/remote/transport/transport.go b/vendor/github.com/google/go-containerregistry/pkg/v1/remote/transport/transport.go index 01fe1fa820..bd539b44fb 100644 --- a/vendor/github.com/google/go-containerregistry/pkg/v1/remote/transport/transport.go +++ b/vendor/github.com/google/go-containerregistry/pkg/v1/remote/transport/transport.go @@ -16,8 +16,8 @@ package transport import ( "context" - "fmt" "net/http" + "strings" "github.com/google/go-containerregistry/pkg/authn" "github.com/google/go-containerregistry/pkg/name" @@ -59,7 +59,7 @@ func NewWithContext(ctx context.Context, reg name.Registry, auth authn.Authentic // First we ping the registry to determine the parameters of the authentication handshake // (if one is even necessary). - pr, err := ping(ctx, reg, t) + pr, err := Ping(ctx, reg, t) if err != nil { return nil, err } @@ -69,39 +69,32 @@ func NewWithContext(ctx context.Context, reg name.Registry, auth authn.Authentic t = NewUserAgent(t, "") } + scheme := "https" + if pr.Insecure { + scheme = "http" + } + // Wrap t in a transport that selects the appropriate scheme based on the ping response. t = &schemeTransport{ - scheme: pr.scheme, + scheme: scheme, registry: reg, inner: t, } - switch pr.challenge.Canonical() { - case anonymous, basic: + if strings.ToLower(pr.Scheme) != "bearer" { return &Wrapper{&basicTransport{inner: t, auth: auth, target: reg.RegistryStr()}}, nil - case bearer: - // We require the realm, which tells us where to send our Basic auth to turn it into Bearer auth. - realm, ok := pr.parameters["realm"] - if !ok { - return nil, fmt.Errorf("malformed www-authenticate, missing realm: %v", pr.parameters) - } - service := pr.parameters["service"] - bt := &bearerTransport{ - inner: t, - basic: auth, - realm: realm, - registry: reg, - service: service, - scopes: scopes, - scheme: pr.scheme, - } - if err := bt.refresh(ctx); err != nil { - return nil, err - } - return &Wrapper{bt}, nil - default: - return nil, fmt.Errorf("unrecognized challenge: %s", pr.challenge) } + + bt, err := fromChallenge(reg, auth, t, pr) + if err != nil { + return nil, err + } + bt.scopes = scopes + + if err := bt.refresh(ctx); err != nil { + return nil, err + } + return &Wrapper{bt}, nil } // Wrapper results in *not* wrapping supplied transport with additional logic such as retries, useragent and debug logging diff --git a/vendor/github.com/google/go-containerregistry/pkg/v1/remote/write.go b/vendor/github.com/google/go-containerregistry/pkg/v1/remote/write.go index f4369e2a07..6bfce75e72 100644 --- a/vendor/github.com/google/go-containerregistry/pkg/v1/remote/write.go +++ b/vendor/github.com/google/go-containerregistry/pkg/v1/remote/write.go @@ -210,7 +210,7 @@ func (w *writer) initiateUpload(ctx context.Context, from, mount, origin string) req.Header.Set("Content-Type", "application/json") resp, err := w.client.Do(req.WithContext(ctx)) if err != nil { - if origin != "" && origin != w.repo.RegistryStr() { + if from != "" { // https://github.com/google/go-containerregistry/issues/1679 logs.Warn.Printf("retrying without mount: %v", err) return w.initiateUpload(ctx, "", "", "") @@ -220,7 +220,7 @@ func (w *writer) initiateUpload(ctx context.Context, from, mount, origin string) defer resp.Body.Close() if err := transport.CheckError(resp, http.StatusCreated, http.StatusAccepted); err != nil { - if origin != "" && origin != w.repo.RegistryStr() { + if from != "" { // https://github.com/google/go-containerregistry/issues/1404 logs.Warn.Printf("retrying without mount: %v", err) return w.initiateUpload(ctx, "", "", "") @@ -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) diff --git a/vendor/modules.txt b/vendor/modules.txt index a68f4b3187..dbb540fd71 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -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