mirror of https://github.com/docker/buildx.git
vendor: github.com/docker/cli v23.0.0
full diff: https://github.com/docker/cli/compare/v23.0.0-rc.1...v23.0.0 Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
parent
260117289b
commit
081447c9b1
2
go.mod
2
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
|
||||
|
|
4
go.sum
4
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=
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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}}
|
||||
`
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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 <device-path>:<number>[<unit>]. 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 <device-path>:<number>. 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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
107
vendor/github.com/docker/distribution/manifest/ocischema/builder.go
generated
vendored
Normal file
107
vendor/github.com/docker/distribution/manifest/ocischema/builder.go
generated
vendored
Normal file
|
@ -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
|
||||
}
|
146
vendor/github.com/docker/distribution/manifest/ocischema/manifest.go
generated
vendored
Normal file
146
vendor/github.com/docker/distribution/manifest/ocischema/manifest.go
generated
vendored
Normal file
|
@ -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
|
||||
}
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue