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
|
||||
|
||||
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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
@ -354,14 +356,15 @@ func (u *UlimitsConfig) MarshalYAML() (interface{}, error) {
|
|||
|
||||
// NetworkConfig for a network
|
||||
type NetworkConfig struct {
|
||||
Name string `yaml:",omitempty"`
|
||||
Driver string `yaml:",omitempty"`
|
||||
DriverOpts map[string]string `mapstructure:"driver_opts" yaml:"driver_opts,omitempty"`
|
||||
Ipam IPAMConfig `yaml:",omitempty"`
|
||||
External External `yaml:",omitempty"`
|
||||
Internal bool `yaml:",omitempty"`
|
||||
Attachable bool `yaml:",omitempty"`
|
||||
Labels Labels `yaml:",omitempty"`
|
||||
Name string `yaml:",omitempty"`
|
||||
Driver string `yaml:",omitempty"`
|
||||
DriverOpts map[string]string `mapstructure:"driver_opts" yaml:"driver_opts,omitempty"`
|
||||
Ipam IPAMConfig `yaml:",omitempty"`
|
||||
External External `yaml:",omitempty"`
|
||||
Internal bool `yaml:",omitempty"`
|
||||
Attachable bool `yaml:",omitempty"`
|
||||
Labels Labels `yaml:",omitempty"`
|
||||
Extras map[string]interface{} `yaml:",inline"`
|
||||
}
|
||||
|
||||
// IPAMConfig for a network
|
||||
|
@ -377,11 +380,12 @@ type IPAMPool struct {
|
|||
|
||||
// VolumeConfig for a volume
|
||||
type VolumeConfig struct {
|
||||
Name string `yaml:",omitempty"`
|
||||
Driver string `yaml:",omitempty"`
|
||||
DriverOpts map[string]string `mapstructure:"driver_opts" yaml:"driver_opts,omitempty"`
|
||||
External External `yaml:",omitempty"`
|
||||
Labels Labels `yaml:",omitempty"`
|
||||
Name string `yaml:",omitempty"`
|
||||
Driver string `yaml:",omitempty"`
|
||||
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
|
||||
|
@ -408,10 +412,11 @@ type CredentialSpecConfig struct {
|
|||
|
||||
// FileObjectConfig is a config type for a file used by a service
|
||||
type FileObjectConfig struct {
|
||||
Name string `yaml:",omitempty"`
|
||||
File string `yaml:",omitempty"`
|
||||
External External `yaml:",omitempty"`
|
||||
Labels Labels `yaml:",omitempty"`
|
||||
Name string `yaml:",omitempty"`
|
||||
File string `yaml:",omitempty"`
|
||||
External External `yaml:",omitempty"`
|
||||
Labels Labels `yaml:",omitempty"`
|
||||
Extras map[string]interface{} `yaml:",inline"`
|
||||
}
|
||||
|
||||
// SecretConfig for a secret
|
||||
|
|
Loading…
Reference in New Issue