Skip to content

Commit

Permalink
add support for config credentialspecs to compose
Browse files Browse the repository at this point in the history
Signed-off-by: Drew Erny <drew.erny@docker.com>
  • Loading branch information
dperny committed Apr 12, 2019
1 parent 4cacd13 commit 42ec51e
Show file tree
Hide file tree
Showing 3 changed files with 125 additions and 17 deletions.
3 changes: 3 additions & 0 deletions cli/command/service/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,9 @@ func setConfigs(apiClient client.ConfigAPIClient, service *swarm.ServiceSpec, op
for _, config := range configs {
if config.ConfigName == cs.Config {
service.TaskTemplate.ContainerSpec.Privileges.CredentialSpec.Config = config.ConfigID
// we've found the right config, no need to keep iterating
// through the rest of them.
break
}
}
}
Expand Down
78 changes: 68 additions & 10 deletions cli/compose/convert/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func Services(
if err != nil {
return nil, errors.Wrapf(err, "service %s", service.Name)
}
configs, err := convertServiceConfigObjs(client, namespace, service.Configs, config.Configs)
configs, err := convertServiceConfigObjs(client, namespace, service, config.Configs)
if err != nil {
return nil, errors.Wrapf(err, "service %s", service.Name)
}
Expand Down Expand Up @@ -109,7 +109,9 @@ func Service(
}

var privileges swarm.Privileges
privileges.CredentialSpec, err = convertCredentialSpec(service.CredentialSpec)
privileges.CredentialSpec, err = convertCredentialSpec(
namespace, service.CredentialSpec, configs,
)
if err != nil {
return swarm.ServiceSpec{}, err
}
Expand Down Expand Up @@ -286,11 +288,17 @@ func convertServiceSecrets(
return secrs, err
}

// convertServiceConfigObjs takes an API client, a namespace, a ServiceConfig,
// and a set of compose Config specs, and creates the swarm ConfigReferences
// required by the serivce. Unlike convertServiceSecrets, this takes the whole
// ServiceConfig, because some Configs may be needed as a result of other
// fields (like CredentialSpecs).
//
// TODO: fix configs API so that ConfigsAPIClient is not required here
func convertServiceConfigObjs(
client client.ConfigAPIClient,
namespace Namespace,
configs []composetypes.ServiceConfigObjConfig,
service composetypes.ServiceConfig,
configSpecs map[string]composetypes.ConfigObjConfig,
) ([]*swarm.ConfigReference, error) {
refs := []*swarm.ConfigReference{}
Expand All @@ -302,7 +310,7 @@ func convertServiceConfigObjs(
}
return composetypes.FileObjectConfig(configSpec), nil
}
for _, config := range configs {
for _, config := range service.Configs {
obj, err := convertFileObject(namespace, composetypes.FileReferenceConfig(config), lookup)
if err != nil {
return nil, err
Expand All @@ -315,6 +323,38 @@ func convertServiceConfigObjs(
})
}

// finally, after converting all of the file objects, create any
// Runtime-type configs that are needed. these are configs that are not
// mounted into the container, but are used in some other way by the
// container runtime. Currently, this only means CredentialSpecs, but in
// the future it may be used for other fields

// grab the CredentialSpec out of the Service
credSpec := service.CredentialSpec
// if the credSpec uses a config, then we should grab the config name, and
// create a config reference for it. A File or Registry-type CredentialSpec
// does not need this operation.
if credSpec.Config != "" {
// look up the config in the configSpecs.
obj, err := lookup(credSpec.Config)
if err != nil {
return nil, err
}

// get the actual correct name.
name := namespace.Scope(credSpec.Config)
if obj.Name != "" {
name = obj.Name
}

// now append a Runtime-type config.
refs = append(refs, &swarm.ConfigReference{
ConfigName: name,
Runtime: &swarm.ConfigReferenceRuntimeTarget{},
})

}

confs, err := servicecli.ParseConfigs(client, refs)
if err != nil {
return nil, err
Expand Down Expand Up @@ -342,11 +382,6 @@ func convertFileObject(
config composetypes.FileReferenceConfig,
lookup func(key string) (composetypes.FileObjectConfig, error),
) (swarmReferenceObject, error) {
target := config.Target
if target == "" {
target = config.Source
}

obj, err := lookup(config.Source)
if err != nil {
return swarmReferenceObject{}, err
Expand All @@ -357,6 +392,11 @@ func convertFileObject(
source = obj.Name
}

target := config.Target
if target == "" {
target = config.Source
}

uid := config.UID
gid := config.GID
if uid == "" {
Expand Down Expand Up @@ -599,7 +639,7 @@ func convertDNSConfig(DNS []string, DNSSearch []string) (*swarm.DNSConfig, error
return nil, nil
}

func convertCredentialSpec(spec composetypes.CredentialSpecConfig) (*swarm.CredentialSpec, error) {
func convertCredentialSpec(namespace Namespace, spec composetypes.CredentialSpecConfig, refs []*swarm.ConfigReference) (*swarm.CredentialSpec, error) {
var o []string

// Config was added in API v1.40
Expand All @@ -622,5 +662,23 @@ func convertCredentialSpec(spec composetypes.CredentialSpecConfig) (*swarm.Crede
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)
// if we're using a swarm Config for the credential spec, over-write it
// here with the config ID
if swarmCredSpec.Config != "" {
for _, config := range refs {
if swarmCredSpec.Config == config.ConfigName {
swarmCredSpec.Config = config.ConfigID
return &swarmCredSpec, nil
}
}
// if none of the configs match, try namespacing
for _, config := range refs {
if namespace.Scope(swarmCredSpec.Config) == config.ConfigName {
swarmCredSpec.Config = config.ConfigID
return &swarmCredSpec, nil
}
}
return nil, errors.Errorf("invalid credential spec: spec specifies config %v, but no such config can be found", swarmCredSpec.Config)
}
return &swarmCredSpec, nil
}
61 changes: 54 additions & 7 deletions cli/compose/convert/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,7 @@ func TestConvertCredentialSpec(t *testing.T) {
name string
in composetypes.CredentialSpecConfig
out *swarm.CredentialSpec
configs []*swarm.ConfigReference
expectedErr string
}{
{
Expand All @@ -343,10 +344,41 @@ func TestConvertCredentialSpec(t *testing.T) {
in: composetypes.CredentialSpecConfig{Config: "0bt9dmxjvjiqermk6xrop3ekq", File: "somefile.json", Registry: "testing"},
expectedErr: `invalid credential spec: cannot specify both "Config", "File", and "Registry"`,
},
{
name: "missing-config-reference",
in: composetypes.CredentialSpecConfig{Config: "missing"},
expectedErr: "invalid credential spec: spec specifies config missing, but no such config can be found",
configs: []*swarm.ConfigReference{
{
ConfigName: "someName",
ConfigID: "missing",
},
},
},
{
name: "namespaced-config",
in: composetypes.CredentialSpecConfig{Config: "name"},
configs: []*swarm.ConfigReference{
{
ConfigName: "namespaced-config_name",
ConfigID: "someID",
},
},
out: &swarm.CredentialSpec{Config: "someID"},
},
{
name: "config",
in: composetypes.CredentialSpecConfig{Config: "0bt9dmxjvjiqermk6xrop3ekq"},
out: &swarm.CredentialSpec{Config: "0bt9dmxjvjiqermk6xrop3ekq"},
in: composetypes.CredentialSpecConfig{Config: "someName"},
configs: []*swarm.ConfigReference{
{
ConfigName: "someOtherName",
ConfigID: "someOtherID",
}, {
ConfigName: "someName",
ConfigID: "someID",
},
},
out: &swarm.CredentialSpec{Config: "someID"},
},
{
name: "file",
Expand All @@ -363,7 +395,8 @@ func TestConvertCredentialSpec(t *testing.T) {
for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
swarmSpec, err := convertCredentialSpec(tc.in)
namespace := NewNamespace(tc.name)
swarmSpec, err := convertCredentialSpec(namespace, tc.in, tc.configs)

if tc.expectedErr != "" {
assert.Error(t, err, tc.expectedErr)
Expand Down Expand Up @@ -502,9 +535,14 @@ func TestConvertServiceSecrets(t *testing.T) {

func TestConvertServiceConfigs(t *testing.T) {
namespace := Namespace{name: "foo"}
configs := []composetypes.ServiceConfigObjConfig{
{Source: "foo_config"},
{Source: "bar_config"},
service := composetypes.ServiceConfig{
Configs: []composetypes.ServiceConfigObjConfig{
{Source: "foo_config"},
{Source: "bar_config"},
},
CredentialSpec: composetypes.CredentialSpecConfig{
Config: "baz_config",
},
}
configSpecs := map[string]composetypes.ConfigObjConfig{
"foo_config": {
Expand All @@ -513,18 +551,23 @@ func TestConvertServiceConfigs(t *testing.T) {
"bar_config": {
Name: "bar_config",
},
"baz_config": {
Name: "baz_config",
},
}
client := &fakeClient{
configListFunc: func(opts types.ConfigListOptions) ([]swarm.Config, error) {
assert.Check(t, is.Contains(opts.Filters.Get("name"), "foo_config"))
assert.Check(t, is.Contains(opts.Filters.Get("name"), "bar_config"))
assert.Check(t, is.Contains(opts.Filters.Get("name"), "baz_config"))
return []swarm.Config{
{Spec: swarm.ConfigSpec{Annotations: swarm.Annotations{Name: "foo_config"}}},
{Spec: swarm.ConfigSpec{Annotations: swarm.Annotations{Name: "bar_config"}}},
{Spec: swarm.ConfigSpec{Annotations: swarm.Annotations{Name: "baz_config"}}},
}, nil
},
}
refs, err := convertServiceConfigObjs(client, namespace, configs, configSpecs)
refs, err := convertServiceConfigObjs(client, namespace, service, configSpecs)
assert.NilError(t, err)
expected := []*swarm.ConfigReference{
{
Expand All @@ -536,6 +579,10 @@ func TestConvertServiceConfigs(t *testing.T) {
Mode: 0444,
},
},
{
ConfigName: "baz_config",
Runtime: &swarm.ConfigReferenceRuntimeTarget{},
},
{
ConfigName: "foo_config",
File: &swarm.ConfigReferenceFileTarget{
Expand Down

0 comments on commit 42ec51e

Please sign in to comment.