diff --git a/cli/command/service/create.go b/cli/command/service/create.go index ae359bd68717..4c709eb3a6f9 100644 --- a/cli/command/service/create.go +++ b/cli/command/service/create.go @@ -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 } } } diff --git a/cli/compose/convert/service.go b/cli/compose/convert/service.go index 0fe40bf3a7bc..2da8c6dc5545 100644 --- a/cli/compose/convert/service.go +++ b/cli/compose/convert/service.go @@ -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) } @@ -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 } @@ -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{} @@ -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 @@ -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 @@ -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 @@ -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 == "" { @@ -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 @@ -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 } diff --git a/cli/compose/convert/service_test.go b/cli/compose/convert/service_test.go index f275fb874cad..42050386672d 100644 --- a/cli/compose/convert/service_test.go +++ b/cli/compose/convert/service_test.go @@ -318,6 +318,7 @@ func TestConvertCredentialSpec(t *testing.T) { name string in composetypes.CredentialSpecConfig out *swarm.CredentialSpec + configs []*swarm.ConfigReference expectedErr string }{ { @@ -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", @@ -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) @@ -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": { @@ -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{ { @@ -536,6 +579,10 @@ func TestConvertServiceConfigs(t *testing.T) { Mode: 0444, }, }, + { + ConfigName: "baz_config", + Runtime: &swarm.ConfigReferenceRuntimeTarget{}, + }, { ConfigName: "foo_config", File: &swarm.ConfigReferenceFileTarget{ diff --git a/cli/compose/schema/bindata.go b/cli/compose/schema/bindata.go index 6651be946a46..96699a50e101 100644 --- a/cli/compose/schema/bindata.go +++ b/cli/compose/schema/bindata.go @@ -510,45 +510,45 @@ bnBpPlHfjORjkTRf1wyAwiYqMXd9/G6313QfoXs6/sbZ66r6e179PwAA//8ZL3SpvkUAAA== "/data/config_schema_v3.8.json": { local: "data/config_schema_v3.8.json", - size: 18048, + size: 18049, modtime: 1518458244, compressed: ` -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 -ek8n4jg2XlV/T6v/BgAA//8BShmMgEYAAA== +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 +0Ik4jo1X1d/n1f8DAAD//xO2FMuBRgAA `, }, diff --git a/cli/compose/schema/data/config_schema_v3.8.json b/cli/compose/schema/data/config_schema_v3.8.json index 08a9719c785d..ceeefe80937b 100644 --- a/cli/compose/schema/data/config_schema_v3.8.json +++ b/cli/compose/schema/data/config_schema_v3.8.json @@ -115,7 +115,7 @@ "target": {"type": "string"}, "uid": {"type": "string"}, "gid": {"type": "string"}, - "mode": {"type": "number"} + "mode": {"type": "number"}, } } ]