mirror of https://github.com/docker/cli.git
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:
parent
1c20e3f601
commit
80c26f618e
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue