diff --git a/go.mod b/go.mod index 67b927e4..7529206d 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/compose-spec/compose-go v1.9.0 github.com/containerd/console v1.0.3 github.com/containerd/containerd v1.6.16 - github.com/docker/cli v23.0.0-rc.1+incompatible + github.com/docker/cli v23.0.0+incompatible github.com/docker/cli-docs-tool v0.5.1 github.com/docker/distribution v2.8.1+incompatible github.com/docker/docker v23.0.0+incompatible diff --git a/go.sum b/go.sum index 113c9d5e..6448fe46 100644 --- a/go.sum +++ b/go.sum @@ -163,8 +163,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/distribution/distribution/v3 v3.0.0-20221103125252-ebfa2a0ac0a9 h1:doprs/RuXCuN864IfxC3h2qocrt158wGv3A5mcqSZQw= github.com/distribution/distribution/v3 v3.0.0-20221103125252-ebfa2a0ac0a9/go.mod h1:6rIc5NMSjXjjnwzWWy3HAm9gDBu+X7aCzL8VrHIKgxM= -github.com/docker/cli v23.0.0-rc.1+incompatible h1:Vl3pcUK4/LFAD56Ys3BrqgAtuwpWd/IO3amuSL0ZbP0= -github.com/docker/cli v23.0.0-rc.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v23.0.0+incompatible h1:bcM4syaQ+EM/iczJTimMOGzvnzJBFPFEf4acS7sZ+RM= +github.com/docker/cli v23.0.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/cli-docs-tool v0.5.1 h1:jIk/cCZurZERhALPVKhqlNxTQGxn2kcI+56gE57PQXg= github.com/docker/cli-docs-tool v0.5.1/go.mod h1:zMjqTFCU361PRh8apiXzeAZ1Q/xupbIwTusYpzCXS/o= github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68= diff --git a/vendor/github.com/docker/cli/cli-plugins/plugin/plugin.go b/vendor/github.com/docker/cli/cli-plugins/plugin/plugin.go index 3d986622..bdb5a648 100644 --- a/vendor/github.com/docker/cli/cli-plugins/plugin/plugin.go +++ b/vendor/github.com/docker/cli/cli-plugins/plugin/plugin.go @@ -133,7 +133,9 @@ func newPluginCommand(dockerCli *command.DockerCli, plugin *cobra.Command, meta } opts, flags := cli.SetupPluginRootCommand(cmd) + cmd.SetIn(dockerCli.In()) cmd.SetOut(dockerCli.Out()) + cmd.SetErr(dockerCli.Err()) cmd.AddCommand( plugin, diff --git a/vendor/github.com/docker/cli/cli/cobra.go b/vendor/github.com/docker/cli/cli/cobra.go index dbb6caa8..1b07fc56 100644 --- a/vendor/github.com/docker/cli/cli/cobra.go +++ b/vendor/github.com/docker/cli/cli/cobra.go @@ -61,7 +61,10 @@ func setupCommonRootCommand(rootCmd *cobra.Command) (*cliflags.ClientOptions, *p rootCmd.PersistentFlags().MarkShorthandDeprecated("help", "please use --help") rootCmd.PersistentFlags().Lookup("help").Hidden = true - rootCmd.Annotations = map[string]string{"additionalHelp": "For more help on how to use Docker, head to https://docs.docker.com/go/guides/"} + rootCmd.Annotations = map[string]string{ + "additionalHelp": "For more help on how to use Docker, head to https://docs.docker.com/go/guides/", + "docs.code-delimiter": `"`, // https://github.com/docker/cli-docs-tool/blob/77abede22166eaea4af7335096bdcedd043f5b19/annotation/annotation.go#L20-L22 + } // Configure registry.CertsDir() when running in rootless-mode if os.Getenv("ROOTLESSKIT_STATE_DIR") != "" { @@ -231,9 +234,13 @@ func isExperimental(cmd *cobra.Command) bool { } func additionalHelp(cmd *cobra.Command) string { - if additionalHelp, ok := cmd.Annotations["additionalHelp"]; ok { + if msg, ok := cmd.Annotations["additionalHelp"]; ok { + out := cmd.OutOrStderr() + if _, isTerminal := term.GetFdInfo(out); !isTerminal { + return msg + } style := aec.EmptyBuilder.Bold().ANSI - return style.Apply(additionalHelp) + return style.Apply(msg) } return "" } @@ -504,6 +511,7 @@ Run '{{.CommandPath}} COMMAND --help' for more information on a command. {{- if hasAdditionalHelp .}} {{ additionalHelp . }} + {{- end}} ` diff --git a/vendor/github.com/docker/cli/cli/command/utils.go b/vendor/github.com/docker/cli/cli/command/utils.go index 8913b9a3..753f428a 100644 --- a/vendor/github.com/docker/cli/cli/command/utils.go +++ b/vendor/github.com/docker/cli/cli/command/utils.go @@ -96,26 +96,26 @@ func PruneFilters(dockerCli Cli, pruneFilters filters.Args) filters.Args { return pruneFilters } for _, f := range dockerCli.ConfigFile().PruneFilters { - parts := strings.SplitN(f, "=", 2) - if len(parts) != 2 { + k, v, ok := strings.Cut(f, "=") + if !ok { continue } - if parts[0] == "label" { + if k == "label" { // CLI label filter supersede config.json. // If CLI label filter conflict with config.json, // skip adding label! filter in config.json. - if pruneFilters.Contains("label!") && pruneFilters.ExactMatch("label!", parts[1]) { + if pruneFilters.Contains("label!") && pruneFilters.ExactMatch("label!", v) { continue } - } else if parts[0] == "label!" { + } else if k == "label!" { // CLI label! filter supersede config.json. // If CLI label! filter conflict with config.json, // skip adding label filter in config.json. - if pruneFilters.Contains("label") && pruneFilters.ExactMatch("label", parts[1]) { + if pruneFilters.Contains("label") && pruneFilters.ExactMatch("label", v) { continue } } - pruneFilters.Add(parts[0], parts[1]) + pruneFilters.Add(k, v) } return pruneFilters 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 796b0a0a..609a88c2 100644 --- a/vendor/github.com/docker/cli/cli/config/configfile/file.go +++ b/vendor/github.com/docker/cli/cli/config/configfile/file.go @@ -241,12 +241,11 @@ func decodeAuth(authStr string) (string, string, error) { if n > decLen { return "", "", errors.Errorf("Something went wrong decoding auth config") } - arr := strings.SplitN(string(decoded), ":", 2) - if len(arr) != 2 { + userName, password, ok := strings.Cut(string(decoded), ":") + if !ok || userName == "" { return "", "", errors.Errorf("Invalid auth configuration file") } - password := strings.Trim(arr[1], "\x00") - return arr[0], password, nil + return userName, strings.Trim(password, "\x00"), nil } // GetCredentialsStore returns a new credentials store from the settings in the @@ -301,7 +300,8 @@ func (configFile *ConfigFile) GetAllCredentials() (map[string]types.AuthConfig, for registryHostname := range configFile.CredentialHelpers { newAuth, err := configFile.GetAuthConfig(registryHostname) if err != nil { - return nil, err + logrus.WithError(err).Warnf("Failed to get credentials for registry: %s", registryHostname) + continue } auths[registryHostname] = newAuth } diff --git a/vendor/github.com/docker/cli/cli/config/credentials/file_store.go b/vendor/github.com/docker/cli/cli/config/credentials/file_store.go index e509820b..de1c676e 100644 --- a/vendor/github.com/docker/cli/cli/config/credentials/file_store.go +++ b/vendor/github.com/docker/cli/cli/config/credentials/file_store.go @@ -75,7 +75,6 @@ func ConvertToHostname(url string) string { stripped = strings.TrimPrefix(url, "https://") } - nameParts := strings.SplitN(stripped, "/", 2) - - return nameParts[0] + hostName, _, _ := strings.Cut(stripped, "/") + return hostName } diff --git a/vendor/github.com/docker/cli/cli/flags/options.go b/vendor/github.com/docker/cli/cli/flags/options.go index 3f461b24..cea0faf7 100644 --- a/vendor/github.com/docker/cli/cli/flags/options.go +++ b/vendor/github.com/docker/cli/cli/flags/options.go @@ -67,7 +67,7 @@ func (o *ClientOptions) InstallFlags(flags *pflag.FlagSet) { } flags.BoolVarP(&o.Debug, "debug", "D", false, "Enable debug mode") - flags.StringVarP(&o.LogLevel, "log-level", "l", "info", `Set the logging level ("debug"|"info"|"warn"|"error"|"fatal")`) + flags.StringVarP(&o.LogLevel, "log-level", "l", "info", `Set the logging level ("debug", "info", "warn", "error", "fatal")`) flags.BoolVar(&o.TLS, "tls", dockerTLS, "Use TLS; implied by --tlsverify") flags.BoolVar(&o.TLSVerify, FlagTLSVerify, dockerTLSVerify, "Use TLS and verify the remote") diff --git a/vendor/github.com/docker/cli/cli/manifest/types/types.go b/vendor/github.com/docker/cli/cli/manifest/types/types.go index 5b094f51..ca2a3e78 100644 --- a/vendor/github.com/docker/cli/cli/manifest/types/types.go +++ b/vendor/github.com/docker/cli/cli/manifest/types/types.go @@ -5,6 +5,7 @@ import ( "github.com/docker/distribution" "github.com/docker/distribution/manifest/manifestlist" + "github.com/docker/distribution/manifest/ocischema" "github.com/docker/distribution/manifest/schema2" "github.com/docker/distribution/reference" "github.com/opencontainers/go-digest" @@ -16,10 +17,12 @@ import ( type ImageManifest struct { Ref *SerializableNamed Descriptor ocispec.Descriptor + Raw []byte `json:",omitempty"` // SchemaV2Manifest is used for inspection - // TODO: Deprecate this and store manifest blobs SchemaV2Manifest *schema2.DeserializedManifest `json:",omitempty"` + // OCIManifest is used for inspection + OCIManifest *ocischema.DeserializedManifest `json:",omitempty"` } // OCIPlatform creates an OCI platform from a manifest list platform spec @@ -53,8 +56,15 @@ func PlatformSpecFromOCI(p *ocispec.Platform) *manifestlist.PlatformSpec { // Blobs returns the digests for all the blobs referenced by this manifest func (i ImageManifest) Blobs() []digest.Digest { digests := []digest.Digest{} - for _, descriptor := range i.SchemaV2Manifest.References() { - digests = append(digests, descriptor.Digest) + switch { + case i.SchemaV2Manifest != nil: + for _, descriptor := range i.SchemaV2Manifest.References() { + digests = append(digests, descriptor.Digest) + } + case i.OCIManifest != nil: + for _, descriptor := range i.OCIManifest.References() { + digests = append(digests, descriptor.Digest) + } } return digests } @@ -65,6 +75,8 @@ func (i ImageManifest) Payload() (string, []byte, error) { switch { case i.SchemaV2Manifest != nil: return i.SchemaV2Manifest.Payload() + case i.OCIManifest != nil: + return i.OCIManifest.Payload() default: return "", nil, errors.Errorf("%s has no payload", i.Ref) } @@ -76,6 +88,8 @@ func (i ImageManifest) References() []distribution.Descriptor { switch { case i.SchemaV2Manifest != nil: return i.SchemaV2Manifest.References() + case i.OCIManifest != nil: + return i.OCIManifest.References() default: return nil } @@ -84,13 +98,35 @@ func (i ImageManifest) References() []distribution.Descriptor { // NewImageManifest returns a new ImageManifest object. The values for Platform // are initialized from those in the image func NewImageManifest(ref reference.Named, desc ocispec.Descriptor, manifest *schema2.DeserializedManifest) ImageManifest { + raw, err := manifest.MarshalJSON() + if err != nil { + raw = nil + } + return ImageManifest{ Ref: &SerializableNamed{Named: ref}, Descriptor: desc, + Raw: raw, SchemaV2Manifest: manifest, } } +// NewOCIImageManifest returns a new ImageManifest object. The values for +// Platform are initialized from those in the image +func NewOCIImageManifest(ref reference.Named, desc ocispec.Descriptor, manifest *ocischema.DeserializedManifest) ImageManifest { + raw, err := manifest.MarshalJSON() + if err != nil { + raw = nil + } + + return ImageManifest{ + Ref: &SerializableNamed{Named: ref}, + Descriptor: desc, + Raw: raw, + OCIManifest: manifest, + } +} + // SerializableNamed is a reference.Named that can be serialized and deserialized // from JSON type SerializableNamed struct { diff --git a/vendor/github.com/docker/cli/cli/registry/client/fetcher.go b/vendor/github.com/docker/cli/cli/registry/client/fetcher.go index ef8011d1..acae274a 100644 --- a/vendor/github.com/docker/cli/cli/registry/client/fetcher.go +++ b/vendor/github.com/docker/cli/cli/registry/client/fetcher.go @@ -7,6 +7,7 @@ import ( "github.com/docker/cli/cli/manifest/types" "github.com/docker/distribution" "github.com/docker/distribution/manifest/manifestlist" + "github.com/docker/distribution/manifest/ocischema" "github.com/docker/distribution/manifest/schema2" "github.com/docker/distribution/reference" "github.com/docker/distribution/registry/api/errcode" @@ -35,6 +36,12 @@ func fetchManifest(ctx context.Context, repo distribution.Repository, ref refere return types.ImageManifest{}, err } return imageManifest, nil + case *ocischema.DeserializedManifest: + imageManifest, err := pullManifestOCISchema(ctx, ref, repo, *v) + if err != nil { + return types.ImageManifest{}, err + } + return imageManifest, nil case *manifestlist.DeserializedManifestList: return types.ImageManifest{}, errors.Errorf("%s is a manifest list", ref) } @@ -94,6 +101,28 @@ func pullManifestSchemaV2(ctx context.Context, ref reference.Named, repo distrib return types.NewImageManifest(ref, manifestDesc, &mfst), nil } +func pullManifestOCISchema(ctx context.Context, ref reference.Named, repo distribution.Repository, mfst ocischema.DeserializedManifest) (types.ImageManifest, error) { + manifestDesc, err := validateManifestDigest(ref, mfst) + if err != nil { + return types.ImageManifest{}, err + } + configJSON, err := pullManifestSchemaV2ImageConfig(ctx, mfst.Target().Digest, repo) + if err != nil { + return types.ImageManifest{}, err + } + + if manifestDesc.Platform == nil { + manifestDesc.Platform = &ocispec.Platform{} + } + + // Fill in os and architecture fields from config JSON + if err := json.Unmarshal(configJSON, manifestDesc.Platform); err != nil { + return types.ImageManifest{}, err + } + + return types.NewOCIImageManifest(ref, manifestDesc, &mfst), nil +} + func pullManifestSchemaV2ImageConfig(ctx context.Context, dgst digest.Digest, repo distribution.Repository) ([]byte, error) { blobs := repo.Blobs(ctx) configJSON, err := blobs.Get(ctx, dgst) @@ -153,16 +182,21 @@ func pullManifestList(ctx context.Context, ref reference.Named, repo distributio if err != nil { return nil, err } - v, ok := manifest.(*schema2.DeserializedManifest) - if !ok { - return nil, errors.Errorf("unsupported manifest format: %v", v) - } manifestRef, err := reference.WithDigest(ref, manifestDescriptor.Digest) if err != nil { return nil, err } - imageManifest, err := pullManifestSchemaV2(ctx, manifestRef, repo, *v) + + var imageManifest types.ImageManifest + switch v := manifest.(type) { + case *schema2.DeserializedManifest: + imageManifest, err = pullManifestSchemaV2(ctx, manifestRef, repo, *v) + case *ocischema.DeserializedManifest: + imageManifest, err = pullManifestOCISchema(ctx, manifestRef, repo, *v) + default: + err = errors.Errorf("unsupported manifest type: %T", manifest) + } if err != nil { return nil, err } diff --git a/vendor/github.com/docker/cli/opts/config.go b/vendor/github.com/docker/cli/opts/config.go index 40bc13e2..3be0fa93 100644 --- a/vendor/github.com/docker/cli/opts/config.go +++ b/vendor/github.com/docker/cli/opts/config.go @@ -40,25 +40,23 @@ func (o *ConfigOpt) Set(value string) error { } for _, field := range fields { - parts := strings.SplitN(field, "=", 2) - key := strings.ToLower(parts[0]) - - if len(parts) != 2 { + key, val, ok := strings.Cut(field, "=") + if !ok || key == "" { return fmt.Errorf("invalid field '%s' must be a key=value pair", field) } - value := parts[1] - switch key { + // TODO(thaJeztah): these options should not be case-insensitive. + switch strings.ToLower(key) { case "source", "src": - options.ConfigName = value + options.ConfigName = val case "target": - options.File.Name = value + options.File.Name = val case "uid": - options.File.UID = value + options.File.UID = val case "gid": - options.File.GID = value + options.File.GID = val case "mode": - m, err := strconv.ParseUint(value, 0, 32) + m, err := strconv.ParseUint(val, 0, 32) if err != nil { return fmt.Errorf("invalid mode specified: %v", err) } diff --git a/vendor/github.com/docker/cli/opts/env.go b/vendor/github.com/docker/cli/opts/env.go index d21c8ccb..214d6f44 100644 --- a/vendor/github.com/docker/cli/opts/env.go +++ b/vendor/github.com/docker/cli/opts/env.go @@ -16,15 +16,16 @@ import ( // // The only validation here is to check if name is empty, per #25099 func ValidateEnv(val string) (string, error) { - arr := strings.SplitN(val, "=", 2) - if arr[0] == "" { + k, _, hasValue := strings.Cut(val, "=") + if k == "" { return "", errors.New("invalid environment variable: " + val) } - if len(arr) > 1 { + if hasValue { + // val contains a "=" (but value may be an empty string) return val, nil } - if envVal, ok := os.LookupEnv(arr[0]); ok { - return arr[0] + "=" + envVal, nil + if envVal, ok := os.LookupEnv(k); ok { + return k + "=" + envVal, nil } return val, nil } diff --git a/vendor/github.com/docker/cli/opts/file.go b/vendor/github.com/docker/cli/opts/file.go index 2346cc16..72b90e11 100644 --- a/vendor/github.com/docker/cli/opts/file.go +++ b/vendor/github.com/docker/cli/opts/file.go @@ -46,10 +46,10 @@ func parseKeyValueFile(filename string, emptyFn func(string) (string, bool)) ([] currentLine++ // line is not empty, and not starting with '#' if len(line) > 0 && !strings.HasPrefix(line, "#") { - data := strings.SplitN(line, "=", 2) + variable, value, hasValue := strings.Cut(line, "=") // trim the front of a variable, but nothing else - variable := strings.TrimLeft(data[0], whiteSpaces) + variable = strings.TrimLeft(variable, whiteSpaces) if strings.ContainsAny(variable, whiteSpaces) { return []string{}, ErrBadKey{fmt.Sprintf("variable '%s' contains whitespaces", variable)} } @@ -57,18 +57,17 @@ func parseKeyValueFile(filename string, emptyFn func(string) (string, bool)) ([] return []string{}, ErrBadKey{fmt.Sprintf("no variable name on line '%s'", line)} } - if len(data) > 1 { + if hasValue { // pass the value through, no trimming - lines = append(lines, fmt.Sprintf("%s=%s", variable, data[1])) + lines = append(lines, variable+"="+value) } else { - var value string var present bool if emptyFn != nil { value, present = emptyFn(line) } if present { // if only a pass-through variable is given, clean it up. - lines = append(lines, fmt.Sprintf("%s=%s", strings.TrimSpace(line), value)) + lines = append(lines, strings.TrimSpace(variable)+"="+value) } } } diff --git a/vendor/github.com/docker/cli/opts/gpus.go b/vendor/github.com/docker/cli/opts/gpus.go index 8796a805..93bf9397 100644 --- a/vendor/github.com/docker/cli/opts/gpus.go +++ b/vendor/github.com/docker/cli/opts/gpus.go @@ -38,14 +38,13 @@ func (o *GpuOpts) Set(value string) error { seen := map[string]struct{}{} // Set writable as the default for _, field := range fields { - parts := strings.SplitN(field, "=", 2) - key := parts[0] + key, val, withValue := strings.Cut(field, "=") if _, ok := seen[key]; ok { return fmt.Errorf("gpu request key '%s' can be specified only once", key) } seen[key] = struct{}{} - if len(parts) == 1 { + if !withValue { seen["count"] = struct{}{} req.Count, err = parseCount(key) if err != nil { @@ -54,21 +53,20 @@ func (o *GpuOpts) Set(value string) error { continue } - value := parts[1] switch key { case "driver": - req.Driver = value + req.Driver = val case "count": - req.Count, err = parseCount(value) + req.Count, err = parseCount(val) if err != nil { return err } case "device": - req.DeviceIDs = strings.Split(value, ",") + req.DeviceIDs = strings.Split(val, ",") case "capabilities": - req.Capabilities = [][]string{append(strings.Split(value, ","), "gpu")} + req.Capabilities = [][]string{append(strings.Split(val, ","), "gpu")} case "options": - r := csv.NewReader(strings.NewReader(value)) + r := csv.NewReader(strings.NewReader(val)) optFields, err := r.Read() if err != nil { return errors.Wrap(err, "failed to read gpu options") diff --git a/vendor/github.com/docker/cli/opts/hosts.go b/vendor/github.com/docker/cli/opts/hosts.go index d59421b3..7cdd1218 100644 --- a/vendor/github.com/docker/cli/opts/hosts.go +++ b/vendor/github.com/docker/cli/opts/hosts.go @@ -33,6 +33,8 @@ const ( ) // ValidateHost validates that the specified string is a valid host and returns it. +// +// TODO(thaJeztah): ValidateHost appears to be unused; deprecate it. func ValidateHost(val string) (string, error) { host := strings.TrimSpace(val) // The empty string means default and is not handled by parseDockerDaemonHost @@ -69,18 +71,19 @@ func ParseHost(defaultToTLS bool, val string) (string, error) { // parseDockerDaemonHost parses the specified address and returns an address that will be used as the host. // Depending of the address specified, this may return one of the global Default* strings defined in hosts.go. func parseDockerDaemonHost(addr string) (string, error) { - addrParts := strings.SplitN(addr, "://", 2) - if len(addrParts) == 1 && addrParts[0] != "" { - addrParts = []string{"tcp", addrParts[0]} + proto, host, hasProto := strings.Cut(addr, "://") + if !hasProto && proto != "" { + host = proto + proto = "tcp" } - switch addrParts[0] { + switch proto { case "tcp": - return ParseTCPAddr(addrParts[1], defaultTCPHost) + return ParseTCPAddr(host, defaultTCPHost) case "unix": - return parseSimpleProtoAddr("unix", addrParts[1], defaultUnixSocket) + return parseSimpleProtoAddr(proto, host, defaultUnixSocket) case "npipe": - return parseSimpleProtoAddr("npipe", addrParts[1], defaultNamedPipe) + return parseSimpleProtoAddr(proto, host, defaultNamedPipe) case "fd": return addr, nil case "ssh": @@ -160,16 +163,18 @@ func ParseTCPAddr(tryAddr string, defaultAddr string) (string, error) { // ValidateExtraHost validates that the specified string is a valid extrahost and returns it. // ExtraHost is in the form of name:ip where the ip has to be a valid ip (IPv4 or IPv6). +// +// TODO(thaJeztah): remove client-side validation, and delegate to the API server. func ValidateExtraHost(val string) (string, error) { // allow for IPv6 addresses in extra hosts by only splitting on first ":" - arr := strings.SplitN(val, ":", 2) - if len(arr) != 2 || len(arr[0]) == 0 { + k, v, ok := strings.Cut(val, ":") + if !ok || k == "" { return "", fmt.Errorf("bad format for add-host: %q", val) } // Skip IPaddr validation for "host-gateway" string - if arr[1] != hostGatewayName { - if _, err := ValidateIPAddress(arr[1]); err != nil { - return "", fmt.Errorf("invalid IP address in add-host: %q", arr[1]) + if v != hostGatewayName { + if _, err := ValidateIPAddress(v); err != nil { + return "", fmt.Errorf("invalid IP address in add-host: %q", v) } } return val, nil diff --git a/vendor/github.com/docker/cli/opts/mount.go b/vendor/github.com/docker/cli/opts/mount.go index 7ffc3acc..2b531127 100644 --- a/vendor/github.com/docker/cli/opts/mount.go +++ b/vendor/github.com/docker/cli/opts/mount.go @@ -56,21 +56,21 @@ func (m *MountOpt) Set(value string) error { } setValueOnMap := func(target map[string]string, value string) { - parts := strings.SplitN(value, "=", 2) - if len(parts) == 1 { - target[value] = "" - } else { - target[parts[0]] = parts[1] + k, v, _ := strings.Cut(value, "=") + if k != "" { + target[k] = v } } mount.Type = mounttypes.TypeVolume // default to volume mounts // Set writable as the default for _, field := range fields { - parts := strings.SplitN(field, "=", 2) - key := strings.ToLower(parts[0]) + key, val, ok := strings.Cut(field, "=") - if len(parts) == 1 { + // TODO(thaJeztah): these options should not be case-insensitive. + key = strings.ToLower(key) + + if !ok { switch key { case "readonly", "ro": mount.ReadOnly = true @@ -81,64 +81,61 @@ func (m *MountOpt) Set(value string) error { case "bind-nonrecursive": bindOptions().NonRecursive = true continue + default: + return fmt.Errorf("invalid field '%s' must be a key=value pair", field) } } - if len(parts) != 2 { - return fmt.Errorf("invalid field '%s' must be a key=value pair", field) - } - - value := parts[1] switch key { case "type": - mount.Type = mounttypes.Type(strings.ToLower(value)) + mount.Type = mounttypes.Type(strings.ToLower(val)) case "source", "src": - mount.Source = value - if strings.HasPrefix(value, "."+string(filepath.Separator)) || value == "." { - if abs, err := filepath.Abs(value); err == nil { + mount.Source = val + if strings.HasPrefix(val, "."+string(filepath.Separator)) || val == "." { + if abs, err := filepath.Abs(val); err == nil { mount.Source = abs } } case "target", "dst", "destination": - mount.Target = value + mount.Target = val case "readonly", "ro": - mount.ReadOnly, err = strconv.ParseBool(value) + mount.ReadOnly, err = strconv.ParseBool(val) if err != nil { - return fmt.Errorf("invalid value for %s: %s", key, value) + return fmt.Errorf("invalid value for %s: %s", key, val) } case "consistency": - mount.Consistency = mounttypes.Consistency(strings.ToLower(value)) + mount.Consistency = mounttypes.Consistency(strings.ToLower(val)) case "bind-propagation": - bindOptions().Propagation = mounttypes.Propagation(strings.ToLower(value)) + bindOptions().Propagation = mounttypes.Propagation(strings.ToLower(val)) case "bind-nonrecursive": - bindOptions().NonRecursive, err = strconv.ParseBool(value) + bindOptions().NonRecursive, err = strconv.ParseBool(val) if err != nil { - return fmt.Errorf("invalid value for %s: %s", key, value) + return fmt.Errorf("invalid value for %s: %s", key, val) } case "volume-nocopy": - volumeOptions().NoCopy, err = strconv.ParseBool(value) + volumeOptions().NoCopy, err = strconv.ParseBool(val) if err != nil { - return fmt.Errorf("invalid value for volume-nocopy: %s", value) + return fmt.Errorf("invalid value for volume-nocopy: %s", val) } case "volume-label": - setValueOnMap(volumeOptions().Labels, value) + setValueOnMap(volumeOptions().Labels, val) case "volume-driver": - volumeOptions().DriverConfig.Name = value + volumeOptions().DriverConfig.Name = val case "volume-opt": if volumeOptions().DriverConfig.Options == nil { volumeOptions().DriverConfig.Options = make(map[string]string) } - setValueOnMap(volumeOptions().DriverConfig.Options, value) + setValueOnMap(volumeOptions().DriverConfig.Options, val) case "tmpfs-size": - sizeBytes, err := units.RAMInBytes(value) + sizeBytes, err := units.RAMInBytes(val) if err != nil { - return fmt.Errorf("invalid value for %s: %s", key, value) + return fmt.Errorf("invalid value for %s: %s", key, val) } tmpfsOptions().SizeBytes = sizeBytes case "tmpfs-mode": - ui64, err := strconv.ParseUint(value, 8, 32) + ui64, err := strconv.ParseUint(val, 8, 32) if err != nil { - return fmt.Errorf("invalid value for %s: %s", key, value) + return fmt.Errorf("invalid value for %s: %s", key, val) } tmpfsOptions().Mode = os.FileMode(ui64) default: diff --git a/vendor/github.com/docker/cli/opts/network.go b/vendor/github.com/docker/cli/opts/network.go index ce7370ee..12c3977b 100644 --- a/vendor/github.com/docker/cli/opts/network.go +++ b/vendor/github.com/docker/cli/opts/network.go @@ -48,34 +48,33 @@ func (n *NetworkOpt) Set(value string) error { netOpt.Aliases = []string{} for _, field := range fields { - parts := strings.SplitN(field, "=", 2) - - if len(parts) < 2 { + // TODO(thaJeztah): these options should not be case-insensitive. + key, val, ok := strings.Cut(strings.ToLower(field), "=") + if !ok || key == "" { return fmt.Errorf("invalid field %s", field) } - key := strings.TrimSpace(strings.ToLower(parts[0])) - value := strings.TrimSpace(strings.ToLower(parts[1])) + key = strings.TrimSpace(key) + val = strings.TrimSpace(val) switch key { case networkOptName: - netOpt.Target = value + netOpt.Target = val case networkOptAlias: - netOpt.Aliases = append(netOpt.Aliases, value) + netOpt.Aliases = append(netOpt.Aliases, val) case networkOptIPv4Address: - netOpt.IPv4Address = value + netOpt.IPv4Address = val case networkOptIPv6Address: - netOpt.IPv6Address = value + netOpt.IPv6Address = val case driverOpt: - key, value, err = parseDriverOpt(value) - if err == nil { - if netOpt.DriverOpts == nil { - netOpt.DriverOpts = make(map[string]string) - } - netOpt.DriverOpts[key] = value - } else { + key, val, err = parseDriverOpt(val) + if err != nil { return err } + if netOpt.DriverOpts == nil { + netOpt.DriverOpts = make(map[string]string) + } + netOpt.DriverOpts[key] = val default: return fmt.Errorf("invalid field key %s", key) } @@ -116,11 +115,13 @@ func (n *NetworkOpt) NetworkMode() string { } func parseDriverOpt(driverOpt string) (string, string, error) { - parts := strings.SplitN(driverOpt, "=", 2) - if len(parts) != 2 { + // TODO(thaJeztah): these options should not be case-insensitive. + // TODO(thaJeztah): should value be converted to lowercase as well, or only the key? + key, value, ok := strings.Cut(strings.ToLower(driverOpt), "=") + if !ok || key == "" { return "", "", fmt.Errorf("invalid key value pair format in driver options") } - key := strings.TrimSpace(strings.ToLower(parts[0])) - value := strings.TrimSpace(strings.ToLower(parts[1])) + key = strings.TrimSpace(key) + value = strings.TrimSpace(value) return key, value, nil } diff --git a/vendor/github.com/docker/cli/opts/opts.go b/vendor/github.com/docker/cli/opts/opts.go index 03550023..4e7790f0 100644 --- a/vendor/github.com/docker/cli/opts/opts.go +++ b/vendor/github.com/docker/cli/opts/opts.go @@ -55,7 +55,7 @@ func (opts *ListOpts) Set(value string) error { } value = v } - (*opts.values) = append((*opts.values), value) + *opts.values = append(*opts.values, value) return nil } @@ -63,7 +63,7 @@ func (opts *ListOpts) Set(value string) error { func (opts *ListOpts) Delete(key string) { for i, k := range *opts.values { if k == key { - (*opts.values) = append((*opts.values)[:i], (*opts.values)[i+1:]...) + *opts.values = append((*opts.values)[:i], (*opts.values)[i+1:]...) return } } @@ -81,7 +81,7 @@ func (opts *ListOpts) GetMap() map[string]struct{} { // GetAll returns the values of slice. func (opts *ListOpts) GetAll() []string { - return (*opts.values) + return *opts.values } // GetAllOrEmpty returns the values of the slice @@ -106,7 +106,7 @@ func (opts *ListOpts) Get(key string) bool { // Len returns the amount of element in the slice. func (opts *ListOpts) Len() int { - return len((*opts.values)) + return len(*opts.values) } // Type returns a string name for this Option type @@ -165,12 +165,8 @@ func (opts *MapOpts) Set(value string) error { } value = v } - vals := strings.SplitN(value, "=", 2) - if len(vals) == 1 { - (opts.values)[vals[0]] = "" - } else { - (opts.values)[vals[0]] = vals[1] - } + k, v, _ := strings.Cut(value, "=") + opts.values[k] = v return nil } @@ -277,16 +273,16 @@ func validateDomain(val string) (string, error) { // // TODO discuss if quotes (and other special characters) should be valid or invalid for keys // TODO discuss if leading/trailing whitespace in keys should be preserved (and valid) -func ValidateLabel(val string) (string, error) { - arr := strings.SplitN(val, "=", 2) - key := strings.TrimLeft(arr[0], whiteSpaces) +func ValidateLabel(value string) (string, error) { + key, _, _ := strings.Cut(value, "=") + key = strings.TrimLeft(key, whiteSpaces) if key == "" { - return "", fmt.Errorf("invalid label '%s': empty name", val) + return "", fmt.Errorf("invalid label '%s': empty name", value) } if strings.ContainsAny(key, whiteSpaces) { return "", fmt.Errorf("label '%s' contains whitespaces", key) } - return val, nil + return value, nil } // ValidateSysctl validates a sysctl and returns it. @@ -305,20 +301,19 @@ func ValidateSysctl(val string) (string, error) { "net.", "fs.mqueue.", } - arr := strings.Split(val, "=") - if len(arr) < 2 { - return "", fmt.Errorf("sysctl '%s' is not whitelisted", val) + k, _, ok := strings.Cut(val, "=") + if !ok || k == "" { + return "", fmt.Errorf("sysctl '%s' is not allowed", val) } - if validSysctlMap[arr[0]] { + if validSysctlMap[k] { return val, nil } - for _, vp := range validSysctlPrefixes { - if strings.HasPrefix(arr[0], vp) { + if strings.HasPrefix(k, vp) { return val, nil } } - return "", fmt.Errorf("sysctl '%s' is not whitelisted", val) + return "", fmt.Errorf("sysctl '%s' is not allowed", val) } // FilterOpt is a flag type for validating filters @@ -347,11 +342,12 @@ func (o *FilterOpt) Set(value string) error { if !strings.Contains(value, "=") { return errors.New("bad format of filter (expected name=value)") } - f := strings.SplitN(value, "=", 2) - name := strings.ToLower(strings.TrimSpace(f[0])) - value = strings.TrimSpace(f[1]) + name, val, _ := strings.Cut(value, "=") - o.filter.Add(name, value) + // TODO(thaJeztah): these options should not be case-insensitive. + name = strings.ToLower(strings.TrimSpace(name)) + val = strings.TrimSpace(val) + o.filter.Add(name, val) return nil } @@ -411,10 +407,14 @@ func ParseLink(val string) (string, string, error) { if val == "" { return "", "", fmt.Errorf("empty string specified for links") } - arr := strings.Split(val, ":") + // We expect two parts, but restrict to three to allow detecting invalid formats. + arr := strings.SplitN(val, ":", 3) + + // TODO(thaJeztah): clean up this logic!! if len(arr) > 2 { return "", "", fmt.Errorf("bad format for links: %s", val) } + // TODO(thaJeztah): this should trim the "/" prefix as well?? if len(arr) == 1 { return val, val, nil } @@ -422,6 +422,7 @@ func ParseLink(val string) (string, string, error) { // from an already created container and the format is not `foo:bar` // but `/foo:/c1/bar` if strings.HasPrefix(arr[0], "/") { + // TODO(thaJeztah): clean up this logic!! _, alias := path.Split(arr[1]) return arr[0][1:], alias, nil } diff --git a/vendor/github.com/docker/cli/opts/parse.go b/vendor/github.com/docker/cli/opts/parse.go index 4012c461..017577e4 100644 --- a/vendor/github.com/docker/cli/opts/parse.go +++ b/vendor/github.com/docker/cli/opts/parse.go @@ -41,12 +41,8 @@ func readKVStrings(files []string, override []string, emptyFn func(string) (stri func ConvertKVStringsToMap(values []string) map[string]string { result := make(map[string]string, len(values)) for _, value := range values { - kv := strings.SplitN(value, "=", 2) - if len(kv) == 1 { - result[kv[0]] = "" - } else { - result[kv[0]] = kv[1] - } + k, v, _ := strings.Cut(value, "=") + result[k] = v } return result @@ -62,11 +58,11 @@ func ConvertKVStringsToMap(values []string) map[string]string { func ConvertKVStringsToMapWithNil(values []string) map[string]*string { result := make(map[string]*string, len(values)) for _, value := range values { - kv := strings.SplitN(value, "=", 2) - if len(kv) == 1 { - result[kv[0]] = nil + k, v, ok := strings.Cut(value, "=") + if !ok { + result[k] = nil } else { - result[kv[0]] = &kv[1] + result[k] = &v } } @@ -81,21 +77,15 @@ func ParseRestartPolicy(policy string) (container.RestartPolicy, error) { return p, nil } - parts := strings.Split(policy, ":") - - if len(parts) > 2 { - return p, fmt.Errorf("invalid restart policy format") - } - if len(parts) == 2 { - count, err := strconv.Atoi(parts[1]) + k, v, _ := strings.Cut(policy, ":") + if v != "" { + count, err := strconv.Atoi(v) if err != nil { - return p, fmt.Errorf("maximum retry count must be an integer") + return p, fmt.Errorf("invalid restart policy format: maximum retry count must be an integer") } - p.MaximumRetryCount = count } - p.Name = parts[0] - + p.Name = k return p, nil } diff --git a/vendor/github.com/docker/cli/opts/port.go b/vendor/github.com/docker/cli/opts/port.go index c0814dbd..fe41cdd2 100644 --- a/vendor/github.com/docker/cli/opts/port.go +++ b/vendor/github.com/docker/cli/opts/port.go @@ -42,36 +42,33 @@ func (p *PortOpt) Set(value string) error { pConfig := swarm.PortConfig{} for _, field := range fields { - parts := strings.SplitN(field, "=", 2) - if len(parts) != 2 { + // TODO(thaJeztah): these options should not be case-insensitive. + key, val, ok := strings.Cut(strings.ToLower(field), "=") + if !ok || key == "" { return fmt.Errorf("invalid field %s", field) } - - key := strings.ToLower(parts[0]) - value := strings.ToLower(parts[1]) - switch key { case portOptProtocol: - if value != string(swarm.PortConfigProtocolTCP) && value != string(swarm.PortConfigProtocolUDP) && value != string(swarm.PortConfigProtocolSCTP) { - return fmt.Errorf("invalid protocol value %s", value) + if val != string(swarm.PortConfigProtocolTCP) && val != string(swarm.PortConfigProtocolUDP) && val != string(swarm.PortConfigProtocolSCTP) { + return fmt.Errorf("invalid protocol value %s", val) } - pConfig.Protocol = swarm.PortConfigProtocol(value) + pConfig.Protocol = swarm.PortConfigProtocol(val) case portOptMode: - if value != string(swarm.PortConfigPublishModeIngress) && value != string(swarm.PortConfigPublishModeHost) { - return fmt.Errorf("invalid publish mode value %s", value) + if val != string(swarm.PortConfigPublishModeIngress) && val != string(swarm.PortConfigPublishModeHost) { + return fmt.Errorf("invalid publish mode value %s", val) } - pConfig.PublishMode = swarm.PortConfigPublishMode(value) + pConfig.PublishMode = swarm.PortConfigPublishMode(val) case portOptTargetPort: - tPort, err := strconv.ParseUint(value, 10, 16) + tPort, err := strconv.ParseUint(val, 10, 16) if err != nil { return err } pConfig.TargetPort = uint32(tPort) case portOptPublishedPort: - pPort, err := strconv.ParseUint(value, 10, 16) + pPort, err := strconv.ParseUint(val, 10, 16) if err != nil { return err } diff --git a/vendor/github.com/docker/cli/opts/secret.go b/vendor/github.com/docker/cli/opts/secret.go index fabc62c0..750dbe4f 100644 --- a/vendor/github.com/docker/cli/opts/secret.go +++ b/vendor/github.com/docker/cli/opts/secret.go @@ -40,25 +40,22 @@ func (o *SecretOpt) Set(value string) error { } for _, field := range fields { - parts := strings.SplitN(field, "=", 2) - key := strings.ToLower(parts[0]) - - if len(parts) != 2 { + key, val, ok := strings.Cut(field, "=") + if !ok || key == "" { return fmt.Errorf("invalid field '%s' must be a key=value pair", field) } - - value := parts[1] - switch key { + // TODO(thaJeztah): these options should not be case-insensitive. + switch strings.ToLower(key) { case "source", "src": - options.SecretName = value + options.SecretName = val case "target": - options.File.Name = value + options.File.Name = val case "uid": - options.File.UID = value + options.File.UID = val case "gid": - options.File.GID = value + options.File.GID = val case "mode": - m, err := strconv.ParseUint(value, 0, 32) + m, err := strconv.ParseUint(val, 0, 32) if err != nil { return fmt.Errorf("invalid mode specified: %v", err) } diff --git a/vendor/github.com/docker/cli/opts/throttledevice.go b/vendor/github.com/docker/cli/opts/throttledevice.go index 0bf5dd66..9fb78843 100644 --- a/vendor/github.com/docker/cli/opts/throttledevice.go +++ b/vendor/github.com/docker/cli/opts/throttledevice.go @@ -14,14 +14,15 @@ type ValidatorThrottleFctType func(val string) (*blkiodev.ThrottleDevice, error) // ValidateThrottleBpsDevice validates that the specified string has a valid device-rate format. func ValidateThrottleBpsDevice(val string) (*blkiodev.ThrottleDevice, error) { - split := strings.SplitN(val, ":", 2) - if len(split) != 2 { + k, v, ok := strings.Cut(val, ":") + if !ok || k == "" { return nil, fmt.Errorf("bad format: %s", val) } - if !strings.HasPrefix(split[0], "/dev/") { + // TODO(thaJeztah): should we really validate this on the client? + if !strings.HasPrefix(k, "/dev/") { return nil, fmt.Errorf("bad format for device path: %s", val) } - rate, err := units.RAMInBytes(split[1]) + rate, err := units.RAMInBytes(v) if err != nil { return nil, fmt.Errorf("invalid rate for device: %s. The correct format is :[]. Number must be a positive integer. Unit is optional and can be kb, mb, or gb", val) } @@ -30,26 +31,27 @@ func ValidateThrottleBpsDevice(val string) (*blkiodev.ThrottleDevice, error) { } return &blkiodev.ThrottleDevice{ - Path: split[0], + Path: v, Rate: uint64(rate), }, nil } // ValidateThrottleIOpsDevice validates that the specified string has a valid device-rate format. func ValidateThrottleIOpsDevice(val string) (*blkiodev.ThrottleDevice, error) { - split := strings.SplitN(val, ":", 2) - if len(split) != 2 { + k, v, ok := strings.Cut(val, ":") + if !ok || k == "" { return nil, fmt.Errorf("bad format: %s", val) } - if !strings.HasPrefix(split[0], "/dev/") { + // TODO(thaJeztah): should we really validate this on the client? + if !strings.HasPrefix(k, "/dev/") { return nil, fmt.Errorf("bad format for device path: %s", val) } - rate, err := strconv.ParseUint(split[1], 10, 64) + rate, err := strconv.ParseUint(v, 10, 64) if err != nil { return nil, fmt.Errorf("invalid rate for device: %s. The correct format is :. Number must be a positive integer", val) } - return &blkiodev.ThrottleDevice{Path: split[0], Rate: rate}, nil + return &blkiodev.ThrottleDevice{Path: k, Rate: rate}, nil } // ThrottledeviceOpt defines a map of ThrottleDevices @@ -77,7 +79,7 @@ func (opt *ThrottledeviceOpt) Set(val string) error { } value = v } - (opt.values) = append((opt.values), value) + opt.values = append(opt.values, value) return nil } @@ -93,10 +95,7 @@ func (opt *ThrottledeviceOpt) String() string { // GetList returns a slice of pointers to ThrottleDevices. func (opt *ThrottledeviceOpt) GetList() []*blkiodev.ThrottleDevice { - var throttledevice []*blkiodev.ThrottleDevice - throttledevice = append(throttledevice, opt.values...) - - return throttledevice + return append([]*blkiodev.ThrottleDevice{}, opt.values...) } // Type returns the option type diff --git a/vendor/github.com/docker/cli/opts/weightdevice.go b/vendor/github.com/docker/cli/opts/weightdevice.go index f8057d0f..3077e3da 100644 --- a/vendor/github.com/docker/cli/opts/weightdevice.go +++ b/vendor/github.com/docker/cli/opts/weightdevice.go @@ -13,14 +13,15 @@ type ValidatorWeightFctType func(val string) (*blkiodev.WeightDevice, error) // ValidateWeightDevice validates that the specified string has a valid device-weight format. func ValidateWeightDevice(val string) (*blkiodev.WeightDevice, error) { - split := strings.SplitN(val, ":", 2) - if len(split) != 2 { + k, v, ok := strings.Cut(val, ":") + if !ok || k == "" { return nil, fmt.Errorf("bad format: %s", val) } - if !strings.HasPrefix(split[0], "/dev/") { + // TODO(thaJeztah): should we really validate this on the client? + if !strings.HasPrefix(k, "/dev/") { return nil, fmt.Errorf("bad format for device path: %s", val) } - weight, err := strconv.ParseUint(split[1], 10, 16) + weight, err := strconv.ParseUint(v, 10, 16) if err != nil { return nil, fmt.Errorf("invalid weight for device: %s", val) } @@ -29,7 +30,7 @@ func ValidateWeightDevice(val string) (*blkiodev.WeightDevice, error) { } return &blkiodev.WeightDevice{ - Path: split[0], + Path: k, Weight: uint16(weight), }, nil } @@ -42,9 +43,8 @@ type WeightdeviceOpt struct { // NewWeightdeviceOpt creates a new WeightdeviceOpt func NewWeightdeviceOpt(validator ValidatorWeightFctType) WeightdeviceOpt { - values := []*blkiodev.WeightDevice{} return WeightdeviceOpt{ - values: values, + values: []*blkiodev.WeightDevice{}, validator: validator, } } @@ -59,7 +59,7 @@ func (opt *WeightdeviceOpt) Set(val string) error { } value = v } - (opt.values) = append((opt.values), value) + opt.values = append(opt.values, value) return nil } diff --git a/vendor/github.com/docker/distribution/manifest/ocischema/builder.go b/vendor/github.com/docker/distribution/manifest/ocischema/builder.go new file mode 100644 index 00000000..b89bf5b7 --- /dev/null +++ b/vendor/github.com/docker/distribution/manifest/ocischema/builder.go @@ -0,0 +1,107 @@ +package ocischema + +import ( + "context" + "errors" + + "github.com/docker/distribution" + "github.com/docker/distribution/manifest" + "github.com/opencontainers/go-digest" + v1 "github.com/opencontainers/image-spec/specs-go/v1" +) + +// Builder is a type for constructing manifests. +type Builder struct { + // bs is a BlobService used to publish the configuration blob. + bs distribution.BlobService + + // configJSON references + configJSON []byte + + // layers is a list of layer descriptors that gets built by successive + // calls to AppendReference. + layers []distribution.Descriptor + + // Annotations contains arbitrary metadata relating to the targeted content. + annotations map[string]string + + // For testing purposes + mediaType string +} + +// NewManifestBuilder is used to build new manifests for the current schema +// version. It takes a BlobService so it can publish the configuration blob +// as part of the Build process, and annotations. +func NewManifestBuilder(bs distribution.BlobService, configJSON []byte, annotations map[string]string) distribution.ManifestBuilder { + mb := &Builder{ + bs: bs, + configJSON: make([]byte, len(configJSON)), + annotations: annotations, + mediaType: v1.MediaTypeImageManifest, + } + copy(mb.configJSON, configJSON) + + return mb +} + +// SetMediaType assigns the passed mediatype or error if the mediatype is not a +// valid media type for oci image manifests currently: "" or "application/vnd.oci.image.manifest.v1+json" +func (mb *Builder) SetMediaType(mediaType string) error { + if mediaType != "" && mediaType != v1.MediaTypeImageManifest { + return errors.New("invalid media type for OCI image manifest") + } + + mb.mediaType = mediaType + return nil +} + +// Build produces a final manifest from the given references. +func (mb *Builder) Build(ctx context.Context) (distribution.Manifest, error) { + m := Manifest{ + Versioned: manifest.Versioned{ + SchemaVersion: 2, + MediaType: mb.mediaType, + }, + Layers: make([]distribution.Descriptor, len(mb.layers)), + Annotations: mb.annotations, + } + copy(m.Layers, mb.layers) + + configDigest := digest.FromBytes(mb.configJSON) + + var err error + m.Config, err = mb.bs.Stat(ctx, configDigest) + switch err { + case nil: + // Override MediaType, since Put always replaces the specified media + // type with application/octet-stream in the descriptor it returns. + m.Config.MediaType = v1.MediaTypeImageConfig + return FromStruct(m) + case distribution.ErrBlobUnknown: + // nop + default: + return nil, err + } + + // Add config to the blob store + m.Config, err = mb.bs.Put(ctx, v1.MediaTypeImageConfig, mb.configJSON) + // Override MediaType, since Put always replaces the specified media + // type with application/octet-stream in the descriptor it returns. + m.Config.MediaType = v1.MediaTypeImageConfig + if err != nil { + return nil, err + } + + return FromStruct(m) +} + +// AppendReference adds a reference to the current ManifestBuilder. +func (mb *Builder) AppendReference(d distribution.Describable) error { + mb.layers = append(mb.layers, d.Descriptor()) + return nil +} + +// References returns the current references added to this builder. +func (mb *Builder) References() []distribution.Descriptor { + return mb.layers +} diff --git a/vendor/github.com/docker/distribution/manifest/ocischema/manifest.go b/vendor/github.com/docker/distribution/manifest/ocischema/manifest.go new file mode 100644 index 00000000..d51f8deb --- /dev/null +++ b/vendor/github.com/docker/distribution/manifest/ocischema/manifest.go @@ -0,0 +1,146 @@ +package ocischema + +import ( + "encoding/json" + "errors" + "fmt" + + "github.com/docker/distribution" + "github.com/docker/distribution/manifest" + "github.com/opencontainers/go-digest" + v1 "github.com/opencontainers/image-spec/specs-go/v1" +) + +var ( + // SchemaVersion provides a pre-initialized version structure for this + // packages version of the manifest. + SchemaVersion = manifest.Versioned{ + SchemaVersion: 2, // historical value here.. does not pertain to OCI or docker version + MediaType: v1.MediaTypeImageManifest, + } +) + +func init() { + ocischemaFunc := func(b []byte) (distribution.Manifest, distribution.Descriptor, error) { + if err := validateManifest(b); err != nil { + return nil, distribution.Descriptor{}, err + } + m := new(DeserializedManifest) + err := m.UnmarshalJSON(b) + if err != nil { + return nil, distribution.Descriptor{}, err + } + + dgst := digest.FromBytes(b) + return m, distribution.Descriptor{Digest: dgst, Size: int64(len(b)), MediaType: v1.MediaTypeImageManifest}, err + } + err := distribution.RegisterManifestSchema(v1.MediaTypeImageManifest, ocischemaFunc) + if err != nil { + panic(fmt.Sprintf("Unable to register manifest: %s", err)) + } +} + +// Manifest defines a ocischema manifest. +type Manifest struct { + manifest.Versioned + + // Config references the image configuration as a blob. + Config distribution.Descriptor `json:"config"` + + // Layers lists descriptors for the layers referenced by the + // configuration. + Layers []distribution.Descriptor `json:"layers"` + + // Annotations contains arbitrary metadata for the image manifest. + Annotations map[string]string `json:"annotations,omitempty"` +} + +// References returns the descriptors of this manifests references. +func (m Manifest) References() []distribution.Descriptor { + references := make([]distribution.Descriptor, 0, 1+len(m.Layers)) + references = append(references, m.Config) + references = append(references, m.Layers...) + return references +} + +// Target returns the target of this manifest. +func (m Manifest) Target() distribution.Descriptor { + return m.Config +} + +// DeserializedManifest wraps Manifest with a copy of the original JSON. +// It satisfies the distribution.Manifest interface. +type DeserializedManifest struct { + Manifest + + // canonical is the canonical byte representation of the Manifest. + canonical []byte +} + +// FromStruct takes a Manifest structure, marshals it to JSON, and returns a +// DeserializedManifest which contains the manifest and its JSON representation. +func FromStruct(m Manifest) (*DeserializedManifest, error) { + var deserialized DeserializedManifest + deserialized.Manifest = m + + var err error + deserialized.canonical, err = json.MarshalIndent(&m, "", " ") + return &deserialized, err +} + +// UnmarshalJSON populates a new Manifest struct from JSON data. +func (m *DeserializedManifest) UnmarshalJSON(b []byte) error { + m.canonical = make([]byte, len(b)) + // store manifest in canonical + copy(m.canonical, b) + + // Unmarshal canonical JSON into Manifest object + var manifest Manifest + if err := json.Unmarshal(m.canonical, &manifest); err != nil { + return err + } + + if manifest.MediaType != "" && manifest.MediaType != v1.MediaTypeImageManifest { + return fmt.Errorf("if present, mediaType in manifest should be '%s' not '%s'", + v1.MediaTypeImageManifest, manifest.MediaType) + } + + m.Manifest = manifest + + return nil +} + +// MarshalJSON returns the contents of canonical. If canonical is empty, +// marshals the inner contents. +func (m *DeserializedManifest) MarshalJSON() ([]byte, error) { + if len(m.canonical) > 0 { + return m.canonical, nil + } + + return nil, errors.New("JSON representation not initialized in DeserializedManifest") +} + +// Payload returns the raw content of the manifest. The contents can be used to +// calculate the content identifier. +func (m DeserializedManifest) Payload() (string, []byte, error) { + return v1.MediaTypeImageManifest, m.canonical, nil +} + +// unknownDocument represents a manifest, manifest list, or index that has not +// yet been validated +type unknownDocument struct { + Manifests interface{} `json:"manifests,omitempty"` +} + +// validateManifest returns an error if the byte slice is invalid JSON or if it +// contains fields that belong to a index +func validateManifest(b []byte) error { + var doc unknownDocument + if err := json.Unmarshal(b, &doc); err != nil { + return err + } + if doc.Manifests != nil { + return errors.New("ocimanifest: expected manifest but found index") + } + return nil +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 4e854017..a6d6622b 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -195,7 +195,7 @@ github.com/davecgh/go-spew/spew ## explicit; go 1.18 github.com/distribution/distribution/v3/digestset github.com/distribution/distribution/v3/reference -# github.com/docker/cli v23.0.0-rc.1+incompatible +# github.com/docker/cli v23.0.0+incompatible ## explicit github.com/docker/cli/cli github.com/docker/cli/cli-plugins/manager @@ -230,6 +230,7 @@ github.com/docker/distribution github.com/docker/distribution/digestset github.com/docker/distribution/manifest github.com/docker/distribution/manifest/manifestlist +github.com/docker/distribution/manifest/ocischema github.com/docker/distribution/manifest/schema2 github.com/docker/distribution/metrics github.com/docker/distribution/reference