add support for config credentialspecs to compose

Signed-off-by: Drew Erny <drew.erny@docker.com>
This commit is contained in:
Drew Erny 2019-04-01 13:38:11 -05:00
parent 4cacd1304a
commit 42ec51e1ae
3 changed files with 125 additions and 17 deletions

View File

@ -169,6 +169,9 @@ func setConfigs(apiClient client.ConfigAPIClient, service *swarm.ServiceSpec, op
for _, config := range configs { for _, config := range configs {
if config.ConfigName == cs.Config { if config.ConfigName == cs.Config {
service.TaskTemplate.ContainerSpec.Privileges.CredentialSpec.Config = config.ConfigID 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
} }
} }
} }

View File

@ -40,7 +40,7 @@ func Services(
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "service %s", service.Name) 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 { if err != nil {
return nil, errors.Wrapf(err, "service %s", service.Name) return nil, errors.Wrapf(err, "service %s", service.Name)
} }
@ -109,7 +109,9 @@ func Service(
} }
var privileges swarm.Privileges var privileges swarm.Privileges
privileges.CredentialSpec, err = convertCredentialSpec(service.CredentialSpec) privileges.CredentialSpec, err = convertCredentialSpec(
namespace, service.CredentialSpec, configs,
)
if err != nil { if err != nil {
return swarm.ServiceSpec{}, err return swarm.ServiceSpec{}, err
} }
@ -286,11 +288,17 @@ func convertServiceSecrets(
return secrs, err 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 // TODO: fix configs API so that ConfigsAPIClient is not required here
func convertServiceConfigObjs( func convertServiceConfigObjs(
client client.ConfigAPIClient, client client.ConfigAPIClient,
namespace Namespace, namespace Namespace,
configs []composetypes.ServiceConfigObjConfig, service composetypes.ServiceConfig,
configSpecs map[string]composetypes.ConfigObjConfig, configSpecs map[string]composetypes.ConfigObjConfig,
) ([]*swarm.ConfigReference, error) { ) ([]*swarm.ConfigReference, error) {
refs := []*swarm.ConfigReference{} refs := []*swarm.ConfigReference{}
@ -302,7 +310,7 @@ func convertServiceConfigObjs(
} }
return composetypes.FileObjectConfig(configSpec), nil return composetypes.FileObjectConfig(configSpec), nil
} }
for _, config := range configs { for _, config := range service.Configs {
obj, err := convertFileObject(namespace, composetypes.FileReferenceConfig(config), lookup) obj, err := convertFileObject(namespace, composetypes.FileReferenceConfig(config), lookup)
if err != nil { if err != nil {
return nil, err return nil, err
@ -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) confs, err := servicecli.ParseConfigs(client, refs)
if err != nil { if err != nil {
return nil, err return nil, err
@ -342,11 +382,6 @@ func convertFileObject(
config composetypes.FileReferenceConfig, config composetypes.FileReferenceConfig,
lookup func(key string) (composetypes.FileObjectConfig, error), lookup func(key string) (composetypes.FileObjectConfig, error),
) (swarmReferenceObject, error) { ) (swarmReferenceObject, error) {
target := config.Target
if target == "" {
target = config.Source
}
obj, err := lookup(config.Source) obj, err := lookup(config.Source)
if err != nil { if err != nil {
return swarmReferenceObject{}, err return swarmReferenceObject{}, err
@ -357,6 +392,11 @@ func convertFileObject(
source = obj.Name source = obj.Name
} }
target := config.Target
if target == "" {
target = config.Source
}
uid := config.UID uid := config.UID
gid := config.GID gid := config.GID
if uid == "" { if uid == "" {
@ -599,7 +639,7 @@ func convertDNSConfig(DNS []string, DNSSearch []string) (*swarm.DNSConfig, error
return nil, nil 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 var o []string
// Config was added in API v1.40 // Config was added in API v1.40
@ -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]) 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) 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 return &swarmCredSpec, nil
} }

View File

@ -318,6 +318,7 @@ func TestConvertCredentialSpec(t *testing.T) {
name string name string
in composetypes.CredentialSpecConfig in composetypes.CredentialSpecConfig
out *swarm.CredentialSpec out *swarm.CredentialSpec
configs []*swarm.ConfigReference
expectedErr string expectedErr string
}{ }{
{ {
@ -343,10 +344,41 @@ func TestConvertCredentialSpec(t *testing.T) {
in: composetypes.CredentialSpecConfig{Config: "0bt9dmxjvjiqermk6xrop3ekq", File: "somefile.json", Registry: "testing"}, in: composetypes.CredentialSpecConfig{Config: "0bt9dmxjvjiqermk6xrop3ekq", File: "somefile.json", Registry: "testing"},
expectedErr: `invalid credential spec: cannot specify both "Config", "File", and "Registry"`, 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", name: "config",
in: composetypes.CredentialSpecConfig{Config: "0bt9dmxjvjiqermk6xrop3ekq"}, in: composetypes.CredentialSpecConfig{Config: "someName"},
out: &swarm.CredentialSpec{Config: "0bt9dmxjvjiqermk6xrop3ekq"}, configs: []*swarm.ConfigReference{
{
ConfigName: "someOtherName",
ConfigID: "someOtherID",
}, {
ConfigName: "someName",
ConfigID: "someID",
},
},
out: &swarm.CredentialSpec{Config: "someID"},
}, },
{ {
name: "file", name: "file",
@ -363,7 +395,8 @@ func TestConvertCredentialSpec(t *testing.T) {
for _, tc := range tests { for _, tc := range tests {
tc := tc tc := tc
t.Run(tc.name, func(t *testing.T) { 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 != "" { if tc.expectedErr != "" {
assert.Error(t, err, tc.expectedErr) assert.Error(t, err, tc.expectedErr)
@ -502,9 +535,14 @@ func TestConvertServiceSecrets(t *testing.T) {
func TestConvertServiceConfigs(t *testing.T) { func TestConvertServiceConfigs(t *testing.T) {
namespace := Namespace{name: "foo"} namespace := Namespace{name: "foo"}
configs := []composetypes.ServiceConfigObjConfig{ service := composetypes.ServiceConfig{
Configs: []composetypes.ServiceConfigObjConfig{
{Source: "foo_config"}, {Source: "foo_config"},
{Source: "bar_config"}, {Source: "bar_config"},
},
CredentialSpec: composetypes.CredentialSpecConfig{
Config: "baz_config",
},
} }
configSpecs := map[string]composetypes.ConfigObjConfig{ configSpecs := map[string]composetypes.ConfigObjConfig{
"foo_config": { "foo_config": {
@ -513,18 +551,23 @@ func TestConvertServiceConfigs(t *testing.T) {
"bar_config": { "bar_config": {
Name: "bar_config", Name: "bar_config",
}, },
"baz_config": {
Name: "baz_config",
},
} }
client := &fakeClient{ client := &fakeClient{
configListFunc: func(opts types.ConfigListOptions) ([]swarm.Config, error) { 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"), "foo_config"))
assert.Check(t, is.Contains(opts.Filters.Get("name"), "bar_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{ return []swarm.Config{
{Spec: swarm.ConfigSpec{Annotations: swarm.Annotations{Name: "foo_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: "bar_config"}}},
{Spec: swarm.ConfigSpec{Annotations: swarm.Annotations{Name: "baz_config"}}},
}, nil }, nil
}, },
} }
refs, err := convertServiceConfigObjs(client, namespace, configs, configSpecs) refs, err := convertServiceConfigObjs(client, namespace, service, configSpecs)
assert.NilError(t, err) assert.NilError(t, err)
expected := []*swarm.ConfigReference{ expected := []*swarm.ConfigReference{
{ {
@ -536,6 +579,10 @@ func TestConvertServiceConfigs(t *testing.T) {
Mode: 0444, Mode: 0444,
}, },
}, },
{
ConfigName: "baz_config",
Runtime: &swarm.ConfigReferenceRuntimeTarget{},
},
{ {
ConfigName: "foo_config", ConfigName: "foo_config",
File: &swarm.ConfigReferenceFileTarget{ File: &swarm.ConfigReferenceFileTarget{