mirror of
https://github.com/ko-build/ko.git
synced 2026-06-18 20:14:08 +02:00
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:
committed by
Jason Hall
parent
cfc13deeb6
commit
a271b54fe5
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
+65
@@ -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
@@ -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 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),
|
||||
}
|
||||
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(),
|
||||
}
|
||||
} 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 ®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)
|
||||
}
|
||||
}
|
||||
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
|
||||
|
||||
+6
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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 (
|
||||
|
||||
+104
-33
@@ -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
|
||||
}
|
||||
|
||||
|
||||
+25
-35
@@ -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,
|
||||
|
||||
+1
-1
@@ -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)
|
||||
|
||||
Generated
Vendored
+20
-27
@@ -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
@@ -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)
|
||||
|
||||
Vendored
+2
-2
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user