Merge pull request #668 from ilyasotkov/667-secret-config-names

Add "name" field to secrets and configs to allow interpolation in Compose files
This commit is contained in:
Daniel Nephin 2017-11-22 13:33:59 -05:00 committed by GitHub
commit 8d41ba082b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 117 additions and 19 deletions

View File

@ -138,9 +138,15 @@ func fileObjectConfig(namespace Namespace, name string, obj composetypes.FileObj
return swarmFileObject{}, err return swarmFileObject{}, err
} }
if obj.Name != "" {
name = obj.Name
} else {
name = namespace.Scope(name)
}
return swarmFileObject{ return swarmFileObject{
Annotations: swarm.Annotations{ Annotations: swarm.Annotations{
Name: namespace.Scope(name), Name: name,
Labels: AddStackLabel(namespace, obj.Labels), Labels: AddStackLabel(namespace, obj.Labels),
}, },
Data: data, Data: data,

View File

@ -258,14 +258,14 @@ func convertServiceSecrets(
refs := []*swarm.SecretReference{} refs := []*swarm.SecretReference{}
lookup := func(key string) (composetypes.FileObjectConfig, error) { lookup := func(key string) (composetypes.FileObjectConfig, error) {
configSpec, exists := secretSpecs[key] secretSpec, exists := secretSpecs[key]
if !exists { if !exists {
return composetypes.FileObjectConfig{}, errors.Errorf("undefined secret %q", key) return composetypes.FileObjectConfig{}, errors.Errorf("undefined secret %q", key)
} }
return composetypes.FileObjectConfig(configSpec), nil return composetypes.FileObjectConfig(secretSpec), nil
} }
for _, config := range secrets { for _, secret := range secrets {
obj, err := convertFileObject(namespace, composetypes.FileReferenceConfig(config), lookup) obj, err := convertFileObject(namespace, composetypes.FileReferenceConfig(secret), lookup)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -353,8 +353,8 @@ func convertFileObject(
} }
source := namespace.Scope(config.Source) source := namespace.Scope(config.Source)
if obj.External.External { if obj.Name != "" {
source = obj.External.Name source = obj.Name
} }
uid := config.UID uid := config.UID

View File

@ -490,7 +490,7 @@ func LoadSecrets(source map[string]interface{}, details types.ConfigDetails) (ma
return secrets, err return secrets, err
} }
for name, secret := range secrets { for name, secret := range secrets {
obj, err := loadFileObjectConfig(name, types.FileObjectConfig(secret), details) obj, err := loadFileObjectConfig(name, "secret", types.FileObjectConfig(secret), details)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -507,7 +507,7 @@ func LoadConfigObjs(source map[string]interface{}, details types.ConfigDetails)
return configs, err return configs, err
} }
for name, config := range configs { for name, config := range configs {
obj, err := loadFileObjectConfig(name, types.FileObjectConfig(config), details) obj, err := loadFileObjectConfig(name, "config", types.FileObjectConfig(config), details)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -516,13 +516,29 @@ func LoadConfigObjs(source map[string]interface{}, details types.ConfigDetails)
return configs, nil return configs, nil
} }
func loadFileObjectConfig(name string, obj types.FileObjectConfig, details types.ConfigDetails) (types.FileObjectConfig, error) { func loadFileObjectConfig(name string, objType string, obj types.FileObjectConfig, details types.ConfigDetails) (types.FileObjectConfig, error) {
if obj.External.External && obj.External.Name == "" { // if "external: true"
obj.External.Name = name if obj.External.External {
} // handle deprecated external.name
if obj.File != "" { if obj.External.Name != "" {
if obj.Name != "" {
return obj, errors.Errorf("%[1]s %[2]s: %[1]s.external.name and %[1]s.name conflict; only use %[1]s.name", objType, name)
}
if versions.GreaterThanOrEqualTo(details.Version, "3.5") {
logrus.Warnf("%[1]s %[2]s: %[1]s.external.name is deprecated in favor of %[1]s.name", objType, name)
}
obj.Name = obj.External.Name
obj.External.Name = ""
} else {
if obj.Name == "" {
obj.Name = name
}
}
// if not "external: true"
} else {
obj.File = absPath(details.WorkingDir, obj.File) obj.File = absPath(details.WorkingDir, obj.File)
} }
return obj, nil return obj, nil
} }

View File

@ -626,10 +626,10 @@ networks:
}, },
}, },
Configs: map[string]types.ConfigObjConfig{ Configs: map[string]types.ConfigObjConfig{
"appconfig": {External: types.External{External: true, Name: "appconfig"}}, "appconfig": {External: types.External{External: true}, Name: "appconfig"},
}, },
Secrets: map[string]types.SecretConfig{ Secrets: map[string]types.SecretConfig{
"super": {External: types.External{External: true, Name: "super"}}, "super": {External: types.External{External: true}, Name: "super"},
}, },
Volumes: map[string]types.VolumeConfig{ Volumes: map[string]types.VolumeConfig{
"data": {External: types.External{External: true}, Name: "data"}, "data": {External: types.External{External: true}, Name: "data"},
@ -1464,11 +1464,24 @@ services:
image: busybox image: busybox
isolation: process isolation: process
configs: configs:
super: foo:
name: fooqux
external: true external: true
bar:
name: barqux
file: ./example1.env
secrets:
foo:
name: fooqux
external: true
bar:
name: barqux
file: ./full-example.yml
`) `)
require.NoError(t, err) require.NoError(t, err)
require.Len(t, actual.Services, 1) assert.Len(t, actual.Services, 1)
assert.Len(t, actual.Secrets, 2)
assert.Len(t, actual.Configs, 2)
assert.Equal(t, "process", actual.Services[0].Isolation) assert.Equal(t, "process", actual.Services[0].Isolation)
} }
@ -1488,3 +1501,44 @@ configs:
require.Len(t, actual.Services, 1) require.Len(t, actual.Services, 1)
assert.Equal(t, "invalid", actual.Services[0].Isolation) assert.Equal(t, "invalid", actual.Services[0].Isolation)
} }
func TestInvalidSecretExternalNameAndNameCombination(t *testing.T) {
_, err := loadYAML(`
version: "3.5"
secrets:
external_secret:
name: user_specified_name
external:
name: external_name
`)
require.Error(t, err)
assert.Contains(t, err.Error(), "secret.external.name and secret.name conflict; only use secret.name")
assert.Contains(t, err.Error(), "external_secret")
}
func TestLoadSecretsWarnOnDeprecatedExternalNameVersion35(t *testing.T) {
buf, cleanup := patchLogrus()
defer cleanup()
source := map[string]interface{}{
"foo": map[string]interface{}{
"external": map[string]interface{}{
"name": "oops",
},
},
}
details := types.ConfigDetails{
Version: "3.5",
}
secrets, err := LoadSecrets(source, details)
require.NoError(t, err)
expected := map[string]types.SecretConfig{
"foo": {
Name: "oops",
External: types.External{External: true},
},
}
assert.Equal(t, expected, secrets)
assert.Contains(t, buf.String(), "secret.external.name is deprecated")
}

File diff suppressed because one or more lines are too long

View File

@ -470,6 +470,7 @@
"id": "#/definitions/secret", "id": "#/definitions/secret",
"type": "object", "type": "object",
"properties": { "properties": {
"name": {"type": "string"},
"file": {"type": "string"}, "file": {"type": "string"},
"external": { "external": {
"type": ["boolean", "object"], "type": ["boolean", "object"],
@ -486,6 +487,7 @@
"id": "#/definitions/config", "id": "#/definitions/config",
"type": "object", "type": "object",
"properties": { "properties": {
"name": {"type": "string"},
"file": {"type": "string"}, "file": {"type": "string"},
"external": { "external": {
"type": ["boolean", "object"], "type": ["boolean", "object"],

View File

@ -46,6 +46,25 @@ func TestValidateAllowsXTopLevelFields(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
} }
func TestValidateSecretConfigNames(t *testing.T) {
config := dict{
"version": "3.5",
"configs": dict{
"bar": dict{
"name": "foobar",
},
},
"secrets": dict{
"baz": dict{
"name": "foobaz",
},
},
}
err := Validate(config, "3.5")
assert.NoError(t, err)
}
func TestValidateInvalidVersion(t *testing.T) { func TestValidateInvalidVersion(t *testing.T) {
config := dict{ config := dict{
"version": "2.1", "version": "2.1",

View File

@ -348,6 +348,7 @@ type CredentialSpecConfig struct {
// FileObjectConfig is a config type for a file used by a service // FileObjectConfig is a config type for a file used by a service
type FileObjectConfig struct { type FileObjectConfig struct {
Name string
File string File string
External External External External
Labels Labels Labels Labels