Add an `Extras` field on the compose config types.

That field is automaticaly populated with any `x-*` field in the yaml.
And marshalling the compose config struct put them back into place.

This make it possible to get those extra fields without re-inventing
the wheel (i.e. reimplementing 80% of the `cli/compose/*` packages.

Signed-off-by: Vincent Demeester <vincent@sbr.pm>
This commit is contained in:
Vincent Demeester 2018-06-25 10:51:56 +02:00
parent 1c20e3f601
commit 80c26f618e
No known key found for this signature in database
GPG Key ID: 083CC6FD6EB699A3
5 changed files with 155 additions and 28 deletions

View File

@ -278,6 +278,8 @@ services:
size: 10000
working_dir: /code
x-bar: baz
x-foo: bar
networks:
# Entries can be null, which specifies simply that a network
@ -318,6 +320,8 @@ networks:
# can be referred to within this file as "other-external-network"
external:
name: my-cool-network
x-bar: baz
x-foo: bar
volumes:
# Entries can be null, which specifies simply that a volume
@ -361,6 +365,8 @@ volumes:
# can be referred to within this file as "external-volume3"
name: this-is-volume3
external: true
x-bar: baz
x-foo: bar
configs:
config1:
@ -374,6 +380,8 @@ configs:
external: true
config4:
name: foo
x-bar: baz
x-foo: bar
secrets:
secret1:
@ -387,3 +395,10 @@ secrets:
external: true
secret4:
name: bar
x-bar: baz
x-foo: bar
x-bar: baz
x-foo: bar
x-nested:
bar: baz
foo: bar

View File

@ -14,8 +14,16 @@ func fullExampleConfig(workingDir, homeDir string) *types.Config {
Services: services(workingDir, homeDir),
Networks: networks(),
Volumes: volumes(),
Configs: configs(),
Secrets: secrets(),
Configs: configs(workingDir),
Secrets: secrets(workingDir),
Extras: map[string]interface{}{
"x-foo": "bar",
"x-bar": "baz",
"x-nested": map[string]interface{}{
"foo": "bar",
"bar": "baz",
},
},
}
}
@ -136,6 +144,10 @@ func services(workingDir, homeDir string) []types.ServiceConfig {
"somehost:162.242.195.82",
"otherhost:50.31.209.229",
},
Extras: map[string]interface{}{
"x-bar": "baz",
"x-foo": "bar",
},
HealthCheck: &types.HealthCheckConfig{
Test: types.HealthCheckTest([]string{"CMD-SHELL", "echo \"hello world\""}),
Interval: durationPtr(10 * time.Second),
@ -392,6 +404,10 @@ func networks() map[string]types.NetworkConfig {
"other-external-network": {
Name: "my-cool-network",
External: types.External{External: true},
Extras: map[string]interface{}{
"x-bar": "baz",
"x-foo": "bar",
},
},
}
}
@ -428,14 +444,18 @@ func volumes() map[string]types.VolumeConfig {
"external-volume3": {
Name: "this-is-volume3",
External: types.External{External: true},
Extras: map[string]interface{}{
"x-bar": "baz",
"x-foo": "bar",
},
},
}
}
func configs() map[string]types.ConfigObjConfig {
func configs(workingDir string) map[string]types.ConfigObjConfig {
return map[string]types.ConfigObjConfig{
"config1": {
File: "./config_data",
File: filepath.Join(workingDir, "config_data"),
Labels: map[string]string{
"foo": "bar",
},
@ -445,18 +465,24 @@ func configs() map[string]types.ConfigObjConfig {
External: types.External{External: true},
},
"config3": {
Name: "config3",
External: types.External{External: true},
},
"config4": {
Name: "foo",
File: workingDir,
Extras: map[string]interface{}{
"x-bar": "baz",
"x-foo": "bar",
},
},
}
}
func secrets() map[string]types.SecretConfig {
func secrets(workingDir string) map[string]types.SecretConfig {
return map[string]types.SecretConfig{
"secret1": {
File: "./secret_data",
File: filepath.Join(workingDir, "secret_data"),
Labels: map[string]string{
"foo": "bar",
},
@ -466,10 +492,16 @@ func secrets() map[string]types.SecretConfig {
External: types.External{External: true},
},
"secret3": {
Name: "secret3",
External: types.External{External: true},
},
"secret4": {
Name: "bar",
File: workingDir,
Extras: map[string]interface{}{
"x-bar": "baz",
"x-foo": "bar",
},
},
}
}
@ -760,6 +792,8 @@ services:
tmpfs:
size: 10000
working_dir: /code
x-bar: baz
x-foo: bar
networks:
external-network:
name: external-network
@ -767,6 +801,8 @@ networks:
other-external-network:
name: my-cool-network
external: true
x-bar: baz
x-foo: bar
other-network:
driver: overlay
driver_opts:
@ -793,6 +829,8 @@ volumes:
external-volume3:
name: this-is-volume3
external: true
x-bar: baz
x-foo: bar
other-external-volume:
name: my-cool-volume
external: true
@ -806,27 +844,46 @@ volumes:
some-volume: {}
secrets:
secret1:
file: ./secret_data
file: %s/secret_data
labels:
foo: bar
secret2:
name: my_secret
external: true
secret3:
name: secret3
external: true
secret4:
name: bar
file: %s
x-bar: baz
x-foo: bar
configs:
config1:
file: ./config_data
file: %s/config_data
labels:
foo: bar
config2:
name: my_config
external: true
config3:
name: config3
external: true
config4:
name: foo
`, filepath.Join(workingDir, "static"), filepath.Join(workingDir, "opt"))
file: %s
x-bar: baz
x-foo: bar
x-bar: baz
x-foo: bar
x-nested:
bar: baz
foo: bar
`,
filepath.Join(workingDir, "static"),
filepath.Join(workingDir, "opt"),
workingDir,
workingDir,
workingDir,
workingDir)
}

View File

@ -147,6 +147,7 @@ func loadSections(config map[string]interface{}, configDetails types.ConfigDetai
return nil, err
}
}
cfg.Extras = getExtras(config)
return &cfg, nil
}
@ -364,9 +365,32 @@ func LoadService(name string, serviceDict map[string]interface{}, workingDir str
if err := resolveVolumePaths(serviceConfig.Volumes, workingDir, lookupEnv); err != nil {
return nil, err
}
serviceConfig.Extras = getExtras(serviceDict)
return serviceConfig, nil
}
func loadExtras(name string, source map[string]interface{}) map[string]interface{} {
if dict, ok := source[name].(map[string]interface{}); ok {
return getExtras(dict)
}
return nil
}
func getExtras(dict map[string]interface{}) map[string]interface{} {
extras := map[string]interface{}{}
for key, value := range dict {
if strings.HasPrefix(key, "x-") {
extras[key] = value
}
}
if len(extras) == 0 {
return nil
}
return extras
}
func updateEnvironment(environment map[string]*string, vars map[string]*string, lookupEnv template.Mapping) {
for k, v := range vars {
interpolatedV, ok := lookupEnv(k)
@ -479,6 +503,7 @@ func LoadNetworks(source map[string]interface{}, version string) (map[string]typ
case network.Name == "":
network.Name = name
}
network.Extras = loadExtras(name, source)
networks[name] = network
}
return networks, nil
@ -521,6 +546,7 @@ func LoadVolumes(source map[string]interface{}, version string) (map[string]type
case volume.Name == "":
volume.Name = name
}
volume.Extras = loadExtras(name, source)
volumes[name] = volume
}
return volumes, nil
@ -538,7 +564,9 @@ func LoadSecrets(source map[string]interface{}, details types.ConfigDetails) (ma
if err != nil {
return nil, err
}
secrets[name] = types.SecretConfig(obj)
secretConfig := types.SecretConfig(obj)
secretConfig.Extras = loadExtras(name, source)
secrets[name] = secretConfig
}
return secrets, nil
}
@ -555,7 +583,9 @@ func LoadConfigObjs(source map[string]interface{}, details types.ConfigDetails)
if err != nil {
return nil, err
}
configs[name] = types.ConfigObjConfig(obj)
configConfig := types.ConfigObjConfig(obj)
configConfig.Extras = loadExtras(name, source)
configs[name] = configConfig
}
return configs, nil
}

View File

@ -182,6 +182,23 @@ func TestLoad(t *testing.T) {
assert.Check(t, is.DeepEqual(sampleConfig.Volumes, actual.Volumes))
}
func TestLoadExtras(t *testing.T) {
actual, err := loadYAML(`
version: "3.7"
services:
foo:
image: busybox
x-foo: bar`)
assert.NilError(t, err)
assert.Check(t, is.Len(actual.Services, 1))
service := actual.Services[0]
assert.Check(t, is.Equal("busybox", service.Image))
extras := map[string]interface{}{
"x-foo": "bar",
}
assert.Check(t, is.DeepEqual(extras, service.Extras))
}
func TestLoadV31(t *testing.T) {
actual, err := loadYAML(`
version: "3.1"
@ -825,6 +842,9 @@ func TestFullExample(t *testing.T) {
assert.Check(t, is.DeepEqual(expectedConfig.Services, config.Services))
assert.Check(t, is.DeepEqual(expectedConfig.Networks, config.Networks))
assert.Check(t, is.DeepEqual(expectedConfig.Volumes, config.Volumes))
assert.Check(t, is.DeepEqual(expectedConfig.Secrets, config.Secrets))
assert.Check(t, is.DeepEqual(expectedConfig.Configs, config.Configs))
assert.Check(t, is.DeepEqual(expectedConfig.Extras, config.Extras))
}
func TestLoadTmpfsVolume(t *testing.T) {

View File

@ -77,6 +77,7 @@ type Config struct {
Volumes map[string]VolumeConfig
Secrets map[string]SecretConfig
Configs map[string]ConfigObjConfig
Extras map[string]interface{} `yaml:",inline"`
}
// Services is a list of ServiceConfig
@ -142,6 +143,7 @@ type ServiceConfig struct {
Volumes []ServiceVolumeConfig `yaml:",omitempty"`
WorkingDir string `mapstructure:"working_dir" yaml:"working_dir,omitempty"`
Isolation string `mapstructure:"isolation" yaml:"isolation,omitempty"`
Extras map[string]interface{} `yaml:",inline"`
}
// BuildConfig is a type for build
@ -362,6 +364,7 @@ type NetworkConfig struct {
Internal bool `yaml:",omitempty"`
Attachable bool `yaml:",omitempty"`
Labels Labels `yaml:",omitempty"`
Extras map[string]interface{} `yaml:",inline"`
}
// IPAMConfig for a network
@ -382,6 +385,7 @@ type VolumeConfig struct {
DriverOpts map[string]string `mapstructure:"driver_opts" yaml:"driver_opts,omitempty"`
External External `yaml:",omitempty"`
Labels Labels `yaml:",omitempty"`
Extras map[string]interface{} `yaml:",inline"`
}
// External identifies a Volume or Network as a reference to a resource that is
@ -412,6 +416,7 @@ type FileObjectConfig struct {
File string `yaml:",omitempty"`
External External `yaml:",omitempty"`
Labels Labels `yaml:",omitempty"`
Extras map[string]interface{} `yaml:",inline"`
}
// SecretConfig for a secret