Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion cli/command/service/opts.go
Original file line number Diff line number Diff line change
Expand Up @@ -331,12 +331,14 @@ func (c *credentialSpecOpt) Set(value string) error {
c.source = value
c.value = &swarm.CredentialSpec{}
switch {
case strings.HasPrefix(value, "config://"):
c.value.Config = strings.TrimPrefix(value, "config://")
case strings.HasPrefix(value, "file://"):
c.value.File = strings.TrimPrefix(value, "file://")
case strings.HasPrefix(value, "registry://"):
c.value.Registry = strings.TrimPrefix(value, "registry://")
default:
return errors.New("Invalid credential spec - value must be prefixed file:// or registry:// followed by a value")
return errors.New(`invalid credential spec: value must be prefixed with "config://", "file://", or "registry://"`)
}

return nil
Expand Down
55 changes: 55 additions & 0 deletions cli/command/service/opts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,61 @@ import (
is "gotest.tools/assert/cmp"
)

func TestCredentialSpecOpt(t *testing.T) {
tests := []struct {
name string
in string
value swarm.CredentialSpec
expectedErr string
}{
{
name: "empty",
in: "",
value: swarm.CredentialSpec{},
expectedErr: `invalid credential spec: value must be prefixed with "config://", "file://", or "registry://"`,
},
{
name: "no-prefix",
in: "noprefix",
value: swarm.CredentialSpec{},
expectedErr: `invalid credential spec: value must be prefixed with "config://", "file://", or "registry://"`,
},
{
name: "config",
in: "config://0bt9dmxjvjiqermk6xrop3ekq",
value: swarm.CredentialSpec{Config: "0bt9dmxjvjiqermk6xrop3ekq"},
},
{
name: "file",
in: "file://somefile.json",
value: swarm.CredentialSpec{File: "somefile.json"},
},
{
name: "registry",
in: "registry://testing",
value: swarm.CredentialSpec{Registry: "testing"},
},
}

for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
var cs credentialSpecOpt

err := cs.Set(tc.in)

if tc.expectedErr != "" {
assert.Error(t, err, tc.expectedErr)
} else {
assert.NilError(t, err)
}

assert.Equal(t, cs.String(), tc.in)
assert.DeepEqual(t, cs.Value(), &tc.value)
})
}
}

func TestMemBytesString(t *testing.T) {
var mem opts.MemBytes = 1048576
assert.Check(t, is.Equal("1MiB", mem.String()))
Expand Down
23 changes: 19 additions & 4 deletions cli/compose/convert/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -600,11 +600,26 @@ func convertDNSConfig(DNS []string, DNSSearch []string) (*swarm.DNSConfig, error
}

func convertCredentialSpec(spec composetypes.CredentialSpecConfig) (*swarm.CredentialSpec, error) {
if spec.File == "" && spec.Registry == "" {
return nil, nil
var o []string

// Config was added in API v1.40
if spec.Config != "" {
o = append(o, `"Config"`)
}
if spec.File != "" {
o = append(o, `"File"`)
}
if spec.File != "" && spec.Registry != "" {
return nil, errors.New("Invalid credential spec - must provide one of `File` or `Registry`")
if spec.Registry != "" {
o = append(o, `"Registry"`)
}
l := len(o)
switch {
case l == 0:
return nil, nil
case l == 2:
return nil, errors.Errorf("invalid credential spec: cannot specify both %s and %s", o[0], o[1])
case l > 2:
return nil, errors.Errorf("invalid credential spec: cannot specify both %s, and %s", strings.Join(o[:l-1], ", "), o[l-1])
}
swarmCredSpec := swarm.CredentialSpec(spec)
return &swarmCredSpec, nil
Expand Down
79 changes: 57 additions & 22 deletions cli/compose/convert/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -314,30 +314,65 @@ func TestConvertDNSConfigSearch(t *testing.T) {
}

func TestConvertCredentialSpec(t *testing.T) {
swarmSpec, err := convertCredentialSpec(composetypes.CredentialSpecConfig{})
assert.NilError(t, err)
assert.Check(t, is.Nil(swarmSpec))

swarmSpec, err = convertCredentialSpec(composetypes.CredentialSpecConfig{
File: "/foo",
})
assert.NilError(t, err)
assert.Check(t, is.Equal(swarmSpec.File, "/foo"))
assert.Check(t, is.Equal(swarmSpec.Registry, ""))
tests := []struct {
name string
in composetypes.CredentialSpecConfig
out *swarm.CredentialSpec
expectedErr string
}{
{
name: "empty",
},
{
name: "config-and-file",
in: composetypes.CredentialSpecConfig{Config: "0bt9dmxjvjiqermk6xrop3ekq", File: "somefile.json"},
expectedErr: `invalid credential spec: cannot specify both "Config" and "File"`,
},
{
name: "config-and-registry",
in: composetypes.CredentialSpecConfig{Config: "0bt9dmxjvjiqermk6xrop3ekq", Registry: "testing"},
expectedErr: `invalid credential spec: cannot specify both "Config" and "Registry"`,
},
{
name: "file-and-registry",
in: composetypes.CredentialSpecConfig{File: "somefile.json", Registry: "testing"},
expectedErr: `invalid credential spec: cannot specify both "File" and "Registry"`,
},
{
name: "config-and-file-and-registry",
in: composetypes.CredentialSpecConfig{Config: "0bt9dmxjvjiqermk6xrop3ekq", File: "somefile.json", Registry: "testing"},
expectedErr: `invalid credential spec: cannot specify both "Config", "File", and "Registry"`,
},
{
name: "config",
in: composetypes.CredentialSpecConfig{Config: "0bt9dmxjvjiqermk6xrop3ekq"},
out: &swarm.CredentialSpec{Config: "0bt9dmxjvjiqermk6xrop3ekq"},
},
{
name: "file",
in: composetypes.CredentialSpecConfig{File: "somefile.json"},
out: &swarm.CredentialSpec{File: "somefile.json"},
},
{
name: "registry",
in: composetypes.CredentialSpecConfig{Registry: "testing"},
out: &swarm.CredentialSpec{Registry: "testing"},
},
}

swarmSpec, err = convertCredentialSpec(composetypes.CredentialSpecConfig{
Registry: "foo",
})
assert.NilError(t, err)
assert.Check(t, is.Equal(swarmSpec.File, ""))
assert.Check(t, is.Equal(swarmSpec.Registry, "foo"))
for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
swarmSpec, err := convertCredentialSpec(tc.in)

swarmSpec, err = convertCredentialSpec(composetypes.CredentialSpecConfig{
File: "/asdf",
Registry: "foo",
})
assert.Check(t, is.ErrorContains(err, ""))
assert.Check(t, is.Nil(swarmSpec))
if tc.expectedErr != "" {
assert.Error(t, err, tc.expectedErr)
} else {
assert.NilError(t, err)
}
assert.DeepEqual(t, swarmSpec, tc.out)
})
}
}

func TestConvertUpdateConfigOrder(t *testing.T) {
Expand Down
14 changes: 14 additions & 0 deletions cli/compose/loader/loader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,20 @@ configs:
assert.Assert(t, is.Len(actual.Configs, 1))
}

func TestLoadV38(t *testing.T) {
actual, err := loadYAML(`
version: "3.8"
services:
foo:
image: busybox
credential_spec:
config: "0bt9dmxjvjiqermk6xrop3ekq"
`)
assert.NilError(t, err)
assert.Assert(t, is.Len(actual.Services, 1))
assert.Check(t, is.Equal(actual.Services[0].CredentialSpec.Config, "0bt9dmxjvjiqermk6xrop3ekq"))
}

func TestParseAndLoad(t *testing.T) {
actual, err := loadYAML(sampleYAML)
assert.NilError(t, err)
Expand Down
72 changes: 36 additions & 36 deletions cli/compose/schema/bindata.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions cli/compose/schema/data/config_schema_v3.8.json
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@
"credential_spec": {
"type": "object",
"properties": {
"config": {"type": "string"},
"file": {"type": "string"},
"registry": {"type": "string"}
},
Expand Down
4 changes: 2 additions & 2 deletions cli/compose/schema/schema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ func TestValidateCredentialSpecs(t *testing.T) {
{version: "3.5", expectedErr: "config"},
{version: "3.6", expectedErr: "config"},
{version: "3.7", expectedErr: "config"},
{version: "3.8", expectedErr: "something"},
{version: "3.8"},
}

for _, tc := range tests {
Expand All @@ -104,7 +104,7 @@ func TestValidateCredentialSpecs(t *testing.T) {
"foo": dict{
"image": "busybox",
"credential_spec": dict{
tc.expectedErr: "foobar",
"config": "foobar",
},
},
},
Expand Down
3 changes: 1 addition & 2 deletions cli/compose/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -500,8 +500,7 @@ func (e External) MarshalJSON() ([]byte, error) {

// CredentialSpecConfig for credential spec on Windows
type CredentialSpecConfig struct {
// @TODO Config is not yet in use
Config string `yaml:"-" json:"-"` // Config was added in API v1.40
Config string `yaml:",omitempty" json:"config,omitempty"` // Config was added in API v1.40
File string `yaml:",omitempty" json:"file,omitempty"`
Registry string `yaml:",omitempty" json:"registry,omitempty"`
}
Expand Down