From 80c26f618e2389e3e0e3adc80ff6570d52137ec5 Mon Sep 17 00:00:00 2001 From: Vincent Demeester Date: Mon, 25 Jun 2018 10:51:56 +0200 Subject: [PATCH] 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 --- cli/compose/loader/full-example.yml | 15 ++++++ cli/compose/loader/full-struct_test.go | 75 ++++++++++++++++++++++---- cli/compose/loader/loader.go | 34 +++++++++++- cli/compose/loader/loader_test.go | 20 +++++++ cli/compose/types/types.go | 39 ++++++++------ 5 files changed, 155 insertions(+), 28 deletions(-) diff --git a/cli/compose/loader/full-example.yml b/cli/compose/loader/full-example.yml index 6da091b180..2dd5799a19 100644 --- a/cli/compose/loader/full-example.yml +++ b/cli/compose/loader/full-example.yml @@ -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 diff --git a/cli/compose/loader/full-struct_test.go b/cli/compose/loader/full-struct_test.go index 3e8cfdb31f..4ee7798f42 100644 --- a/cli/compose/loader/full-struct_test.go +++ b/cli/compose/loader/full-struct_test.go @@ -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) } diff --git a/cli/compose/loader/loader.go b/cli/compose/loader/loader.go index 505e4c78fe..3c138d59ec 100644 --- a/cli/compose/loader/loader.go +++ b/cli/compose/loader/loader.go @@ -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 } diff --git a/cli/compose/loader/loader_test.go b/cli/compose/loader/loader_test.go index a00a972eb8..55b58cca17 100644 --- a/cli/compose/loader/loader_test.go +++ b/cli/compose/loader/loader_test.go @@ -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) { diff --git a/cli/compose/types/types.go b/cli/compose/types/types.go index 397bc79c1f..d59e1f1495 100644 --- a/cli/compose/types/types.go +++ b/cli/compose/types/types.go @@ -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