Merge component 'cli' from git@github.com:docker/cli master

This commit is contained in:
GordonTheTurtle
2018-06-25 16:41:38 +00:00
10 changed files with 237 additions and 52 deletions

View File

@ -162,7 +162,12 @@ func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions) error {
if err != nil {
return err
}
hasExperimental, err := isEnabled(cli.configFile.Experimental)
var experimentalValue string
// Environment variable always overrides configuration
if experimentalValue = os.Getenv("DOCKER_CLI_EXPERIMENTAL"); experimentalValue == "" {
experimentalValue = cli.configFile.Experimental
}
hasExperimental, err := isEnabled(experimentalValue)
if err != nil {
return errors.Wrap(err, "Experimental field")
}

View File

@ -150,6 +150,7 @@ func Service(
ReadOnly: service.ReadOnly,
Privileges: &privileges,
Isolation: container.Isolation(service.Isolation),
Init: service.Init,
},
LogDriver: logDriver,
Resources: resources,

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) {
@ -1395,3 +1415,51 @@ networks:
}
assert.DeepEqual(t, config, expected, cmpopts.EquateEmpty())
}
func TestLoadInit(t *testing.T) {
booleanTrue := true
booleanFalse := false
var testcases = []struct {
doc string
yaml string
init *bool
}{
{
doc: "no init defined",
yaml: `
version: '3.7'
services:
foo:
image: alpine`,
},
{
doc: "has true init",
yaml: `
version: '3.7'
services:
foo:
image: alpine
init: true`,
init: &booleanTrue,
},
{
doc: "has false init",
yaml: `
version: '3.7'
services:
foo:
image: alpine
init: false`,
init: &booleanFalse,
},
}
for _, testcase := range testcases {
t.Run(testcase.doc, func(t *testing.T) {
config, err := loadYAML(testcase.yaml)
assert.NilError(t, err)
assert.Check(t, is.Len(config.Services, 1))
assert.Check(t, is.DeepEqual(config.Services[0].Init, testcase.init))
})
}
}

View File

@ -467,7 +467,7 @@ DoTuq9lAU9Q4O1xV/59X/w8AAP//zRo7vm9CAAA=
"/data/config_schema_v3.7.json": {
local: "data/config_schema_v3.7.json",
size: 17740,
size: 17777,
modtime: 1518458244,
compressed: `
H4sIAAAAAAAC/+xcS4/bOBK++1cYmrlNPwLsYBeb2x73tHvehiPQVNnmNEVyipTTTuD/vtDTEkWKlK1O
@ -484,27 +484,27 @@ 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
`,
},

View File

@ -154,6 +154,7 @@
"healthcheck": {"$ref": "#/definitions/healthcheck"},
"hostname": {"type": "string"},
"image": {"type": "string"},
"init": {"type": "boolean"},
"ipc": {"type": "string"},
"isolation": {"type": "string"},
"labels": {"$ref": "#/definitions/list_or_dict"},

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
@ -118,7 +119,9 @@ type ServiceConfig struct {
Hostname string `yaml:",omitempty"`
HealthCheck *HealthCheckConfig `yaml:",omitempty"`
Image string `yaml:",omitempty"`
Init *bool `yaml:",omitempty"`
Ipc string `yaml:",omitempty"`
Isolation string `mapstructure:"isolation" yaml:"isolation,omitempty"`
Labels Labels `yaml:",omitempty"`
Links []string `yaml:",omitempty"`
Logging *LoggingConfig `yaml:",omitempty"`
@ -141,7 +144,8 @@ type ServiceConfig struct {
User string `yaml:",omitempty"`
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 +358,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 +382,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 +414,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

View File

@ -61,6 +61,7 @@ by the `docker` command line:
* `DOCKER_API_VERSION` The API version to use (e.g. `1.19`)
* `DOCKER_CONFIG` The location of your client configuration files.
* `DOCKER_CERT_PATH` The location of your authentication keys.
* `DOCKER_CLI_EXPERIMENTAL` Enable experimental features for the cli (e.g. `enabled` or `disabled`)
* `DOCKER_DRIVER` The graph driver to use.
* `DOCKER_HOST` Daemon socket to connect to.
* `DOCKER_NOWARN_KERNEL_VERSION` Prevent warnings that your Linux kernel is