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 size: 10000
working_dir: /code working_dir: /code
x-bar: baz
x-foo: bar
networks: networks:
# Entries can be null, which specifies simply that a network # 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" # can be referred to within this file as "other-external-network"
external: external:
name: my-cool-network name: my-cool-network
x-bar: baz
x-foo: bar
volumes: volumes:
# Entries can be null, which specifies simply that a volume # 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" # can be referred to within this file as "external-volume3"
name: this-is-volume3 name: this-is-volume3
external: true external: true
x-bar: baz
x-foo: bar
configs: configs:
config1: config1:
@ -374,6 +380,8 @@ configs:
external: true external: true
config4: config4:
name: foo name: foo
x-bar: baz
x-foo: bar
secrets: secrets:
secret1: secret1:
@ -387,3 +395,10 @@ secrets:
external: true external: true
secret4: secret4:
name: bar 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), Services: services(workingDir, homeDir),
Networks: networks(), Networks: networks(),
Volumes: volumes(), Volumes: volumes(),
Configs: configs(), Configs: configs(workingDir),
Secrets: secrets(), 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", "somehost:162.242.195.82",
"otherhost:50.31.209.229", "otherhost:50.31.209.229",
}, },
Extras: map[string]interface{}{
"x-bar": "baz",
"x-foo": "bar",
},
HealthCheck: &types.HealthCheckConfig{ HealthCheck: &types.HealthCheckConfig{
Test: types.HealthCheckTest([]string{"CMD-SHELL", "echo \"hello world\""}), Test: types.HealthCheckTest([]string{"CMD-SHELL", "echo \"hello world\""}),
Interval: durationPtr(10 * time.Second), Interval: durationPtr(10 * time.Second),
@ -392,6 +404,10 @@ func networks() map[string]types.NetworkConfig {
"other-external-network": { "other-external-network": {
Name: "my-cool-network", Name: "my-cool-network",
External: types.External{External: true}, 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": { "external-volume3": {
Name: "this-is-volume3", Name: "this-is-volume3",
External: types.External{External: true}, 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{ return map[string]types.ConfigObjConfig{
"config1": { "config1": {
File: "./config_data", File: filepath.Join(workingDir, "config_data"),
Labels: map[string]string{ Labels: map[string]string{
"foo": "bar", "foo": "bar",
}, },
@ -445,18 +465,24 @@ func configs() map[string]types.ConfigObjConfig {
External: types.External{External: true}, External: types.External{External: true},
}, },
"config3": { "config3": {
Name: "config3",
External: types.External{External: true}, External: types.External{External: true},
}, },
"config4": { "config4": {
Name: "foo", 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{ return map[string]types.SecretConfig{
"secret1": { "secret1": {
File: "./secret_data", File: filepath.Join(workingDir, "secret_data"),
Labels: map[string]string{ Labels: map[string]string{
"foo": "bar", "foo": "bar",
}, },
@ -466,10 +492,16 @@ func secrets() map[string]types.SecretConfig {
External: types.External{External: true}, External: types.External{External: true},
}, },
"secret3": { "secret3": {
Name: "secret3",
External: types.External{External: true}, External: types.External{External: true},
}, },
"secret4": { "secret4": {
Name: "bar", Name: "bar",
File: workingDir,
Extras: map[string]interface{}{
"x-bar": "baz",
"x-foo": "bar",
},
}, },
} }
} }
@ -760,6 +792,8 @@ services:
tmpfs: tmpfs:
size: 10000 size: 10000
working_dir: /code working_dir: /code
x-bar: baz
x-foo: bar
networks: networks:
external-network: external-network:
name: external-network name: external-network
@ -767,6 +801,8 @@ networks:
other-external-network: other-external-network:
name: my-cool-network name: my-cool-network
external: true external: true
x-bar: baz
x-foo: bar
other-network: other-network:
driver: overlay driver: overlay
driver_opts: driver_opts:
@ -793,6 +829,8 @@ volumes:
external-volume3: external-volume3:
name: this-is-volume3 name: this-is-volume3
external: true external: true
x-bar: baz
x-foo: bar
other-external-volume: other-external-volume:
name: my-cool-volume name: my-cool-volume
external: true external: true
@ -806,27 +844,46 @@ volumes:
some-volume: {} some-volume: {}
secrets: secrets:
secret1: secret1:
file: ./secret_data file: %s/secret_data
labels: labels:
foo: bar foo: bar
secret2: secret2:
name: my_secret name: my_secret
external: true external: true
secret3: secret3:
name: secret3
external: true external: true
secret4: secret4:
name: bar name: bar
file: %s
x-bar: baz
x-foo: bar
configs: configs:
config1: config1:
file: ./config_data file: %s/config_data
labels: labels:
foo: bar foo: bar
config2: config2:
name: my_config name: my_config
external: true external: true
config3: config3:
name: config3
external: true external: true
config4: config4:
name: foo 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 return nil, err
} }
} }
cfg.Extras = getExtras(config)
return &cfg, nil 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 { if err := resolveVolumePaths(serviceConfig.Volumes, workingDir, lookupEnv); err != nil {
return nil, err return nil, err
} }
serviceConfig.Extras = getExtras(serviceDict)
return serviceConfig, nil 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) { func updateEnvironment(environment map[string]*string, vars map[string]*string, lookupEnv template.Mapping) {
for k, v := range vars { for k, v := range vars {
interpolatedV, ok := lookupEnv(k) interpolatedV, ok := lookupEnv(k)
@ -479,6 +503,7 @@ func LoadNetworks(source map[string]interface{}, version string) (map[string]typ
case network.Name == "": case network.Name == "":
network.Name = name network.Name = name
} }
network.Extras = loadExtras(name, source)
networks[name] = network networks[name] = network
} }
return networks, nil return networks, nil
@ -521,6 +546,7 @@ func LoadVolumes(source map[string]interface{}, version string) (map[string]type
case volume.Name == "": case volume.Name == "":
volume.Name = name volume.Name = name
} }
volume.Extras = loadExtras(name, source)
volumes[name] = volume volumes[name] = volume
} }
return volumes, nil return volumes, nil
@ -538,7 +564,9 @@ func LoadSecrets(source map[string]interface{}, details types.ConfigDetails) (ma
if err != nil { if err != nil {
return nil, err return nil, err
} }
secrets[name] = types.SecretConfig(obj) secretConfig := types.SecretConfig(obj)
secretConfig.Extras = loadExtras(name, source)
secrets[name] = secretConfig
} }
return secrets, nil return secrets, nil
} }
@ -555,7 +583,9 @@ func LoadConfigObjs(source map[string]interface{}, details types.ConfigDetails)
if err != nil { if err != nil {
return nil, err return nil, err
} }
configs[name] = types.ConfigObjConfig(obj) configConfig := types.ConfigObjConfig(obj)
configConfig.Extras = loadExtras(name, source)
configs[name] = configConfig
} }
return configs, nil return configs, nil
} }

View File

@ -182,6 +182,23 @@ func TestLoad(t *testing.T) {
assert.Check(t, is.DeepEqual(sampleConfig.Volumes, actual.Volumes)) 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) { func TestLoadV31(t *testing.T) {
actual, err := loadYAML(` actual, err := loadYAML(`
version: "3.1" 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.Services, config.Services))
assert.Check(t, is.DeepEqual(expectedConfig.Networks, config.Networks)) assert.Check(t, is.DeepEqual(expectedConfig.Networks, config.Networks))
assert.Check(t, is.DeepEqual(expectedConfig.Volumes, config.Volumes)) 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) { func TestLoadTmpfsVolume(t *testing.T) {

View File

@ -77,6 +77,7 @@ type Config struct {
Volumes map[string]VolumeConfig Volumes map[string]VolumeConfig
Secrets map[string]SecretConfig Secrets map[string]SecretConfig
Configs map[string]ConfigObjConfig Configs map[string]ConfigObjConfig
Extras map[string]interface{} `yaml:",inline"`
} }
// Services is a list of ServiceConfig // Services is a list of ServiceConfig
@ -142,6 +143,7 @@ type ServiceConfig struct {
Volumes []ServiceVolumeConfig `yaml:",omitempty"` Volumes []ServiceVolumeConfig `yaml:",omitempty"`
WorkingDir string `mapstructure:"working_dir" yaml:"working_dir,omitempty"` WorkingDir string `mapstructure:"working_dir" yaml:"working_dir,omitempty"`
Isolation string `mapstructure:"isolation" yaml:"isolation,omitempty"` Isolation string `mapstructure:"isolation" yaml:"isolation,omitempty"`
Extras map[string]interface{} `yaml:",inline"`
} }
// BuildConfig is a type for build // BuildConfig is a type for build
@ -362,6 +364,7 @@ type NetworkConfig struct {
Internal bool `yaml:",omitempty"` Internal bool `yaml:",omitempty"`
Attachable bool `yaml:",omitempty"` Attachable bool `yaml:",omitempty"`
Labels Labels `yaml:",omitempty"` Labels Labels `yaml:",omitempty"`
Extras map[string]interface{} `yaml:",inline"`
} }
// IPAMConfig for a network // IPAMConfig for a network
@ -382,6 +385,7 @@ type VolumeConfig struct {
DriverOpts map[string]string `mapstructure:"driver_opts" yaml:"driver_opts,omitempty"` DriverOpts map[string]string `mapstructure:"driver_opts" yaml:"driver_opts,omitempty"`
External External `yaml:",omitempty"` External External `yaml:",omitempty"`
Labels Labels `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 // 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"` File string `yaml:",omitempty"`
External External `yaml:",omitempty"` External External `yaml:",omitempty"`
Labels Labels `yaml:",omitempty"` Labels Labels `yaml:",omitempty"`
Extras map[string]interface{} `yaml:",inline"`
} }
// SecretConfig for a secret // SecretConfig for a secret