Fix the dependency issue (#231)

This commit is contained in:
Robbie Zhang
2018-06-21 12:09:42 -07:00
committed by GitHub
parent 027b76651d
commit 6ec1098bb8
16629 changed files with 74837 additions and 4975021 deletions

View File

@@ -1,226 +0,0 @@
package config
import (
"fmt"
"os"
"testing"
yaml "github.com/cloudfoundry-incubator/candiedyaml"
"github.com/stretchr/testify/assert"
)
func testInterpolatedLine(t *testing.T, expectedLine, interpolatedLine string, envVariables map[string]string) {
interpolatedLine, _ = parseLine(interpolatedLine, func(s string) string {
return envVariables[s]
})
assert.Equal(t, expectedLine, interpolatedLine)
}
func testInvalidInterpolatedLine(t *testing.T, line string) {
_, success := parseLine(line, func(string) string {
return ""
})
assert.Equal(t, false, success)
}
func TestParseLine(t *testing.T) {
variables := map[string]string{
"A": "ABC",
"X": "XYZ",
"E": "",
"lower": "WORKED",
"MiXeD": "WORKED",
"split_VaLue": "WORKED",
"9aNumber": "WORKED",
"a9Number": "WORKED",
}
testInterpolatedLine(t, "WORKED", "$lower", variables)
testInterpolatedLine(t, "WORKED", "${MiXeD}", variables)
testInterpolatedLine(t, "WORKED", "${split_VaLue}", variables)
// Starting with a number isn't valid
testInterpolatedLine(t, "", "$9aNumber", variables)
testInterpolatedLine(t, "WORKED", "$a9Number", variables)
testInterpolatedLine(t, "ABC", "$A", variables)
testInterpolatedLine(t, "ABC", "${A}", variables)
testInterpolatedLine(t, "ABC DE", "$A DE", variables)
testInterpolatedLine(t, "ABCDE", "${A}DE", variables)
testInterpolatedLine(t, "$A", "$$A", variables)
testInterpolatedLine(t, "${A}", "$${A}", variables)
testInterpolatedLine(t, "$ABC", "$$${A}", variables)
testInterpolatedLine(t, "$ABC", "$$$A", variables)
testInterpolatedLine(t, "ABC XYZ", "$A $X", variables)
testInterpolatedLine(t, "ABCXYZ", "$A$X", variables)
testInterpolatedLine(t, "ABCXYZ", "${A}${X}", variables)
testInterpolatedLine(t, "", "$B", variables)
testInterpolatedLine(t, "", "${B}", variables)
testInterpolatedLine(t, "", "$ADE", variables)
testInterpolatedLine(t, "", "$E", variables)
testInterpolatedLine(t, "", "${E}", variables)
testInvalidInterpolatedLine(t, "${")
testInvalidInterpolatedLine(t, "$}")
testInvalidInterpolatedLine(t, "${}")
testInvalidInterpolatedLine(t, "${ }")
testInvalidInterpolatedLine(t, "${A }")
testInvalidInterpolatedLine(t, "${ A}")
testInvalidInterpolatedLine(t, "${A!}")
testInvalidInterpolatedLine(t, "$!")
}
type MockEnvironmentLookup struct {
Variables map[string]string
}
func (m MockEnvironmentLookup) Lookup(key, serviceName string, config *ServiceConfig) []string {
return []string{fmt.Sprintf("%s=%s", key, m.Variables[key])}
}
func testInterpolatedConfig(t *testing.T, expectedConfig, interpolatedConfig string, envVariables map[string]string) {
for k, v := range envVariables {
os.Setenv(k, v)
}
expectedConfigBytes := []byte(expectedConfig)
interpolatedConfigBytes := []byte(interpolatedConfig)
expectedData := make(RawServiceMap)
interpolatedData := make(RawServiceMap)
yaml.Unmarshal(expectedConfigBytes, &expectedData)
yaml.Unmarshal(interpolatedConfigBytes, &interpolatedData)
_ = Interpolate(MockEnvironmentLookup{envVariables}, &interpolatedData)
for k := range envVariables {
os.Unsetenv(k)
}
assert.Equal(t, expectedData, interpolatedData)
}
func testInvalidInterpolatedConfig(t *testing.T, interpolatedConfig string) {
interpolatedConfigBytes := []byte(interpolatedConfig)
interpolatedData := make(RawServiceMap)
yaml.Unmarshal(interpolatedConfigBytes, &interpolatedData)
err := Interpolate(new(MockEnvironmentLookup), &interpolatedData)
assert.NotNil(t, err)
}
func TestInterpolate(t *testing.T) {
testInterpolatedConfig(t,
`web:
# unbracketed name
image: busybox
# array element
ports:
- "80:8000"
# dictionary item value
labels:
mylabel: "myvalue"
# unset value
hostname: "host-"
# escaped interpolation
command: "${ESCAPED}"`,
`web:
# unbracketed name
image: $IMAGE
# array element
ports:
- "${HOST_PORT}:8000"
# dictionary item value
labels:
mylabel: "${LABEL_VALUE}"
# unset value
hostname: "host-${UNSET_VALUE}"
# escaped interpolation
command: "$${ESCAPED}"`, map[string]string{
"IMAGE": "busybox",
"HOST_PORT": "80",
"LABEL_VALUE": "myvalue",
})
// Same as above, but testing with equal signs in variables
testInterpolatedConfig(t,
`web:
# unbracketed name
image: =busybox
# array element
ports:
- "=:8000"
# dictionary item value
labels:
mylabel: "myvalue=="
# unset value
hostname: "host-"
# escaped interpolation
command: "${ESCAPED}"`,
`web:
# unbracketed name
image: $IMAGE
# array element
ports:
- "${HOST_PORT}:8000"
# dictionary item value
labels:
mylabel: "${LABEL_VALUE}"
# unset value
hostname: "host-${UNSET_VALUE}"
# escaped interpolation
command: "$${ESCAPED}"`, map[string]string{
"IMAGE": "=busybox",
"HOST_PORT": "=",
"LABEL_VALUE": "myvalue==",
})
testInvalidInterpolatedConfig(t,
`web:
image: "${"`)
testInvalidInterpolatedConfig(t,
`web:
image: busybox
# array element
ports:
- "${}:8000"`)
testInvalidInterpolatedConfig(t,
`web:
image: busybox
# array element
ports:
- "80:8000"
# dictionary item value
labels:
mylabel: "${ LABEL_VALUE}"`)
}

View File

@@ -1,90 +0,0 @@
package config
import (
"testing"
yaml "github.com/cloudfoundry-incubator/candiedyaml"
yamlTypes "github.com/hyperhq/libcompose/yaml"
"github.com/stretchr/testify/assert"
)
type TestConfig struct {
SystemContainers map[string]*ServiceConfig
}
func newTestConfig() TestConfig {
return TestConfig{
SystemContainers: map[string]*ServiceConfig{
"udev": {
Image: "udev",
Restart: "always",
// NetworkMode: "host",
// Privileged: true,
// DNS: []string{"8.8.8.8", "8.8.4.4"},
Environment: yamlTypes.MaporEqualSlice([]string{
"DAEMON=true",
}),
Labels: yamlTypes.SliceorMap{
"io.rancher.os.detach": "true",
"io.rancher.os.scope": "system",
},
// VolumesFrom: []string{
// "system-volumes",
// },
// Ulimits: yamlTypes.Ulimits{
// Elements: []yamlTypes.Ulimit{
// yamlTypes.NewUlimit("nproc", 65557, 65557),
// },
// },
},
"system-volumes": {
Image: "state",
// NetworkMode: "none",
// ReadOnly: true,
// Privileged: true,
Labels: yamlTypes.SliceorMap{
"io.rancher.os.createonly": "true",
"io.rancher.os.scope": "system",
},
Volumes: []string{
"/dev:/host/dev",
"/var/lib/rancher/conf:/var/lib/rancher/conf",
"/etc/ssl/certs/ca-certificates.crt:/etc/ssl/certs/ca-certificates.crt.rancher",
"/lib/modules:/lib/modules",
"/lib/firmware:/lib/firmware",
"/var/run:/var/run",
"/var/log:/var/log",
},
// Logging: Log{
// Driver: "json-file",
//},
},
},
}
}
func TestMarshalConfig(t *testing.T) {
config := newTestConfig()
bytes, err := yaml.Marshal(config)
assert.Nil(t, err)
config2 := TestConfig{}
err = yaml.Unmarshal(bytes, &config2)
assert.Nil(t, err)
assert.Equal(t, config, config2)
}
func TestMarshalServiceConfig(t *testing.T) {
configPtr := newTestConfig().SystemContainers["udev"]
bytes, err := yaml.Marshal(configPtr)
assert.Nil(t, err)
configPtr2 := &ServiceConfig{}
err = yaml.Unmarshal(bytes, configPtr2)
assert.Nil(t, err)
assert.Equal(t, configPtr, configPtr2)
}

View File

@@ -1,292 +0,0 @@
package config
import "testing"
type NullLookup struct {
}
func (n *NullLookup) Lookup(file, relativeTo string) ([]byte, string, error) {
return nil, "", nil
}
func (n *NullLookup) ResolvePath(path, inFile string) string {
return ""
}
func TestExtendsInheritImage(t *testing.T) {
configV1, _, _, err := Merge(NewServiceConfigs(), nil, &NullLookup{}, "", []byte(`
parent:
image: foo
child:
extends:
service: parent
`))
if err != nil {
t.Fatal(err)
}
configV2, _, _, err := Merge(NewServiceConfigs(), nil, &NullLookup{}, "", []byte(`
version: '2'
services:
parent:
image: foo
child:
extends:
service: parent
`))
if err != nil {
t.Fatal(err)
}
for _, config := range []map[string]*ServiceConfig{configV1, configV2} {
parent := config["parent"]
child := config["child"]
if parent.Image != "foo" {
t.Fatal("Invalid image", parent.Image)
}
// if child.Build.Context != "" {
// t.Fatal("Invalid build", child.Build)
// }
if child.Image != "foo" {
t.Fatal("Invalid image", child.Image)
}
}
}
/*
func TestExtendsInheritBuild(t *testing.T) {
configV1, _, _, err := Merge(NewServiceConfigs(), nil, &NullLookup{}, "", []byte(`
parent:
build: .
child:
extends:
service: parent
`))
if err != nil {
t.Fatal(err)
}
configV2, _, _, err := Merge(NewServiceConfigs(), nil, &NullLookup{}, "", []byte(`
version: '2'
services:
parent:
build:
context: .
child:
extends:
service: parent
`))
if err != nil {
t.Fatal(err)
}
for _, config := range []map[string]*ServiceConfig{configV1, configV2} {
parent := config["parent"]
child := config["child"]
if parent.Build.Context != "." {
t.Fatal("Invalid build", parent.Build)
}
if child.Build.Context != "." {
t.Fatal("Invalid build", child.Build)
}
if child.Image != "" {
t.Fatal("Invalid image", child.Image)
}
}
}
func TestExtendBuildOverImage(t *testing.T) {
configV1, _, _, err := Merge(NewServiceConfigs(), nil, &NullLookup{}, "", []byte(`
parent:
image: foo
child:
build: .
extends:
service: parent
`))
if err != nil {
t.Fatal(err)
}
configV2, _, _, err := Merge(NewServiceConfigs(), nil, &NullLookup{}, "", []byte(`
version: '2'
services:
parent:
image: foo
child:
build:
context: .
extends:
service: parent
`))
if err != nil {
t.Fatal(err)
}
for _, config := range []map[string]*ServiceConfig{configV1, configV2} {
parent := config["parent"]
child := config["child"]
if parent.Image != "foo" {
t.Fatal("Invalid image", parent.Image)
}
if child.Build.Context != "." {
t.Fatal("Invalid build", child.Build)
}
if child.Image != "" {
t.Fatal("Invalid image", child.Image)
}
}
}
func TestExtendImageOverBuild(t *testing.T) {
configV1, _, _, err := Merge(NewServiceConfigs(), nil, &NullLookup{}, "", []byte(`
parent:
build: .
child:
image: foo
extends:
service: parent
`))
if err != nil {
t.Fatal(err)
}
configV2, _, _, err := Merge(NewServiceConfigs(), nil, &NullLookup{}, "", []byte(`
version: '2'
services:
parent:
build:
context: .
child:
image: foo
extends:
service: parent
`))
if err != nil {
t.Fatal(err)
}
for _, config := range []map[string]*ServiceConfig{configV1, configV2} {
parent := config["parent"]
child := config["child"]
if parent.Image != "" {
t.Fatal("Invalid image", parent.Image)
}
if parent.Build.Context != "." {
t.Fatal("Invalid build", parent.Build)
}
if child.Build.Context != "" {
t.Fatal("Invalid build", child.Build)
}
if child.Image != "foo" {
t.Fatal("Invalid image", child.Image)
}
}
}
*/
func TestRestartNo(t *testing.T) {
configV1, _, _, err := Merge(NewServiceConfigs(), nil, &NullLookup{}, "", []byte(`
test:
restart: "no"
image: foo
`))
if err != nil {
t.Fatal(err)
}
configV2, _, _, err := Merge(NewServiceConfigs(), nil, &NullLookup{}, "", []byte(`
version: '2'
services:
test:
restart: "no"
image: foo
`))
if err != nil {
t.Fatal(err)
}
for _, config := range []map[string]*ServiceConfig{configV1, configV2} {
test := config["test"]
if test.Restart != "no" {
t.Fatal("Invalid restart policy", test.Restart)
}
}
}
func TestRestartAlways(t *testing.T) {
configV1, _, _, err := Merge(NewServiceConfigs(), nil, &NullLookup{}, "", []byte(`
test:
restart: always
image: foo
`))
if err != nil {
t.Fatal(err)
}
configV2, _, _, err := Merge(NewServiceConfigs(), nil, &NullLookup{}, "", []byte(`
version: '2'
services:
test:
restart: always
image: foo
`))
if err != nil {
t.Fatal(err)
}
for _, config := range []map[string]*ServiceConfig{configV1, configV2} {
test := config["test"]
if test.Restart != "always" {
t.Fatal("Invalid restart policy", test.Restart)
}
}
}
func TestIsValidRemote(t *testing.T) {
gitUrls := []string{
"git://github.com/docker/docker",
"git@github.com:docker/docker.git",
"git@bitbucket.org:atlassianlabs/atlassian-docker.git",
"https://github.com/docker/docker.git",
"http://github.com/docker/docker.git",
"http://github.com/docker/docker.git#branch",
"http://github.com/docker/docker.git#:dir",
}
incompleteGitUrls := []string{
"github.com/docker/docker",
}
invalidGitUrls := []string{
"http://github.com/docker/docker.git:#branch",
}
for _, url := range gitUrls {
if !IsValidRemote(url) {
t.Fatalf("%q should have been a valid remote", url)
}
}
for _, url := range incompleteGitUrls {
if !IsValidRemote(url) {
t.Fatalf("%q should have been a valid remote", url)
}
}
for _, url := range invalidGitUrls {
if !IsValidRemote(url) {
t.Fatalf("%q should have been a valid remote", url)
}
}
}

View File

@@ -1,360 +0,0 @@
package config
import (
"bytes"
"fmt"
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
func testValidSchema(t *testing.T, serviceMap RawServiceMap) {
err := validate(serviceMap, "v1")
assert.Nil(t, err)
for name, service := range serviceMap {
err := validateServiceConstraints(service, name)
assert.Nil(t, err)
}
}
func testInvalidSchema(t *testing.T, serviceMap RawServiceMap, errMsgs []string, errCount int) {
var combinedErrMsg bytes.Buffer
err := validate(serviceMap, "v2")
if err != nil {
combinedErrMsg.WriteString(err.Error())
combinedErrMsg.WriteRune('\n')
}
for name, service := range serviceMap {
err := validateServiceConstraints(service, name)
if err != nil {
combinedErrMsg.WriteString(err.Error())
combinedErrMsg.WriteRune('\n')
}
}
for _, errMsg := range errMsgs {
assert.True(t, strings.Contains(combinedErrMsg.String(), errMsg))
}
// gojsonschema has bugs that can cause extraneous errors
// This makes sure we don't have more errors than expected
assert.True(t, strings.Count(combinedErrMsg.String(), "\n") == errCount)
}
func TestInvalidServiceNames(t *testing.T) {
invalidServiceNames := []string{"?not?allowed", " ", "", "!", "/"}
for _, invalidServiceName := range invalidServiceNames {
testInvalidSchema(t, RawServiceMap{
invalidServiceName: map[string]interface{}{
"image": "busybox",
},
}, []string{fmt.Sprintf("Invalid service name '%s' - only [a-zA-Z0-9\\._\\-] characters are allowed", invalidServiceName)}, 1)
}
}
func TestValidServiceNames(t *testing.T) {
validServiceNames := []string{"_", "-", ".__.", "_what-up.", "what_.up----", "whatup"}
for _, validServiceName := range validServiceNames {
testValidSchema(t, RawServiceMap{
validServiceName: map[string]interface{}{
"image": "busybox",
},
})
}
}
/*
func TestConfigInvalidPorts(t *testing.T) {
portsValues := []interface{}{
map[string]interface{}{
"1": "8000",
},
false,
0,
"8000",
}
for _, portsValue := range portsValues {
testInvalidSchema(t, RawServiceMap{
"web": map[string]interface{}{
"image": "busybox",
"ports": portsValue,
},
}, []string{"Service 'web' configuration key 'ports' contains an invalid type, it should be an array"}, 1)
}
testInvalidSchema(t, RawServiceMap{
"web": map[string]interface{}{
"image": "busybox",
"ports": []interface{}{
"8000",
"8000",
},
},
}, []string{"Service 'web' configuration key 'ports' value [8000 8000] has non-unique elements"}, 1)
}
*/
/*
func TestConfigValidPorts(t *testing.T) {
portsValues := []interface{}{
[]interface{}{"8000", "9000"},
[]interface{}{"8000"},
[]interface{}{8000},
[]interface{}{"127.0.0.1::8000"},
[]interface{}{"49153-49154:3002-3003"},
}
for _, portsValue := range portsValues {
testValidSchema(t, RawServiceMap{
"web": map[string]interface{}{
"image": "busybox",
"ports": portsValue,
},
})
}
}
*/
func TestConfigHint(t *testing.T) {
testInvalidSchema(t, RawServiceMap{
"foo": map[string]interface{}{
"image": "busybox",
"privilege": "something",
},
}, []string{"Unsupported config option for foo service: 'privilege'"}, 1)
}
func TestTypeShouldBeAnArray(t *testing.T) {
testInvalidSchema(t, RawServiceMap{
"foo": map[string]interface{}{
"image": "busybox",
"links": "an_link",
},
}, []string{"Service 'foo' configuration key 'links' contains an invalid type, it should be an array"}, 1)
}
func TestInvalidTypeWithMultipleValidTypes(t *testing.T) {
testInvalidSchema(t, RawServiceMap{
"web": map[string]interface{}{
"image": "busybox",
"mem_limit": []interface{}{
"array_elem",
},
},
}, []string{"Unsupported config option for web service: 'mem_limit'"}, 1)
}
func TestInvalidNotUniqueItems(t *testing.T) {
// Test property with array as only valid type
testInvalidSchema(t, RawServiceMap{
"foo": map[string]interface{}{
"image": "busybox",
"devices": []string{
"/dev/foo:/dev/foo",
"/dev/foo:/dev/foo",
},
},
}, []string{"Unsupported config option for foo service: 'devices'"}, 1)
// Test property with multiple valid types
testInvalidSchema(t, RawServiceMap{
"foo": map[string]interface{}{
"image": "busybox",
"environment": []string{
"KEY=VAL",
"KEY=VAL",
},
},
}, []string{"Service 'foo' configuration key 'environment' contains non unique items, please remove duplicates from [KEY=VAL KEY=VAL]"}, 1)
}
func TestInvalidListOfStringsFormat(t *testing.T) {
testInvalidSchema(t, RawServiceMap{
"web": map[string]interface{}{
"image": "busybox",
"command": []interface{}{
1,
},
},
}, []string{"Service 'web' configuration key 'command' contains 1, which is an invalid type, it should be a string"}, 1)
}
func TestInvalidExtraHostsString(t *testing.T) {
testInvalidSchema(t, RawServiceMap{
"web": map[string]interface{}{
"image": "busybox",
"extra_hosts": "somehost:162.242.195.82",
},
}, []string{"Unsupported config option for web service: 'extra_hosts'"}, 1)
}
/*
func TestValidConfigWhichAllowsTwoTypeDefinitions(t *testing.T) {
for _, exposeValue := range []interface{}{"8000", 9000} {
testValidSchema(t, RawServiceMap{
"web": map[string]interface{}{
"image": "busybox",
"expose": []interface{}{
exposeValue,
},
},
})
}
}
*/
func TestValidConfigOneOfStringOrList(t *testing.T) {
entrypointValues := []interface{}{
[]interface{}{
"sh",
},
"sh",
}
for _, entrypointValue := range entrypointValues {
testValidSchema(t, RawServiceMap{
"web": map[string]interface{}{
"image": "busybox",
"entrypoint": entrypointValue,
},
})
}
}
func TestInvalidServiceProperty(t *testing.T) {
testInvalidSchema(t, RawServiceMap{
"web": map[string]interface{}{
"image": "busybox",
"invalid_property": "value",
},
}, []string{"Unsupported config option for web service: 'invalid_property'"}, 1)
}
func TestServiceInvalidMissingImageAndBuild(t *testing.T) {
testInvalidSchema(t, RawServiceMap{
"web": map[string]interface{}{},
}, []string{"Service 'web' has neither an image nor a build path specified. Exactly one must be provided."}, 1)
}
func TestServiceInvalidSpecifiesImageAndBuild(t *testing.T) {
testInvalidSchema(t, RawServiceMap{
"web": map[string]interface{}{
"image": "busybox",
"build": ".",
},
}, []string{"Unsupported config option for web service: 'build'"}, 1)
}
func TestServiceInvalidSpecifiesImageAndDockerfile(t *testing.T) {
testInvalidSchema(t, RawServiceMap{
"web": map[string]interface{}{
"image": "busybox",
"dockerfile": "Dockerfile",
},
}, []string{"Unsupported config option for web service: 'dockerfile'"}, 1)
}
func TestInvalidServiceForMultipleErrors(t *testing.T) {
testInvalidSchema(t, RawServiceMap{
"foo": map[string]interface{}{
"image": "busybox",
// "ports": "invalid_type",
"links": "an_type",
"environment": []string{
"KEY=VAL",
"KEY=VAL",
},
},
}, []string{
// "Service 'foo' configuration key 'ports' contains an invalid type, it should be an array",
"Service 'foo' configuration key 'links' contains an invalid type, it should be an array",
"Service 'foo' configuration key 'environment' contains non unique items, please remove duplicates from [KEY=VAL KEY=VAL]",
}, 2)
}
func TestInvalidServiceWithAdditionalProperties(t *testing.T) {
testInvalidSchema(t, RawServiceMap{
"foo": map[string]interface{}{
"image": "busybox",
// "ports": "invalid_type",
"---": "nope",
"environment": []string{
"KEY=VAL",
"KEY=VAL",
},
},
}, []string{
// "Service 'foo' configuration key 'ports' contains an invalid type, it should be an array",
"Unsupported config option for foo service: '---'",
"Service 'foo' configuration key 'environment' contains non unique items, please remove duplicates from [KEY=VAL KEY=VAL]",
}, 2)
}
func TestMultipleInvalidServices(t *testing.T) {
testInvalidSchema(t, RawServiceMap{
"foo1": map[string]interface{}{
"image": "busybox",
// "ports": "invalid_type",
},
"foo2": map[string]interface{}{
"image": "busybox",
// "ports": "invalid_type",
},
}, []string{
// "Service 'foo1' configuration key 'ports' contains an invalid type, it should be an array",
// "Service 'foo2' configuration key 'ports' contains an invalid type, it should be an array",
}, 0)
}
func TestMixedInvalidServicesAndInvalidServiceNames(t *testing.T) {
testInvalidSchema(t, RawServiceMap{
//"foo1": map[string]interface{}{
// "image": "busybox",
// "ports": "invalid_type",
// },
"???": map[string]interface{}{
"image": "busybox",
},
// "foo2": map[string]interface{}{
// "image": "busybox",
// "ports": "invalid_type",
// },
}, []string{
// "Service 'foo1' configuration key 'ports' contains an invalid type, it should be an array",
"Invalid service name '???' - only [a-zA-Z0-9\\._\\-] characters are allowed",
// "Service 'foo2' configuration key 'ports' contains an invalid type, it should be an array",
}, 1)
}
func TestMultipleInvalidServicesForMultipleErrors(t *testing.T) {
testInvalidSchema(t, RawServiceMap{
"foo1": map[string]interface{}{
"image": "busybox",
// "ports": "invalid_type",
"environment": []string{
"KEY=VAL",
"KEY=VAL",
},
},
"foo2": map[string]interface{}{
"image": "busybox",
// "ports": "invalid_type",
"environment": []string{
"KEY=VAL",
"KEY=VAL",
},
},
}, []string{
// "Service 'foo1' configuration key 'ports' contains an invalid type, it should be an array",
"Service 'foo1' configuration key 'environment' contains non unique items, please remove duplicates from [KEY=VAL KEY=VAL]",
// "Service 'foo2' configuration key 'ports' contains an invalid type, it should be an array",
"Service 'foo2' configuration key 'environment' contains non unique items, please remove duplicates from [KEY=VAL KEY=VAL]",
}, 2)
}