Merge pull request #905 from crazy-max/compose-go

compose: fix env
pull/915/head
Tõnis Tiigi 3 years ago committed by GitHub
commit 2b4d305c58
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -76,6 +76,9 @@ func ParseCompose(dt []byte) (*Config, error) {
Dockerfile: dockerfilePathP, Dockerfile: dockerfilePathP,
Labels: s.Build.Labels, Labels: s.Build.Labels,
Args: flatten(s.Build.Args.Resolve(func(val string) (string, bool) { Args: flatten(s.Build.Args.Resolve(func(val string) (string, bool) {
if val, ok := s.Environment[val]; ok && val != nil {
return *val, true
}
val, ok := cfg.Environment[val] val, ok := cfg.Environment[val]
return val, ok return val, ok
})), })),

@ -280,6 +280,36 @@ services:
require.Equal(t, c.Targets[1].NoCache, newBool(true)) require.Equal(t, c.Targets[1].NoCache, newBool(true))
} }
func TestEnv(t *testing.T) {
envf, err := os.CreateTemp("", "env")
require.NoError(t, err)
defer os.Remove(envf.Name())
_, err = envf.WriteString("FOO=bsdf -csdf\n")
require.NoError(t, err)
var dt = []byte(`
services:
scratch:
build:
context: .
args:
CT_ECR: foo
FOO:
NODE_ENV:
environment:
- NODE_ENV=test
- AWS_ACCESS_KEY_ID=dummy
- AWS_SECRET_ACCESS_KEY=dummy
env_file:
- ` + envf.Name() + `
`)
c, err := ParseCompose(dt)
require.NoError(t, err)
require.Equal(t, c.Targets[0].Args, map[string]string{"CT_ECR": "foo", "FOO": "bsdf -csdf", "NODE_ENV": "test"})
}
func newBool(val bool) *bool { func newBool(val bool) *bool {
b := val b := val
return &b return &b

@ -8,7 +8,7 @@ require (
github.com/bugsnag/panicwrap v1.2.0 // indirect github.com/bugsnag/panicwrap v1.2.0 // indirect
github.com/cenkalti/backoff v2.1.1+incompatible // indirect github.com/cenkalti/backoff v2.1.1+incompatible // indirect
github.com/cloudflare/cfssl v0.0.0-20181213083726-b94e044bb51e // indirect github.com/cloudflare/cfssl v0.0.0-20181213083726-b94e044bb51e // indirect
github.com/compose-spec/compose-go v1.0.5 github.com/compose-spec/compose-go v1.0.8
github.com/containerd/console v1.0.3 github.com/containerd/console v1.0.3
github.com/containerd/containerd v1.6.0-beta.3 github.com/containerd/containerd v1.6.0-beta.3
github.com/docker/cli v20.10.11+incompatible github.com/docker/cli v20.10.11+incompatible

@ -280,10 +280,10 @@ github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h
github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA= github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA=
github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI= github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI=
github.com/codahale/hdrhistogram v0.0.0-20160425231609-f8ad88b59a58/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/codahale/hdrhistogram v0.0.0-20160425231609-f8ad88b59a58/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
github.com/compose-spec/compose-go v1.0.5 h1:WtfK7tJsk5C8h12iggum7p28kTxeXH7Xi5c/pLfnBwk= github.com/compose-spec/compose-go v1.0.8 h1:fgT7mYYu5Sp37i2lUIAAvwJpkAHk6dP5ITHy/LlutUk=
github.com/compose-spec/compose-go v1.0.5/go.mod h1:LQ/JAjSIyh8bTu4RV6nkyf0Ow/Yf3qpvzrdEigxduiw= github.com/compose-spec/compose-go v1.0.8/go.mod h1:REnCbBugoIdHB7S1sfkN/aJ7AJpNApGNjNiVjA9L8x4=
github.com/compose-spec/godotenv v1.1.0 h1:wzShe5P6L/Aw3wsV357eWlZdMcPaOe2V2+3+qGwMEL4= github.com/compose-spec/godotenv v1.1.1 h1:lp+WpAInnw06YN9sV/XLUOV/9z4C+6wjJdWlrdVac7o=
github.com/compose-spec/godotenv v1.1.0/go.mod h1:zF/3BOa18Z24tts5qnO/E9YURQanJTBUf7nlcCTNsyc= github.com/compose-spec/godotenv v1.1.1/go.mod h1:zF/3BOa18Z24tts5qnO/E9YURQanJTBUf7nlcCTNsyc=
github.com/containerd/aufs v0.0.0-20200908144142-dab0cbea06f4/go.mod h1:nukgQABAEopAHvB6j7cnP5zJ+/3aVcE7hCYqvIwAHyE= github.com/containerd/aufs v0.0.0-20200908144142-dab0cbea06f4/go.mod h1:nukgQABAEopAHvB6j7cnP5zJ+/3aVcE7hCYqvIwAHyE=
github.com/containerd/aufs v0.0.0-20201003224125-76a6863f2989/go.mod h1:AkGGQs9NM2vtYHaUen+NljV0/baGCAPELGm2q9ZXpWU= github.com/containerd/aufs v0.0.0-20201003224125-76a6863f2989/go.mod h1:AkGGQs9NM2vtYHaUen+NljV0/baGCAPELGm2q9ZXpWU=
github.com/containerd/aufs v0.0.0-20210316121734-20793ff83c97/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU= github.com/containerd/aufs v0.0.0-20210316121734-20793ff83c97/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU=

@ -268,7 +268,7 @@ services:
- .:/code - .:/code
- ./static:/var/www/html - ./static:/var/www/html
# User-relative path # User-relative path
- ~/configs:/etc/configs/:ro - ~/configs:/etc/configs:ro
# Named volume # Named volume
- datavolume:/var/lib/mysql - datavolume:/var/lib/mysql
- type: bind - type: bind

@ -145,7 +145,7 @@ func Load(configDetails types.ConfigDetails, options ...func(*Options)) (*types.
op(opts) op(opts)
} }
configs := []*types.Config{} var configs []*types.Config
for i, file := range configDetails.ConfigFiles { for i, file := range configDetails.ConfigFiles {
configDict := file.Config configDict := file.Config
if configDict == nil { if configDict == nil {
@ -222,14 +222,14 @@ func Load(configDetails types.ConfigDetails, options ...func(*Options)) (*types.
} }
func parseConfig(b []byte, opts *Options) (map[string]interface{}, error) { func parseConfig(b []byte, opts *Options) (map[string]interface{}, error) {
yaml, err := ParseYAML(b) yml, err := ParseYAML(b)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if !opts.SkipInterpolation { if !opts.SkipInterpolation {
return interp.Interpolate(yaml, *opts.Interpolate) return interp.Interpolate(yml, *opts.Interpolate)
} }
return yaml, err return yml, err
} }
func groupXFieldsIntoExtensions(dict map[string]interface{}) map[string]interface{} { func groupXFieldsIntoExtensions(dict map[string]interface{}) map[string]interface{} {
@ -342,8 +342,8 @@ func createTransformHook(additionalTransformers ...Transformer) mapstructure.Dec
reflect.TypeOf(types.UlimitsConfig{}): transformUlimits, reflect.TypeOf(types.UlimitsConfig{}): transformUlimits,
reflect.TypeOf(types.UnitBytes(0)): transformSize, reflect.TypeOf(types.UnitBytes(0)): transformSize,
reflect.TypeOf([]types.ServicePortConfig{}): transformServicePort, reflect.TypeOf([]types.ServicePortConfig{}): transformServicePort,
reflect.TypeOf(types.ServiceSecretConfig{}): transformStringSourceMap, reflect.TypeOf(types.ServiceSecretConfig{}): transformFileReferenceConfig,
reflect.TypeOf(types.ServiceConfigObjConfig{}): transformStringSourceMap, reflect.TypeOf(types.ServiceConfigObjConfig{}): transformFileReferenceConfig,
reflect.TypeOf(types.StringOrNumberList{}): transformStringOrNumberList, reflect.TypeOf(types.StringOrNumberList{}): transformStringOrNumberList,
reflect.TypeOf(map[string]*types.ServiceNetworkConfig{}): transformServiceNetworkMap, reflect.TypeOf(map[string]*types.ServiceNetworkConfig{}): transformServiceNetworkMap,
reflect.TypeOf(types.Mapping{}): transformMappingOrListFunc("=", false), reflect.TypeOf(types.Mapping{}): transformMappingOrListFunc("=", false),
@ -372,7 +372,7 @@ func createTransformHook(additionalTransformers ...Transformer) mapstructure.Dec
} }
} }
// keys needs to be converted to strings for jsonschema // keys need to be converted to strings for jsonschema
func convertToStringKeysRecursive(value interface{}, keyPrefix string) (interface{}, error) { func convertToStringKeysRecursive(value interface{}, keyPrefix string) (interface{}, error) {
if mapping, ok := value.(map[interface{}]interface{}); ok { if mapping, ok := value.(map[interface{}]interface{}); ok {
dict := make(map[string]interface{}) dict := make(map[string]interface{})
@ -396,7 +396,7 @@ func convertToStringKeysRecursive(value interface{}, keyPrefix string) (interfac
return dict, nil return dict, nil
} }
if list, ok := value.([]interface{}); ok { if list, ok := value.([]interface{}); ok {
convertedList := []interface{}{} var convertedList []interface{}
for index, entry := range list { for index, entry := range list {
newKeyPrefix := fmt.Sprintf("%s[%d]", keyPrefix, index) newKeyPrefix := fmt.Sprintf("%s[%d]", keyPrefix, index)
convertedEntry, err := convertToStringKeysRecursive(entry, newKeyPrefix) convertedEntry, err := convertToStringKeysRecursive(entry, newKeyPrefix)
@ -532,7 +532,7 @@ func LoadService(name string, serviceDict map[string]interface{}, workingDir str
} }
for i, volume := range serviceConfig.Volumes { for i, volume := range serviceConfig.Volumes {
if volume.Type != "bind" { if volume.Type != types.VolumeTypeBind {
continue continue
} }
@ -552,14 +552,14 @@ func resolveEnvironment(serviceConfig *types.ServiceConfig, workingDir string, l
environment := types.MappingWithEquals{} environment := types.MappingWithEquals{}
if len(serviceConfig.EnvFile) > 0 { if len(serviceConfig.EnvFile) > 0 {
for _, file := range serviceConfig.EnvFile { for _, envFile := range serviceConfig.EnvFile {
filePath := absPath(workingDir, file) filePath := absPath(workingDir, envFile)
file, err := os.Open(filePath) file, err := os.Open(filePath)
if err != nil { if err != nil {
return err return err
} }
defer file.Close() defer file.Close()
fileVars, err := godotenv.Parse(file) fileVars, err := godotenv.ParseWithLookup(file, godotenv.LookupFn(lookupEnv))
if err != nil { if err != nil {
return err return err
} }
@ -797,7 +797,7 @@ var transformServicePort TransformerFunc = func(data interface{}) (interface{},
// We process the list instead of individual items here. // We process the list instead of individual items here.
// The reason is that one entry might be mapped to multiple ServicePortConfig. // The reason is that one entry might be mapped to multiple ServicePortConfig.
// Therefore we take an input of a list and return an output of a list. // Therefore we take an input of a list and return an output of a list.
ports := []interface{}{} var ports []interface{}
for _, entry := range entries { for _, entry := range entries {
switch value := entry.(type) { switch value := entry.(type) {
case int: case int:
@ -852,17 +852,27 @@ var transformServiceDeviceRequest TransformerFunc = func(data interface{}) (inte
} }
} }
var transformStringSourceMap TransformerFunc = func(data interface{}) (interface{}, error) { var transformFileReferenceConfig TransformerFunc = func(data interface{}) (interface{}, error) {
switch value := data.(type) { switch value := data.(type) {
case string: case string:
return map[string]interface{}{"source": value}, nil return map[string]interface{}{"source": value}, nil
case map[string]interface{}: case map[string]interface{}:
return groupXFieldsIntoExtensions(data.(map[string]interface{})), nil if target, ok := value["target"]; ok {
value["target"] = cleanTarget(target.(string))
}
return groupXFieldsIntoExtensions(value), nil
default: default:
return data, errors.Errorf("invalid type %T for secret", value) return data, errors.Errorf("invalid type %T for secret", value)
} }
} }
func cleanTarget(target string) string {
if target == "" {
return ""
}
return path.Clean(target)
}
var transformBuildConfig TransformerFunc = func(data interface{}) (interface{}, error) { var transformBuildConfig TransformerFunc = func(data interface{}) (interface{}, error) {
switch value := data.(type) { switch value := data.(type) {
case string: case string:
@ -906,9 +916,15 @@ var transformExtendsConfig TransformerFunc = func(data interface{}) (interface{}
var transformServiceVolumeConfig TransformerFunc = func(data interface{}) (interface{}, error) { var transformServiceVolumeConfig TransformerFunc = func(data interface{}) (interface{}, error) {
switch value := data.(type) { switch value := data.(type) {
case string: case string:
return ParseVolume(value) volume, err := ParseVolume(value)
volume.Target = cleanTarget(volume.Target)
return volume, err
case map[string]interface{}: case map[string]interface{}:
return groupXFieldsIntoExtensions(data.(map[string]interface{})), nil data := groupXFieldsIntoExtensions(data.(map[string]interface{}))
if target, ok := data["target"]; ok {
data["target"] = cleanTarget(target.(string))
}
return data, nil
default: default:
return data, errors.Errorf("invalid type %T for service volume", value) return data, errors.Errorf("invalid type %T for service volume", value)
} }
@ -971,7 +987,7 @@ func transformMappingOrList(mappingOrList interface{}, sep string, allowNil bool
switch value := mappingOrList.(type) { switch value := mappingOrList.(type) {
case map[string]interface{}: case map[string]interface{}:
return toMapStringString(value, allowNil) return toMapStringString(value, allowNil)
case ([]interface{}): case []interface{}:
result := make(map[string]interface{}) result := make(map[string]interface{})
for _, value := range value { for _, value := range value {
parts := strings.SplitN(value.(string), sep, 2) parts := strings.SplitN(value.(string), sep, 2)
@ -1054,7 +1070,7 @@ func toString(value interface{}, allowNil bool) interface{} {
} }
func toStringList(value map[string]interface{}, separator string, allowNil bool) []string { func toStringList(value map[string]interface{}, separator string, allowNil bool) []string {
output := []string{} var output []string
for key, value := range value { for key, value := range value {
if value == nil && !allowNil { if value == nil && !allowNil {
continue continue

@ -114,6 +114,11 @@ func _merge(baseService *types.ServiceConfig, overrideService *types.ServiceConf
if overrideService.Entrypoint != nil { if overrideService.Entrypoint != nil {
baseService.Entrypoint = overrideService.Entrypoint baseService.Entrypoint = overrideService.Entrypoint
} }
if baseService.Environment != nil {
baseService.Environment.OverrideBy(overrideService.Environment)
} else {
baseService.Environment = overrideService.Environment
}
return baseService, nil return baseService, nil
} }
@ -179,7 +184,7 @@ func toServiceVolumeConfigsMap(s interface{}) (map[interface{}]interface{}, erro
} }
func toServiceSecretConfigsSlice(dst reflect.Value, m map[interface{}]interface{}) error { func toServiceSecretConfigsSlice(dst reflect.Value, m map[interface{}]interface{}) error {
s := []types.ServiceSecretConfig{} var s []types.ServiceSecretConfig
for _, v := range m { for _, v := range m {
s = append(s, v.(types.ServiceSecretConfig)) s = append(s, v.(types.ServiceSecretConfig))
} }
@ -189,7 +194,7 @@ func toServiceSecretConfigsSlice(dst reflect.Value, m map[interface{}]interface{
} }
func toSServiceConfigObjConfigsSlice(dst reflect.Value, m map[interface{}]interface{}) error { func toSServiceConfigObjConfigsSlice(dst reflect.Value, m map[interface{}]interface{}) error {
s := []types.ServiceConfigObjConfig{} var s []types.ServiceConfigObjConfig
for _, v := range m { for _, v := range m {
s = append(s, v.(types.ServiceConfigObjConfig)) s = append(s, v.(types.ServiceConfigObjConfig))
} }
@ -199,7 +204,7 @@ func toSServiceConfigObjConfigsSlice(dst reflect.Value, m map[interface{}]interf
} }
func toServicePortConfigsSlice(dst reflect.Value, m map[interface{}]interface{}) error { func toServicePortConfigsSlice(dst reflect.Value, m map[interface{}]interface{}) error {
s := []types.ServicePortConfig{} var s []types.ServicePortConfig
for _, v := range m { for _, v := range m {
s = append(s, v.(types.ServicePortConfig)) s = append(s, v.(types.ServicePortConfig))
} }
@ -220,7 +225,7 @@ func toServicePortConfigsSlice(dst reflect.Value, m map[interface{}]interface{})
} }
func toServiceVolumeConfigsSlice(dst reflect.Value, m map[interface{}]interface{}) error { func toServiceVolumeConfigsSlice(dst reflect.Value, m map[interface{}]interface{}) error {
s := []types.ServiceVolumeConfig{} var s []types.ServiceVolumeConfig
for _, v := range m { for _, v := range m {
s = append(s, v.(types.ServiceVolumeConfig)) s = append(s, v.(types.ServiceVolumeConfig))
} }
@ -229,7 +234,7 @@ func toServiceVolumeConfigsSlice(dst reflect.Value, m map[interface{}]interface{
return nil return nil
} }
type tomapFn func(s interface{}) (map[interface{}]interface{}, error) type toMapFn func(s interface{}) (map[interface{}]interface{}, error)
type writeValueFromMapFn func(reflect.Value, map[interface{}]interface{}) error type writeValueFromMapFn func(reflect.Value, map[interface{}]interface{}) error
func safelyMerge(mergeFn func(dst, src reflect.Value) error) func(dst, src reflect.Value) error { func safelyMerge(mergeFn func(dst, src reflect.Value) error) func(dst, src reflect.Value) error {
@ -245,13 +250,13 @@ func safelyMerge(mergeFn func(dst, src reflect.Value) error) func(dst, src refle
} }
} }
func mergeSlice(tomap tomapFn, writeValue writeValueFromMapFn) func(dst, src reflect.Value) error { func mergeSlice(toMap toMapFn, writeValue writeValueFromMapFn) func(dst, src reflect.Value) error {
return func(dst, src reflect.Value) error { return func(dst, src reflect.Value) error {
dstMap, err := sliceToMap(tomap, dst) dstMap, err := sliceToMap(toMap, dst)
if err != nil { if err != nil {
return err return err
} }
srcMap, err := sliceToMap(tomap, src) srcMap, err := sliceToMap(toMap, src)
if err != nil { if err != nil {
return err return err
} }
@ -262,12 +267,12 @@ func mergeSlice(tomap tomapFn, writeValue writeValueFromMapFn) func(dst, src ref
} }
} }
func sliceToMap(tomap tomapFn, v reflect.Value) (map[interface{}]interface{}, error) { func sliceToMap(toMap toMapFn, v reflect.Value) (map[interface{}]interface{}, error) {
// check if valid // check if valid
if !v.IsValid() { if !v.IsValid() {
return nil, errors.Errorf("invalid value : %+v", v) return nil, errors.Errorf("invalid value : %+v", v)
} }
return tomap(v.Interface()) return toMap(v.Interface())
} }
func mergeLoggingConfig(dst, src reflect.Value) error { func mergeLoggingConfig(dst, src reflect.Value) error {

@ -36,11 +36,11 @@ func ParseVolume(spec string) (types.ServiceVolumeConfig, error) {
return volume, errors.New("invalid empty volume spec") return volume, errors.New("invalid empty volume spec")
case 1, 2: case 1, 2:
volume.Target = spec volume.Target = spec
volume.Type = string(types.VolumeTypeVolume) volume.Type = types.VolumeTypeVolume
return volume, nil return volume, nil
} }
buffer := []rune{} var buffer []rune
for _, char := range spec + string(endOfSpec) { for _, char := range spec + string(endOfSpec) {
switch { switch {
case isWindowsDrive(buffer, char): case isWindowsDrive(buffer, char):
@ -50,7 +50,7 @@ func ParseVolume(spec string) (types.ServiceVolumeConfig, error) {
populateType(&volume) populateType(&volume)
return volume, errors.Wrapf(err, "invalid spec: %s", spec) return volume, errors.Wrapf(err, "invalid spec: %s", spec)
} }
buffer = []rune{} buffer = nil
default: default:
buffer = append(buffer, char) buffer = append(buffer, char)
} }

@ -26,7 +26,7 @@ import (
var delimiter = "\\$" var delimiter = "\\$"
var substitutionNamed = "[_a-z][_a-z0-9]*" var substitutionNamed = "[_a-z][_a-z0-9]*"
var substitutionBraced = "[_a-z][_a-z0-9]*(?::?[-?][^}]*)?" var substitutionBraced = "[_a-z][_a-z0-9]*(?::?[-?](.*}|[^}]*))?"
var patternString = fmt.Sprintf( var patternString = fmt.Sprintf(
"%s(?i:(?P<escaped>%s)|(?P<named>%s)|{(?P<braced>%s)}|(?P<invalid>))", "%s(?i:(?P<escaped>%s)|(?P<named>%s)|{(?P<braced>%s)}|(?P<invalid>))",
@ -35,14 +35,6 @@ var patternString = fmt.Sprintf(
var defaultPattern = regexp.MustCompile(patternString) var defaultPattern = regexp.MustCompile(patternString)
// DefaultSubstituteFuncs contains the default SubstituteFunc used by the docker cli
var DefaultSubstituteFuncs = []SubstituteFunc{
softDefault,
hardDefault,
requiredNonEmpty,
required,
}
// InvalidTemplateError is returned when a variable template is not in a valid // InvalidTemplateError is returned when a variable template is not in a valid
// format // format
type InvalidTemplateError struct { type InvalidTemplateError struct {
@ -67,6 +59,14 @@ type SubstituteFunc func(string, Mapping) (string, bool, error)
// SubstituteWith substitute variables in the string with their values. // SubstituteWith substitute variables in the string with their values.
// It accepts additional substitute function. // It accepts additional substitute function.
func SubstituteWith(template string, mapping Mapping, pattern *regexp.Regexp, subsFuncs ...SubstituteFunc) (string, error) { func SubstituteWith(template string, mapping Mapping, pattern *regexp.Regexp, subsFuncs ...SubstituteFunc) (string, error) {
if len(subsFuncs) == 0 {
subsFuncs = []SubstituteFunc{
softDefault,
hardDefault,
requiredNonEmpty,
required,
}
}
var err error var err error
result := pattern.ReplaceAllStringFunc(template, func(substring string) string { result := pattern.ReplaceAllStringFunc(template, func(substring string) string {
matches := pattern.FindStringSubmatch(substring) matches := pattern.FindStringSubmatch(substring)
@ -116,7 +116,7 @@ func SubstituteWith(template string, mapping Mapping, pattern *regexp.Regexp, su
// Substitute variables in the string with their values // Substitute variables in the string with their values
func Substitute(template string, mapping Mapping) (string, error) { func Substitute(template string, mapping Mapping) (string, error) {
return SubstituteWith(template, mapping, defaultPattern, DefaultSubstituteFuncs...) return SubstituteWith(template, mapping, defaultPattern)
} }
// ExtractVariables returns a map of all the variables defined in the specified // ExtractVariables returns a map of all the variables defined in the specified
@ -215,6 +215,10 @@ func softDefault(substitution string, mapping Mapping) (string, bool, error) {
return "", false, nil return "", false, nil
} }
name, defaultValue := partition(substitution, sep) name, defaultValue := partition(substitution, sep)
defaultValue, err := Substitute(defaultValue, mapping)
if err != nil {
return "", false, err
}
value, ok := mapping(name) value, ok := mapping(name)
if !ok || value == "" { if !ok || value == "" {
return defaultValue, true, nil return defaultValue, true, nil
@ -229,6 +233,10 @@ func hardDefault(substitution string, mapping Mapping) (string, bool, error) {
return "", false, nil return "", false, nil
} }
name, defaultValue := partition(substitution, sep) name, defaultValue := partition(substitution, sep)
defaultValue, err := Substitute(defaultValue, mapping)
if err != nil {
return "", false, err
}
value, ok := mapping(name) value, ok := mapping(name)
if !ok { if !ok {
return defaultValue, true, nil return defaultValue, true, nil
@ -249,6 +257,10 @@ func withRequired(substitution string, mapping Mapping, sep string, valid func(s
return "", false, nil return "", false, nil
} }
name, errorMessage := partition(substitution, sep) name, errorMessage := partition(substitution, sep)
errorMessage, err := Substitute(errorMessage, mapping)
if err != nil {
return "", false, err
}
value, ok := mapping(name) value, ok := mapping(name)
if !ok || !valid(value) { if !ok || !valid(value) {
return "", true, &InvalidTemplateError{ return "", true, &InvalidTemplateError{

@ -46,7 +46,7 @@ type Project struct {
// ServiceNames return names for all services in this Compose config // ServiceNames return names for all services in this Compose config
func (p Project) ServiceNames() []string { func (p Project) ServiceNames() []string {
names := []string{} var names []string
for _, s := range p.Services { for _, s := range p.Services {
names = append(names, s.Name) names = append(names, s.Name)
} }
@ -56,7 +56,7 @@ func (p Project) ServiceNames() []string {
// VolumeNames return names for all volumes in this Compose config // VolumeNames return names for all volumes in this Compose config
func (p Project) VolumeNames() []string { func (p Project) VolumeNames() []string {
names := []string{} var names []string
for k := range p.Volumes { for k := range p.Volumes {
names = append(names, k) names = append(names, k)
} }
@ -66,7 +66,7 @@ func (p Project) VolumeNames() []string {
// NetworkNames return names for all volumes in this Compose config // NetworkNames return names for all volumes in this Compose config
func (p Project) NetworkNames() []string { func (p Project) NetworkNames() []string {
names := []string{} var names []string
for k := range p.Networks { for k := range p.Networks {
names = append(names, k) names = append(names, k)
} }
@ -76,7 +76,7 @@ func (p Project) NetworkNames() []string {
// SecretNames return names for all secrets in this Compose config // SecretNames return names for all secrets in this Compose config
func (p Project) SecretNames() []string { func (p Project) SecretNames() []string {
names := []string{} var names []string
for k := range p.Secrets { for k := range p.Secrets {
names = append(names, k) names = append(names, k)
} }
@ -86,7 +86,7 @@ func (p Project) SecretNames() []string {
// ConfigNames return names for all configs in this Compose config // ConfigNames return names for all configs in this Compose config
func (p Project) ConfigNames() []string { func (p Project) ConfigNames() []string {
names := []string{} var names []string
for k := range p.Configs { for k := range p.Configs {
names = append(names, k) names = append(names, k)
} }
@ -179,12 +179,12 @@ func (p *Project) RelativePath(path string) string {
} }
// HasProfile return true if service has no profile declared or has at least one profile matching // HasProfile return true if service has no profile declared or has at least one profile matching
func (service ServiceConfig) HasProfile(profiles []string) bool { func (s ServiceConfig) HasProfile(profiles []string) bool {
if len(service.Profiles) == 0 { if len(s.Profiles) == 0 {
return true return true
} }
for _, p := range profiles { for _, p := range profiles {
for _, sp := range service.Profiles { for _, sp := range s.Profiles {
if sp == p { if sp == p {
return true return true
} }
@ -327,7 +327,6 @@ func (p *Project) ResolveImages(resolver func(named reference.Named) (digest.Dig
if err != nil { if err != nil {
return err return err
} }
named, err = reference.WithDigest(named, digest) named, err = reference.WithDigest(named, digest)
if err != nil { if err != nil {
return err return err

@ -33,7 +33,7 @@ func (d Duration) String() string {
return time.Duration(d).String() return time.Duration(d).String()
} }
// ConvertDurationPtr converts a typedefined Duration pointer to a time.Duration pointer with the same value. // ConvertDurationPtr converts a type defined Duration pointer to a time.Duration pointer with the same value.
func ConvertDurationPtr(d *Duration) *time.Duration { func ConvertDurationPtr(d *Duration) *time.Duration {
if d == nil { if d == nil {
return nil return nil
@ -121,7 +121,7 @@ type ServiceConfig struct {
Extends ExtendsConfig `yaml:"extends,omitempty" json:"extends,omitempty"` Extends ExtendsConfig `yaml:"extends,omitempty" json:"extends,omitempty"`
ExternalLinks []string `mapstructure:"external_links" yaml:"external_links,omitempty" json:"external_links,omitempty"` ExternalLinks []string `mapstructure:"external_links" yaml:"external_links,omitempty" json:"external_links,omitempty"`
ExtraHosts HostsList `mapstructure:"extra_hosts" yaml:"extra_hosts,omitempty" json:"extra_hosts,omitempty"` ExtraHosts HostsList `mapstructure:"extra_hosts" yaml:"extra_hosts,omitempty" json:"extra_hosts,omitempty"`
GroupAdd []string `mapstructure:"group_app" yaml:"group_add,omitempty" json:"group_add,omitempty"` GroupAdd []string `mapstructure:"group_add" yaml:"group_add,omitempty" json:"group_add,omitempty"`
Hostname string `yaml:",omitempty" json:"hostname,omitempty"` Hostname string `yaml:",omitempty" json:"hostname,omitempty"`
HealthCheck *HealthCheckConfig `yaml:",omitempty" json:"healthcheck,omitempty"` HealthCheck *HealthCheckConfig `yaml:",omitempty" json:"healthcheck,omitempty"`
Image string `yaml:",omitempty" json:"image,omitempty"` Image string `yaml:",omitempty" json:"image,omitempty"`
@ -208,7 +208,7 @@ const (
PullPolicyNever = "never" PullPolicyNever = "never"
//PullPolicyIfNotPresent pull missing images //PullPolicyIfNotPresent pull missing images
PullPolicyIfNotPresent = "if_not_present" PullPolicyIfNotPresent = "if_not_present"
//PullPolicyIfNotPresent pull missing images //PullPolicyMissing pull missing images
PullPolicyMissing = "missing" PullPolicyMissing = "missing"
//PullPolicyBuild force building images //PullPolicyBuild force building images
PullPolicyBuild = "build" PullPolicyBuild = "build"
@ -611,7 +611,7 @@ func ParsePortConfig(value string) ([]ServicePortConfig, error) {
} }
func convertPortToPortConfig(port nat.Port, portBindings map[nat.Port][]nat.PortBinding) ([]ServicePortConfig, error) { func convertPortToPortConfig(port nat.Port, portBindings map[nat.Port][]nat.PortBinding) ([]ServicePortConfig, error) {
portConfigs := []ServicePortConfig{} var portConfigs []ServicePortConfig
for _, binding := range portBindings[port] { for _, binding := range portBindings[port] {
startHostPort, endHostPort, err := nat.ParsePortRange(binding.HostPort) startHostPort, endHostPort, err := nat.ParsePortRange(binding.HostPort)
@ -647,13 +647,13 @@ type ServiceVolumeConfig struct {
} }
const ( const (
// TypeBind is the type for mounting host dir // VolumeTypeBind is the type for mounting host dir
VolumeTypeBind = "bind" VolumeTypeBind = "bind"
// TypeVolume is the type for remote storage volumes // VolumeTypeVolume is the type for remote storage volumes
VolumeTypeVolume = "volume" VolumeTypeVolume = "volume"
// TypeTmpfs is the type for mounting tmpfs // VolumeTypeTmpfs is the type for mounting tmpfs
VolumeTypeTmpfs = "tmpfs" VolumeTypeTmpfs = "tmpfs"
// TypeNamedPipe is the type for mounting Windows named pipes // VolumeTypeNamedPipe is the type for mounting Windows named pipes
VolumeTypeNamedPipe = "npipe" VolumeTypeNamedPipe = "npipe"
) )

@ -266,12 +266,12 @@ func parseLineWithLookup(line string, envMap map[string]string, lookupFn LookupF
firstColon := strings.Index(line, ":") firstColon := strings.Index(line, ":")
splitString := strings.SplitN(line, "=", 2) splitString := strings.SplitN(line, "=", 2)
if firstColon != -1 && (firstColon < firstEquals || firstEquals == -1) { if firstColon != -1 && (firstColon < firstEquals || firstEquals == -1) {
//this is a yaml-style line // This is a yaml-style line
splitString = strings.SplitN(line, ":", 2) splitString = strings.SplitN(line, ":", 2)
} }
if len(splitString) != 2 { if len(splitString) != 2 {
err = errors.New("Can't separate key from value") err = errors.New("can't separate key from value")
return return
} }
key = exportRegex.ReplaceAllString(splitString[0], "$1") key = exportRegex.ReplaceAllString(splitString[0], "$1")
@ -341,15 +341,15 @@ func expandVariables(v string, envMap map[string]string, lookupFn LookupFn) stri
if submatch[1] == "\\" || submatch[2] == "(" { if submatch[1] == "\\" || submatch[2] == "(" {
return submatch[0][1:] return submatch[0][1:]
} else if submatch[4] != "" { } else if submatch[4] != "" {
//first check if we have defined this already earlier // first check if we have defined this already earlier
if envMap[submatch[4]] != "" { if envMap[submatch[4]] != "" {
return envMap[submatch[4]] return envMap[submatch[4]]
} }
if lookupFn == nil { if lookupFn == nil {
return "" return ""
} }
//if we have not defined it, check the lookup function provided // if we have not defined it, check the lookup function provided
//by the user // by the user
s2, ok := lookupFn(submatch[4]) s2, ok := lookupFn(submatch[4])
if ok { if ok {
return s2 return s2

@ -127,15 +127,21 @@ loop:
// extractVarValue extracts variable value and returns rest of slice // extractVarValue extracts variable value and returns rest of slice
func extractVarValue(src []byte, envMap map[string]string, lookupFn LookupFn) (value string, rest []byte, err error) { func extractVarValue(src []byte, envMap map[string]string, lookupFn LookupFn) (value string, rest []byte, err error) {
quote, hasPrefix := hasQuotePrefix(src) quote, isQuoted := hasQuotePrefix(src)
if !hasPrefix { if !isQuoted {
// unquoted value - read until whitespace // unquoted value - read until new line
end := bytes.IndexFunc(src, unicode.IsSpace) end := bytes.IndexFunc(src, isNewLine)
if end == -1 { var rest []byte
return expandVariables(string(src), envMap, lookupFn), nil, nil var value string
if end < 0 {
value := strings.TrimRightFunc(string(src), unicode.IsSpace)
rest = nil
return expandVariables(value, envMap, lookupFn), rest, nil
} }
return expandVariables(string(src[0:end]), envMap, lookupFn), src[end:], nil value = strings.TrimRightFunc(string(src[0:end]), unicode.IsSpace)
rest = src[end:]
return expandVariables(value, envMap, lookupFn), rest, nil
} }
// lookup quoted string terminator // lookup quoted string terminator
@ -192,7 +198,7 @@ func indexOfNonSpaceChar(src []byte) int {
} }
// hasQuotePrefix reports whether charset starts with single or double quote and returns quote character // hasQuotePrefix reports whether charset starts with single or double quote and returns quote character
func hasQuotePrefix(src []byte) (prefix byte, isQuored bool) { func hasQuotePrefix(src []byte) (quote byte, isQuoted bool) {
if len(src) == 0 { if len(src) == 0 {
return 0, false return 0, false
} }
@ -221,3 +227,9 @@ func isSpace(r rune) bool {
} }
return false return false
} }
// isNewLine reports whether the rune is a new line character
func isNewLine(r rune) bool {
return r == '\n'
}

@ -30,7 +30,7 @@ github.com/cenkalti/backoff/v4
github.com/cespare/xxhash/v2 github.com/cespare/xxhash/v2
# github.com/cloudflare/cfssl v0.0.0-20181213083726-b94e044bb51e # github.com/cloudflare/cfssl v0.0.0-20181213083726-b94e044bb51e
## explicit ## explicit
# github.com/compose-spec/compose-go v1.0.5 # github.com/compose-spec/compose-go v1.0.8
## explicit ## explicit
github.com/compose-spec/compose-go/errdefs github.com/compose-spec/compose-go/errdefs
github.com/compose-spec/compose-go/interpolation github.com/compose-spec/compose-go/interpolation
@ -38,7 +38,7 @@ github.com/compose-spec/compose-go/loader
github.com/compose-spec/compose-go/schema github.com/compose-spec/compose-go/schema
github.com/compose-spec/compose-go/template github.com/compose-spec/compose-go/template
github.com/compose-spec/compose-go/types github.com/compose-spec/compose-go/types
# github.com/compose-spec/godotenv v1.1.0 # github.com/compose-spec/godotenv v1.1.1
github.com/compose-spec/godotenv github.com/compose-spec/godotenv
# github.com/containerd/console v1.0.3 # github.com/containerd/console v1.0.3
## explicit ## explicit

Loading…
Cancel
Save