diff --git a/go.mod b/go.mod index 9a12ccb8134..aeaddc83753 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/bugsnag/panicwrap v1.2.0 // indirect github.com/cenkalti/backoff v2.1.1+incompatible // indirect github.com/cloudflare/cfssl v0.0.0-20181213083726-b94e044bb51e // indirect - github.com/compose-spec/compose-go v0.0.0-20210729195839-de56f4f0cb3c + github.com/compose-spec/compose-go v1.0.5 github.com/containerd/console v1.0.3 github.com/containerd/containerd v1.5.5 github.com/docker/cli v20.10.8+incompatible diff --git a/go.sum b/go.sum index 836d69f8e82..f055914f146 100644 --- a/go.sum +++ b/go.sum @@ -262,8 +262,10 @@ github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnht github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/codahale/hdrhistogram v0.0.0-20160425231609-f8ad88b59a58/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= -github.com/compose-spec/compose-go v0.0.0-20210729195839-de56f4f0cb3c h1:lSR4wokZlq+Q8uJpgZuFMs3VoLaYVV07cJOZHa1zRBg= -github.com/compose-spec/compose-go v0.0.0-20210729195839-de56f4f0cb3c/go.mod h1:5V65rPnTvvQagtoMxTneJ2QicLq6ZRQQ7fOgPN226fo= +github.com/compose-spec/compose-go v1.0.5 h1:WtfK7tJsk5C8h12iggum7p28kTxeXH7Xi5c/pLfnBwk= +github.com/compose-spec/compose-go v1.0.5/go.mod h1:LQ/JAjSIyh8bTu4RV6nkyf0Ow/Yf3qpvzrdEigxduiw= +github.com/compose-spec/godotenv v1.1.0 h1:wzShe5P6L/Aw3wsV357eWlZdMcPaOe2V2+3+qGwMEL4= +github.com/compose-spec/godotenv v1.1.0/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-20201003224125-76a6863f2989/go.mod h1:AkGGQs9NM2vtYHaUen+NljV0/baGCAPELGm2q9ZXpWU= github.com/containerd/aufs v0.0.0-20210316121734-20793ff83c97/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU= @@ -794,7 +796,6 @@ github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= github.com/jmoiron/sqlx v1.2.1-0.20190826204134-d7d95172beb5/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= github.com/joefitzgerald/rainbow-reporter v0.1.0/go.mod h1:481CNgqmVHQZzdIbN52CupLJyoVwB10FQ/IQlF1pdL8= -github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7/go.mod h1:2iMrUgbbvHEiQClaW2NsSzMyGHqN+rDFqY705q49KG0= @@ -903,8 +904,9 @@ github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0Qu github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.3.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.4.2 h1:6h7AQ0yhTcIsmFmnAwQls75jp2Gzs4iB8W7pjMO+rqo= +github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A= github.com/moby/buildkit v0.8.1/go.mod h1:/kyU1hKy/aYCuP39GZA9MaKioovHku57N6cqlKZIaiQ= github.com/moby/buildkit v0.9.1-0.20211019185819-8778943ac3da h1:DJ6zT0cdxXgUf17GmYAWqCGv47cJXx7nZ1CTHojZ3A4= diff --git a/vendor/github.com/compose-spec/compose-go/loader/interpolate.go b/vendor/github.com/compose-spec/compose-go/loader/interpolate.go index b45172a5960..76839a6d5d6 100644 --- a/vendor/github.com/compose-spec/compose-go/loader/interpolate.go +++ b/vendor/github.com/compose-spec/compose-go/loader/interpolate.go @@ -21,14 +21,21 @@ import ( "strings" interp "github.com/compose-spec/compose-go/interpolation" + "github.com/compose-spec/compose-go/types" "github.com/pkg/errors" ) var interpolateTypeCastMapping = map[interp.Path]interp.Cast{ servicePath("configs", interp.PathMatchList, "mode"): toInt, - servicePath("secrets", interp.PathMatchList, "mode"): toInt, - servicePath("healthcheck", "retries"): toInt, - servicePath("healthcheck", "disable"): toBoolean, + servicePath("cpu_count"): toInt64, + servicePath("cpu_percent"): toFloat, + servicePath("cpu_period"): toInt64, + servicePath("cpu_quota"): toInt64, + servicePath("cpu_rt_period"): toInt64, + servicePath("cpu_rt_runtime"): toInt64, + servicePath("cpus"): toFloat32, + servicePath("cpu_shares"): toInt64, + servicePath("init"): toBoolean, servicePath("deploy", "replicas"): toInt, servicePath("deploy", "update_config", "parallelism"): toInt, servicePath("deploy", "update_config", "max_failure_ratio"): toFloat, @@ -36,20 +43,35 @@ var interpolateTypeCastMapping = map[interp.Path]interp.Cast{ servicePath("deploy", "rollback_config", "max_failure_ratio"): toFloat, servicePath("deploy", "restart_policy", "max_attempts"): toInt, servicePath("deploy", "placement", "max_replicas_per_node"): toInt, + servicePath("healthcheck", "retries"): toInt, + servicePath("healthcheck", "disable"): toBoolean, + servicePath("mem_limit"): toUnitBytes, + servicePath("mem_reservation"): toUnitBytes, + servicePath("memswap_limit"): toUnitBytes, + servicePath("mem_swappiness"): toUnitBytes, + servicePath("oom_kill_disable"): toBoolean, + servicePath("oom_score_adj"): toInt64, + servicePath("pids_limit"): toInt64, servicePath("ports", interp.PathMatchList, "target"): toInt, servicePath("ports", interp.PathMatchList, "published"): toInt, - servicePath("ulimits", interp.PathMatchAll): toInt, - servicePath("ulimits", interp.PathMatchAll, "hard"): toInt, - servicePath("ulimits", interp.PathMatchAll, "soft"): toInt, servicePath("privileged"): toBoolean, servicePath("read_only"): toBoolean, + servicePath("scale"): toInt, + servicePath("secrets", interp.PathMatchList, "mode"): toInt, + servicePath("shm_size"): toUnitBytes, servicePath("stdin_open"): toBoolean, + servicePath("stop_grace_period"): toDuration, servicePath("tty"): toBoolean, + servicePath("ulimits", interp.PathMatchAll): toInt, + servicePath("ulimits", interp.PathMatchAll, "hard"): toInt, + servicePath("ulimits", interp.PathMatchAll, "soft"): toInt, servicePath("volumes", interp.PathMatchList, "read_only"): toBoolean, servicePath("volumes", interp.PathMatchList, "volume", "nocopy"): toBoolean, + servicePath("volumes", interp.PathMatchList, "tmpfs", "size"): toUnitBytes, iPath("networks", interp.PathMatchAll, "external"): toBoolean, iPath("networks", interp.PathMatchAll, "internal"): toBoolean, iPath("networks", interp.PathMatchAll, "attachable"): toBoolean, + iPath("networks", interp.PathMatchAll, "enable_ipv6"): toBoolean, iPath("volumes", interp.PathMatchAll, "external"): toBoolean, iPath("secrets", interp.PathMatchAll, "external"): toBoolean, iPath("configs", interp.PathMatchAll, "external"): toBoolean, @@ -67,10 +89,38 @@ func toInt(value string) (interface{}, error) { return strconv.Atoi(value) } +func toInt64(value string) (interface{}, error) { + return strconv.ParseInt(value, 10, 64) +} + +func toUnitBytes(value string) (interface{}, error) { + i, err := strconv.ParseInt(value, 10, 64) + if err != nil { + return nil, err + } + return types.UnitBytes(i), nil +} + +func toDuration(value string) (interface{}, error) { + i, err := strconv.ParseInt(value, 10, 64) + if err != nil { + return nil, err + } + return types.Duration(i), nil +} + func toFloat(value string) (interface{}, error) { return strconv.ParseFloat(value, 64) } +func toFloat32(value string) (interface{}, error) { + f, err := strconv.ParseFloat(value, 32) + if err != nil { + return nil, err + } + return float32(f), nil +} + // should match http://yaml.org/type/bool.html func toBoolean(value string) (interface{}, error) { switch strings.ToLower(value) { diff --git a/vendor/github.com/compose-spec/compose-go/loader/loader.go b/vendor/github.com/compose-spec/compose-go/loader/loader.go index de145b5a945..57505ee54ba 100644 --- a/vendor/github.com/compose-spec/compose-go/loader/loader.go +++ b/vendor/github.com/compose-spec/compose-go/loader/loader.go @@ -31,14 +31,13 @@ import ( "github.com/compose-spec/compose-go/schema" "github.com/compose-spec/compose-go/template" "github.com/compose-spec/compose-go/types" - units "github.com/docker/go-units" - "github.com/imdario/mergo" - "github.com/joho/godotenv" - shellwords "github.com/mattn/go-shellwords" + "github.com/compose-spec/godotenv" + "github.com/docker/go-units" + "github.com/mattn/go-shellwords" "github.com/mitchellh/mapstructure" "github.com/pkg/errors" "github.com/sirupsen/logrus" - yaml "gopkg.in/yaml.v2" + "gopkg.in/yaml.v2" ) // Options supported by Load @@ -49,6 +48,8 @@ type Options struct { SkipInterpolation bool // Skip normalization SkipNormalization bool + // Resolve paths + ResolvePaths bool // Skip consistency check SkipConsistencyCheck bool // Skip extends @@ -103,6 +104,11 @@ func WithDiscardEnvFiles(opts *Options) { opts.discardEnvFiles = true } +// WithSkipValidation sets the Options to skip validation when loading sections +func WithSkipValidation(opts *Options) { + opts.SkipValidation = true +} + // ParseYAML reads the bytes from a file, parses the bytes into a mapping // structure, and returns it. func ParseYAML(source []byte) (map[string]interface{}, error) { @@ -199,7 +205,7 @@ func Load(configDetails types.ConfigDetails, options ...func(*Options)) (*types. } if !opts.SkipNormalization { - err = normalize(project) + err = normalize(project, opts.ResolvePaths) if err != nil { return nil, err } @@ -216,34 +222,14 @@ func Load(configDetails types.ConfigDetails, options ...func(*Options)) (*types. } func parseConfig(b []byte, opts *Options) (map[string]interface{}, error) { - if !opts.SkipInterpolation { - withoutComments, err := removeYamlComments(b) - if err != nil { - return nil, err - } - - substituted, err := opts.Interpolate.Substitute(string(withoutComments), template.Mapping(opts.Interpolate.LookupValue)) - if err != nil { - return nil, err - } - b = []byte(substituted) - } - - return ParseYAML(b) -} - -// removeYamlComments drop all comments from the yaml file, so we don't try to apply string substitutions on irrelevant places -func removeYamlComments(b []byte) ([]byte, error) { - var cfg interface{} - err := yaml.Unmarshal(b, &cfg) + yaml, err := ParseYAML(b) if err != nil { return nil, err } - b, err = yaml.Marshal(cfg) - if err != nil { - return nil, err + if !opts.SkipInterpolation { + return interp.Interpolate(yaml, *opts.Interpolate) } - return b, nil + return yaml, err } func groupXFieldsIntoExtensions(dict map[string]interface{}) map[string]interface{} { @@ -274,7 +260,7 @@ func loadSections(filename string, config map[string]interface{}, configDetails return nil, err } - cfg.Networks, err = LoadNetworks(getSection(config, "networks"), configDetails.Version) + cfg.Networks, err = LoadNetworks(getSection(config, "networks")) if err != nil { return nil, err } @@ -282,11 +268,11 @@ func loadSections(filename string, config map[string]interface{}, configDetails if err != nil { return nil, err } - cfg.Secrets, err = LoadSecrets(getSection(config, "secrets"), configDetails) + cfg.Secrets, err = LoadSecrets(getSection(config, "secrets"), configDetails, opts.ResolvePaths) if err != nil { return nil, err } - cfg.Configs, err = LoadConfigObjs(getSection(config, "configs"), configDetails) + cfg.Configs, err = LoadConfigObjs(getSection(config, "configs"), configDetails, opts.ResolvePaths) if err != nil { return nil, err } @@ -439,6 +425,14 @@ func formatInvalidKeyError(keyPrefix string, key interface{}) error { func LoadServices(filename string, servicesDict map[string]interface{}, workingDir string, lookupEnv template.Mapping, opts *Options) ([]types.ServiceConfig, error) { var services []types.ServiceConfig + x, ok := servicesDict["extensions"] + if ok { + // as a top-level attribute, "services" doesn't support extensions, and a service can be named `x-foo` + for k, v := range x.(map[string]interface{}) { + servicesDict[k] = v + } + } + for name := range servicesDict { serviceConfig, err := loadServiceWithExtends(filename, name, servicesDict, workingDir, lookupEnv, opts, &cycleTracker{}) if err != nil { @@ -456,7 +450,12 @@ func loadServiceWithExtends(filename, name string, servicesDict map[string]inter return nil, err } - serviceConfig, err := LoadService(name, servicesDict[name].(map[string]interface{}), workingDir, lookupEnv) + target, ok := servicesDict[name] + if !ok { + return nil, fmt.Errorf("cannot extend service %q in %s: service not found", name, filename) + } + + serviceConfig, err := LoadService(name, target.(map[string]interface{}), workingDir, lookupEnv, opts.ResolvePaths) if err != nil { return nil, err } @@ -478,15 +477,7 @@ func loadServiceWithExtends(filename, name string, servicesDict map[string]inter return nil, err } - if !opts.SkipInterpolation { - substitute, err := opts.Interpolate.Substitute(string(bytes), template.Mapping(opts.Interpolate.LookupValue)) - if err != nil { - return nil, err - } - bytes = []byte(substitute) - } - - baseFile, err := ParseYAML(bytes) + baseFile, err := parseConfig(bytes, opts) if err != nil { return nil, err } @@ -516,10 +507,10 @@ func loadServiceWithExtends(filename, name string, servicesDict map[string]inter } } - if err := mergo.Merge(baseService, serviceConfig, mergo.WithAppendSlice, mergo.WithOverride, mergo.WithTransformers(serviceSpecials)); err != nil { - return nil, errors.Wrapf(err, "cannot merge service %s", name) + serviceConfig, err = _merge(baseService, serviceConfig) + if err != nil { + return nil, err } - serviceConfig = baseService } return serviceConfig, nil @@ -527,8 +518,10 @@ func loadServiceWithExtends(filename, name string, servicesDict map[string]inter // LoadService produces a single ServiceConfig from a compose file Dict // the serviceDict is not validated if directly used. Use Load() to enable validation -func LoadService(name string, serviceDict map[string]interface{}, workingDir string, lookupEnv template.Mapping) (*types.ServiceConfig, error) { - serviceConfig := &types.ServiceConfig{} +func LoadService(name string, serviceDict map[string]interface{}, workingDir string, lookupEnv template.Mapping, resolvePaths bool) (*types.ServiceConfig, error) { + serviceConfig := &types.ServiceConfig{ + Scale: 1, + } if err := Transform(serviceDict, serviceConfig); err != nil { return nil, err } @@ -538,8 +531,18 @@ func LoadService(name string, serviceDict map[string]interface{}, workingDir str return nil, err } - if err := resolveVolumePaths(serviceConfig.Volumes, workingDir, lookupEnv); err != nil { - return nil, err + for i, volume := range serviceConfig.Volumes { + if volume.Type != "bind" { + continue + } + + if volume.Source == "" { + return nil, errors.New(`invalid mount config for type "bind": field Source must not be empty`) + } + + if resolvePaths { + serviceConfig.Volumes[i] = resolveVolumePath(volume, workingDir, lookupEnv) + } } return serviceConfig, nil @@ -574,30 +577,19 @@ func resolveEnvironment(serviceConfig *types.ServiceConfig, workingDir string, l return nil } -func resolveVolumePaths(volumes []types.ServiceVolumeConfig, workingDir string, lookupEnv template.Mapping) error { - for i, volume := range volumes { - if volume.Type != "bind" { - continue - } - - if volume.Source == "" { - return errors.New(`invalid mount config for type "bind": field Source must not be empty`) - } - - filePath := expandUser(volume.Source, lookupEnv) - // Check if source is an absolute path (either Unix or Windows), to - // handle a Windows client with a Unix daemon or vice-versa. - // - // Note that this is not required for Docker for Windows when specifying - // a local Windows path, because Docker for Windows translates the Windows - // path into a valid path within the VM. - if !path.IsAbs(filePath) && !isAbs(filePath) { - filePath = absPath(workingDir, filePath) - } - volume.Source = filePath - volumes[i] = volume - } - return nil +func resolveVolumePath(volume types.ServiceVolumeConfig, workingDir string, lookupEnv template.Mapping) types.ServiceVolumeConfig { + filePath := expandUser(volume.Source, lookupEnv) + // Check if source is an absolute path (either Unix or Windows), to + // handle a Windows client with a Unix daemon or vice-versa. + // + // Note that this is not required for Docker for Windows when specifying + // a local Windows path, because Docker for Windows translates the Windows + // path into a valid path within the VM. + if !path.IsAbs(filePath) && !isAbs(filePath) { + filePath = absPath(workingDir, filePath) + } + volume.Source = filePath + return volume } // TODO: make this more robust @@ -633,7 +625,7 @@ func transformUlimits(data interface{}) (interface{}, error) { // LoadNetworks produces a NetworkConfig map from a compose file Dict // the source Dict is not validated if directly used. Use Load() to enable validation -func LoadNetworks(source map[string]interface{}, version string) (map[string]types.NetworkConfig, error) { +func LoadNetworks(source map[string]interface{}) (map[string]types.NetworkConfig, error) { networks := make(map[string]types.NetworkConfig) err := Transform(source, &networks) if err != nil { @@ -701,13 +693,13 @@ func LoadVolumes(source map[string]interface{}) (map[string]types.VolumeConfig, // LoadSecrets produces a SecretConfig map from a compose file Dict // the source Dict is not validated if directly used. Use Load() to enable validation -func LoadSecrets(source map[string]interface{}, details types.ConfigDetails) (map[string]types.SecretConfig, error) { +func LoadSecrets(source map[string]interface{}, details types.ConfigDetails, resolvePaths bool) (map[string]types.SecretConfig, error) { secrets := make(map[string]types.SecretConfig) if err := Transform(source, &secrets); err != nil { return secrets, err } for name, secret := range secrets { - obj, err := loadFileObjectConfig(name, "secret", types.FileObjectConfig(secret), details) + obj, err := loadFileObjectConfig(name, "secret", types.FileObjectConfig(secret), details, resolvePaths) if err != nil { return nil, err } @@ -719,13 +711,13 @@ func LoadSecrets(source map[string]interface{}, details types.ConfigDetails) (ma // LoadConfigObjs produces a ConfigObjConfig map from a compose file Dict // the source Dict is not validated if directly used. Use Load() to enable validation -func LoadConfigObjs(source map[string]interface{}, details types.ConfigDetails) (map[string]types.ConfigObjConfig, error) { +func LoadConfigObjs(source map[string]interface{}, details types.ConfigDetails, resolvePaths bool) (map[string]types.ConfigObjConfig, error) { configs := make(map[string]types.ConfigObjConfig) if err := Transform(source, &configs); err != nil { return configs, err } for name, config := range configs { - obj, err := loadFileObjectConfig(name, "config", types.FileObjectConfig(config), details) + obj, err := loadFileObjectConfig(name, "config", types.FileObjectConfig(config), details, resolvePaths) if err != nil { return nil, err } @@ -735,7 +727,7 @@ func LoadConfigObjs(source map[string]interface{}, details types.ConfigDetails) return configs, nil } -func loadFileObjectConfig(name string, objType string, obj types.FileObjectConfig, details types.ConfigDetails) (types.FileObjectConfig, error) { +func loadFileObjectConfig(name string, objType string, obj types.FileObjectConfig, details types.ConfigDetails, resolvePaths bool) (types.FileObjectConfig, error) { // if "external: true" switch { case obj.External.External: @@ -758,7 +750,9 @@ func loadFileObjectConfig(name string, objType string, obj types.FileObjectConfi return obj, errors.Errorf("%[1]s %[2]s: %[1]s.driver and %[1]s.file conflict; only use %[1]s.driver", objType, name) } default: - obj.File = absPath(details.WorkingDir, obj.File) + if resolvePaths { + obj.File = absPath(details.WorkingDir, obj.File) + } } return obj, nil @@ -1018,10 +1012,13 @@ var transformSize TransformerFunc = func(value interface{}) (interface{}, error) switch value := value.(type) { case int: return int64(value), nil + case int64, types.UnitBytes: + return value, nil case string: return units.RAMInBytes(value) + default: + return value, errors.Errorf("invalid type for size %T", value) } - panic(errors.Errorf("invalid type for size %T", value)) } var transformStringToDuration TransformerFunc = func(value interface{}) (interface{}, error) { diff --git a/vendor/github.com/compose-spec/compose-go/loader/merge.go b/vendor/github.com/compose-spec/compose-go/loader/merge.go index 374384179fc..0c7a6dd56ce 100644 --- a/vendor/github.com/compose-spec/compose-go/loader/merge.go +++ b/vendor/github.com/compose-spec/compose-go/loader/merge.go @@ -33,6 +33,7 @@ var serviceSpecials = &specials{ m: map[reflect.Type]func(dst, src reflect.Value) error{ reflect.TypeOf(&types.LoggingConfig{}): safelyMerge(mergeLoggingConfig), reflect.TypeOf(&types.UlimitsConfig{}): safelyMerge(mergeUlimitsConfig), + reflect.TypeOf([]types.ServiceVolumeConfig{}): mergeSlice(toServiceVolumeConfigsMap, toServiceVolumeConfigsSlice), reflect.TypeOf([]types.ServicePortConfig{}): mergeSlice(toServicePortConfigsMap, toServicePortConfigsSlice), reflect.TypeOf([]types.ServiceSecretConfig{}): mergeSlice(toServiceSecretConfigsMap, toServiceSecretConfigsSlice), reflect.TypeOf([]types.ServiceConfigObjConfig{}): mergeSlice(toServiceConfigObjConfigsMap, toSServiceConfigObjConfigsSlice), @@ -86,13 +87,11 @@ func mergeServices(base, override []types.ServiceConfig) ([]types.ServiceConfig, for name, overrideService := range overrideServices { overrideService := overrideService if baseService, ok := baseServices[name]; ok { - if err := mergo.Merge(&baseService, &overrideService, mergo.WithAppendSlice, mergo.WithOverride, mergo.WithTransformers(serviceSpecials)); err != nil { - return base, errors.Wrapf(err, "cannot merge service %s", name) + merged, err := _merge(&baseService, &overrideService) + if err != nil { + return nil, errors.Wrapf(err, "cannot merge service %s", name) } - if len(overrideService.Command) > 0 { - baseService.Command = overrideService.Command - } - baseServices[name] = baseService + baseServices[name] = *merged continue } baseServices[name] = overrideService @@ -105,6 +104,19 @@ func mergeServices(base, override []types.ServiceConfig) ([]types.ServiceConfig, return services, nil } +func _merge(baseService *types.ServiceConfig, overrideService *types.ServiceConfig) (*types.ServiceConfig, error) { + if err := mergo.Merge(baseService, overrideService, mergo.WithAppendSlice, mergo.WithOverride, mergo.WithTransformers(serviceSpecials)); err != nil { + return nil, err + } + if overrideService.Command != nil { + baseService.Command = overrideService.Command + } + if overrideService.Entrypoint != nil { + baseService.Entrypoint = overrideService.Entrypoint + } + return baseService, nil +} + func toServiceSecretConfigsMap(s interface{}) (map[interface{}]interface{}, error) { secrets, ok := s.([]types.ServiceSecretConfig) if !ok { @@ -135,8 +147,33 @@ func toServicePortConfigsMap(s interface{}) (map[interface{}]interface{}, error) return nil, errors.Errorf("not a servicePortConfig slice: %v", s) } m := map[interface{}]interface{}{} + type port struct { + target uint32 + published uint32 + ip string + protocol string + } + for _, p := range ports { - m[p.Published] = p + mergeKey := port{ + target: p.Target, + published: p.Published, + ip: p.HostIP, + protocol: p.Protocol, + } + m[mergeKey] = p + } + return m, nil +} + +func toServiceVolumeConfigsMap(s interface{}) (map[interface{}]interface{}, error) { + volumes, ok := s.([]types.ServiceVolumeConfig) + if !ok { + return nil, errors.Errorf("not a ServiceVolumeConfig slice: %v", s) + } + m := map[interface{}]interface{}{} + for _, v := range volumes { + m[v.Target] = v } return m, nil } @@ -166,7 +203,28 @@ func toServicePortConfigsSlice(dst reflect.Value, m map[interface{}]interface{}) for _, v := range m { s = append(s, v.(types.ServicePortConfig)) } - sort.Slice(s, func(i, j int) bool { return s[i].Published < s[j].Published }) + sort.Slice(s, func(i, j int) bool { + if s[i].Target != s[j].Target { + return s[i].Target < s[j].Target + } + if s[i].Published != s[j].Published { + return s[i].Published < s[j].Published + } + if s[i].HostIP != s[j].HostIP { + return s[i].HostIP < s[j].HostIP + } + return s[i].Protocol < s[j].Protocol + }) + dst.Set(reflect.ValueOf(s)) + return nil +} + +func toServiceVolumeConfigsSlice(dst reflect.Value, m map[interface{}]interface{}) error { + s := []types.ServiceVolumeConfig{} + for _, v := range m { + s = append(s, v.(types.ServiceVolumeConfig)) + } + sort.Slice(s, func(i, j int) bool { return s[i].Target < s[j].Target }) dst.Set(reflect.ValueOf(s)) return nil } diff --git a/vendor/github.com/compose-spec/compose-go/loader/normalize.go b/vendor/github.com/compose-spec/compose-go/loader/normalize.go index fd527b7e657..23851ae3b98 100644 --- a/vendor/github.com/compose-spec/compose-go/loader/normalize.go +++ b/vendor/github.com/compose-spec/compose-go/loader/normalize.go @@ -28,7 +28,7 @@ import ( ) // normalize compose project by moving deprecated attributes to their canonical position and injecting implicit defaults -func normalize(project *types.Project) error { +func normalize(project *types.Project, resolvePaths bool) error { absWorkingDir, err := filepath.Abs(project.WorkingDir) if err != nil { return err @@ -41,6 +41,10 @@ func normalize(project *types.Project) error { } project.ComposeFiles = absComposeFiles + if project.Networks == nil { + project.Networks = make(map[string]types.NetworkConfig) + } + // If not declared explicitly, Compose model involves an implicit "default" network if _, ok := project.Networks["default"]; !ok { project.Networks["default"] = types.NetworkConfig{} @@ -72,8 +76,9 @@ func normalize(project *types.Project) error { } localContext := absPath(project.WorkingDir, s.Build.Context) if _, err := os.Stat(localContext); err == nil { - s.Build.Context = localContext - s.Build.Dockerfile = absPath(localContext, s.Build.Dockerfile) + if resolvePaths { + s.Build.Context = localContext + } } else { // might be a remote http/git context. Unfortunately supported "remote" syntax is highly ambiguous // in moby/moby and not defined by compose-spec, so let's assume runtime will check @@ -82,17 +87,22 @@ func normalize(project *types.Project) error { } s.Environment = s.Environment.Resolve(fn) - err := relocateLogDriver(s) + err := relocateLogDriver(&s) + if err != nil { + return err + } + + err = relocateLogOpt(&s) if err != nil { return err } - err = relocateLogOpt(s) + err = relocateDockerfile(&s) if err != nil { return err } - err = relocateDockerfile(s) + err = relocateScale(&s) if err != nil { return err } @@ -105,6 +115,21 @@ func normalize(project *types.Project) error { return nil } +func relocateScale(s *types.ServiceConfig) error { + scale := uint64(s.Scale) + if scale != 1 { + logrus.Warn("`scale` is deprecated. Use the `deploy.replicas` element") + if s.Deploy == nil { + s.Deploy = &types.DeployConfig{} + } + if s.Deploy.Replicas != nil && *s.Deploy.Replicas != scale { + return errors.Wrap(errdefs.ErrInvalid, "can't use both 'scale' (deprecated) and 'deploy.replicas'") + } + s.Deploy.Replicas = &scale + } + return nil +} + func absComposeFiles(composeFiles []string) ([]string, error) { absComposeFiles := make([]string, len(composeFiles)) for i, composeFile := range composeFiles { @@ -191,7 +216,7 @@ func relocateExternalName(project *types.Project) error { return nil } -func relocateLogOpt(s types.ServiceConfig) error { +func relocateLogOpt(s *types.ServiceConfig) error { if len(s.LogOpt) != 0 { logrus.Warn("`log_opts` is deprecated. Use the `logging` element") if s.Logging == nil { @@ -208,7 +233,7 @@ func relocateLogOpt(s types.ServiceConfig) error { return nil } -func relocateLogDriver(s types.ServiceConfig) error { +func relocateLogDriver(s *types.ServiceConfig) error { if s.LogDriver != "" { logrus.Warn("`log_driver` is deprecated. Use the `logging` element") if s.Logging == nil { @@ -223,7 +248,7 @@ func relocateLogDriver(s types.ServiceConfig) error { return nil } -func relocateDockerfile(s types.ServiceConfig) error { +func relocateDockerfile(s *types.ServiceConfig) error { if s.Dockerfile != "" { logrus.Warn("`dockerfile` is deprecated. Use the `build` element") if s.Build == nil { diff --git a/vendor/github.com/compose-spec/compose-go/loader/validate.go b/vendor/github.com/compose-spec/compose-go/loader/validate.go index b853d17b0db..4493c051da7 100644 --- a/vendor/github.com/compose-spec/compose-go/loader/validate.go +++ b/vendor/github.com/compose-spec/compose-go/loader/validate.go @@ -38,20 +38,13 @@ func checkConsistency(project *types.Project) error { } } - if strings.HasPrefix(s.NetworkMode, types.NetworkModeServicePrefix) { - serviceName := s.NetworkMode[len(types.NetworkModeServicePrefix):] + if strings.HasPrefix(s.NetworkMode, types.ServicePrefix) { + serviceName := s.NetworkMode[len(types.ServicePrefix):] if _, err := project.GetServices(serviceName); err != nil { return fmt.Errorf("service %q not found for network_mode 'service:%s'", serviceName, serviceName) } } - if strings.HasPrefix(s.NetworkMode, types.NetworkModeContainerPrefix) { - containerName := s.NetworkMode[len(types.NetworkModeContainerPrefix):] - if _, err := project.GetByContainerName(containerName); err != nil { - return fmt.Errorf("service with container_name %q not found for network_mode 'container:%s'", containerName, containerName) - } - } - for _, volume := range s.Volumes { switch volume.Type { case types.VolumeTypeVolume: diff --git a/vendor/github.com/compose-spec/compose-go/schema/compose-spec.json b/vendor/github.com/compose-spec/compose-go/schema/compose-spec.json index cefe476c54d..91bdb34269c 100644 --- a/vendor/github.com/compose-spec/compose-go/schema/compose-spec.json +++ b/vendor/github.com/compose-spec/compose-go/schema/compose-spec.json @@ -331,7 +331,7 @@ "privileged": {"type": "boolean"}, "profiles": {"$ref": "#/definitions/list_of_strings"}, "pull_policy": {"type": "string", "enum": [ - "always", "never", "if_not_present", "build" + "always", "never", "if_not_present", "build", "missing" ]}, "read_only": {"type": "boolean"}, "restart": {"type": "string"}, @@ -367,6 +367,7 @@ "stdin_open": {"type": "boolean"}, "stop_grace_period": {"type": "string", "format": "duration"}, "stop_signal": {"type": "string"}, + "storage_opt": {"type": "object"}, "tmpfs": {"$ref": "#/definitions/string_or_list"}, "tty": {"type": "boolean"}, "ulimits": { @@ -426,8 +427,10 @@ "type": "object", "properties": { "size": { - "type": "integer", - "minimum": 0 + "oneOf": [ + {"type": "integer", "minimum": 0}, + {"type": "string"} + ] } }, "additionalProperties": false, @@ -599,12 +602,12 @@ "items": { "type": "object", "properties": { - "capabilities": {"$ref": "#/definitions/list_of_strings"}, - "count": {"type": ["string", "integer"]}, - "device_ids": {"$ref": "#/definitions/list_of_strings"}, - "driver":{"type": "string"}, - "options":{"$ref": "#/definitions/list_or_dict"} - }, + "capabilities": {"$ref": "#/definitions/list_of_strings"}, + "count": {"type": ["string", "integer"]}, + "device_ids": {"$ref": "#/definitions/list_of_strings"}, + "driver":{"type": "string"}, + "options":{"$ref": "#/definitions/list_or_dict"} + }, "additionalProperties": false, "patternProperties": {"^x-": {}} } @@ -769,7 +772,7 @@ "type": "object", "patternProperties": { ".+": { - "type": ["string", "number", "null"] + "type": ["string", "number", "boolean", "null"] } }, "additionalProperties": false @@ -810,4 +813,4 @@ } } } -} \ No newline at end of file +} diff --git a/vendor/github.com/compose-spec/compose-go/template/template.go b/vendor/github.com/compose-spec/compose-go/template/template.go index d5ad0ae05fe..26e9b36a38a 100644 --- a/vendor/github.com/compose-spec/compose-go/template/template.go +++ b/vendor/github.com/compose-spec/compose-go/template/template.go @@ -25,11 +25,12 @@ import ( ) var delimiter = "\\$" -var substitution = "[_a-z][_a-z0-9]*(?::?[-?][^}]*)?" +var substitutionNamed = "[_a-z][_a-z0-9]*" +var substitutionBraced = "[_a-z][_a-z0-9]*(?::?[-?][^}]*)?" var patternString = fmt.Sprintf( "%s(?i:(?P%s)|(?P%s)|{(?P%s)}|(?P))", - delimiter, delimiter, substitution, substitution, + delimiter, delimiter, substitutionNamed, substitutionBraced, ) var defaultPattern = regexp.MustCompile(patternString) @@ -74,9 +75,11 @@ func SubstituteWith(template string, mapping Mapping, pattern *regexp.Regexp, su return escaped } + braced := false substitution := groups["named"] if substitution == "" { substitution = groups["braced"] + braced = true } if substitution == "" { @@ -84,19 +87,21 @@ func SubstituteWith(template string, mapping Mapping, pattern *regexp.Regexp, su return "" } - for _, f := range subsFuncs { - var ( - value string - applied bool - ) - value, applied, err = f(substitution, mapping) - if err != nil { - return "" - } - if !applied { - continue + if braced { + for _, f := range subsFuncs { + var ( + value string + applied bool + ) + value, applied, err = f(substitution, mapping) + if err != nil { + return "" + } + if !applied { + continue + } + return value } - return value } value, ok := mapping(substitution) diff --git a/vendor/github.com/compose-spec/compose-go/types/project.go b/vendor/github.com/compose-spec/compose-go/types/project.go index 3a65eefc779..155fad0b3e1 100644 --- a/vendor/github.com/compose-spec/compose-go/types/project.go +++ b/vendor/github.com/compose-spec/compose-go/types/project.go @@ -94,24 +94,6 @@ func (p Project) ConfigNames() []string { return names } -func (p Project) GetByContainerName(names ...string) (Services, error) { - if len(names) == 0 { - return p.Services, nil - } - services := Services{} -outLoop: - for _, name := range names { - for _, s := range p.Services { - if name == s.ContainerName { - services = append(services, s) - continue outLoop - } - } - return nil, fmt.Errorf("service with container_name %q could not be found", name) - } - return services, nil -} - // GetServices retrieve services by names, or return all services if no name specified func (p Project) GetServices(names ...string) (Services, error) { if len(names) == 0 { @@ -228,6 +210,11 @@ func (s Services) GetProfiles() []string { // ApplyProfiles disables service which don't match selected profiles func (p *Project) ApplyProfiles(profiles []string) { + for _, p := range profiles { + if p == "*" { + return + } + } var enabled, disabled Services for _, service := range p.Services { if service.HasProfile(profiles) { diff --git a/vendor/github.com/compose-spec/compose-go/types/types.go b/vendor/github.com/compose-spec/compose-go/types/types.go index 608b3a5a1b0..4d68f868f2d 100644 --- a/vendor/github.com/compose-spec/compose-go/types/types.go +++ b/vendor/github.com/compose-spec/compose-go/types/types.go @@ -89,7 +89,7 @@ type ServiceConfig struct { Profiles []string `mapstructure:"profiles" yaml:"profiles,omitempty" json:"profiles,omitempty"` Build *BuildConfig `yaml:",omitempty" json:"build,omitempty"` - BlkioConfig *BlkioConfig `yaml:",omitempty" json:"blkio_config,omitempty"` + BlkioConfig *BlkioConfig `mapstructure:"blkio_config" yaml:",omitempty" json:"blkio_config,omitempty"` CapAdd []string `mapstructure:"cap_add" yaml:"cap_add,omitempty" json:"cap_add,omitempty"` CapDrop []string `mapstructure:"cap_drop" yaml:"cap_drop,omitempty" json:"cap_drop,omitempty"` CgroupParent string `mapstructure:"cgroup_parent" yaml:"cgroup_parent,omitempty" json:"cgroup_parent,omitempty"` @@ -152,7 +152,7 @@ type ServiceConfig struct { ReadOnly bool `mapstructure:"read_only" yaml:"read_only,omitempty" json:"read_only,omitempty"` Restart string `yaml:",omitempty" json:"restart,omitempty"` Runtime string `yaml:",omitempty" json:"runtime,omitempty"` - Scale int `yaml:",omitempty" json:"scale,omitempty"` + Scale int `yaml:"-" json:"-"` Secrets []ServiceSecretConfig `yaml:",omitempty" json:"secrets,omitempty"` SecurityOpt []string `mapstructure:"security_opt" yaml:"security_opt,omitempty" json:"security_opt,omitempty"` ShmSize UnitBytes `mapstructure:"shm_size" yaml:"shm_size,omitempty" json:"shm_size,omitempty"` @@ -226,10 +226,17 @@ const ( ) const ( + // ServicePrefix is the prefix for references pointing to a service + ServicePrefix = "service:" + // ContainerPrefix is the prefix for references pointing to a container + ContainerPrefix = "container:" + // NetworkModeServicePrefix is the prefix for network_mode pointing to a service - NetworkModeServicePrefix = "service:" + // Deprecated prefer ServicePrefix + NetworkModeServicePrefix = ServicePrefix // NetworkModeContainerPrefix is the prefix for network_mode pointing to a container - NetworkModeContainerPrefix = "container:" + // Deprecated prefer ContainerPrefix + NetworkModeContainerPrefix = ContainerPrefix ) // GetDependencies retrieve all services this service depends on @@ -246,9 +253,21 @@ func (s ServiceConfig) GetDependencies() []string { dependencies.append(link) } } - if strings.HasPrefix(s.NetworkMode, NetworkModeServicePrefix) { - dependencies.append(s.NetworkMode[len(NetworkModeServicePrefix):]) + if strings.HasPrefix(s.NetworkMode, ServicePrefix) { + dependencies.append(s.NetworkMode[len(ServicePrefix):]) + } + if strings.HasPrefix(s.Ipc, ServicePrefix) { + dependencies.append(s.Ipc[len(ServicePrefix):]) } + if strings.HasPrefix(s.Pid, ServicePrefix) { + dependencies.append(s.Pid[len(ServicePrefix):]) + } + for _, vol := range s.VolumesFrom { + if !strings.HasPrefix(s.Pid, ContainerPrefix) { + dependencies.append(vol) + } + } + return dependencies.toSlice() } @@ -352,7 +371,7 @@ func (e MappingWithEquals) OverrideBy(other MappingWithEquals) MappingWithEquals // Resolve update a MappingWithEquals for keys without value (`key`, but not `key=`) func (e MappingWithEquals) Resolve(lookupFn func(string) (string, bool)) MappingWithEquals { for k, v := range e { - if v == nil || *v == "" { + if v == nil { if value, ok := lookupFn(k); ok { e[k] = &value } @@ -558,7 +577,7 @@ type ServiceNetworkConfig struct { // ServicePortConfig is the port configuration for a service type ServicePortConfig struct { Mode string `yaml:",omitempty" json:"mode,omitempty"` - HostIP string `yaml:"host_ip,omitempty" json:"host_ip,omitempty"` + HostIP string `mapstructure:"host_ip" yaml:"host_ip,omitempty" json:"host_ip,omitempty"` Target uint32 `yaml:",omitempty" json:"target,omitempty"` Published uint32 `yaml:",omitempty" json:"published,omitempty"` Protocol string `yaml:",omitempty" json:"protocol,omitempty"` @@ -671,7 +690,7 @@ type ServiceVolumeVolume struct { // ServiceVolumeTmpfs are options for a service volume of type tmpfs type ServiceVolumeTmpfs struct { - Size int64 `yaml:",omitempty" json:"size,omitempty"` + Size UnitBytes `yaml:",omitempty" json:"size,omitempty"` Extensions map[string]interface{} `yaml:",inline" json:"-"` } @@ -729,6 +748,7 @@ type NetworkConfig struct { Internal bool `yaml:",omitempty" json:"internal,omitempty"` Attachable bool `yaml:",omitempty" json:"attachable,omitempty"` Labels Labels `yaml:",omitempty" json:"labels,omitempty"` + EnableIPv6 bool `mapstructure:"enable_ipv6" yaml:"enable_ipv6,omitempty" json:"enable_ipv6,omitempty"` Extensions map[string]interface{} `yaml:",inline" json:"-"` } diff --git a/vendor/github.com/joho/godotenv/.gitignore b/vendor/github.com/compose-spec/godotenv/.gitignore similarity index 100% rename from vendor/github.com/joho/godotenv/.gitignore rename to vendor/github.com/compose-spec/godotenv/.gitignore diff --git a/vendor/github.com/compose-spec/godotenv/Earthfile b/vendor/github.com/compose-spec/godotenv/Earthfile new file mode 100644 index 00000000000..8bdfcea0870 --- /dev/null +++ b/vendor/github.com/compose-spec/godotenv/Earthfile @@ -0,0 +1,27 @@ +ARG GOLANG_VERSION=1.17.1 +ARG ALPINE_VERSION=3.14 + +FROM golang:${GOLANG_VERSION}-alpine${ALPINE_VERSION} +WORKDIR /code + +code: + FROM +base + COPY . . + +golangci: + ARG GOLANGCI_VERSION=v1.40.1 + FROM golangci/golangci-lint:${GOLANGCI_VERSION}-alpine + SAVE ARTIFACT /usr/bin/golangci-lint + +lint: + FROM +code + COPY +golangci/golangci-lint /usr/bin/golangci-lint + RUN golangci-lint run --timeout 5m ./... + +test: + FROM +code + RUN go test ./... + +all: + BUILD +lint + BUILD +test diff --git a/vendor/github.com/joho/godotenv/LICENCE b/vendor/github.com/compose-spec/godotenv/LICENCE similarity index 100% rename from vendor/github.com/joho/godotenv/LICENCE rename to vendor/github.com/compose-spec/godotenv/LICENCE diff --git a/vendor/github.com/compose-spec/godotenv/README.md b/vendor/github.com/compose-spec/godotenv/README.md new file mode 100644 index 00000000000..3420cd9c2a9 --- /dev/null +++ b/vendor/github.com/compose-spec/godotenv/README.md @@ -0,0 +1,18 @@ +# GoDotEnv + +A Go (golang) port of the Ruby dotenv project (which loads env vars from a .env file) + +From the original Library: + +> Storing configuration in the environment is one of the tenets of a twelve-factor app. Anything that is likely to change between deployment environments–such as resource handles for databases or credentials for external services–should be extracted from the code into environment variables. +> +> But it is not always practical to set environment variables on development machines or continuous integration servers where multiple projects are run. Dotenv load variables from a .env file into ENV when the environment is bootstrapped. + +This is a fork of [joho/godotenv](https://github.com/joho/godotenv) focussing on `.env` file support by the compose specification + + + +To run linter and tests, please install [Earthly](https://earthly.dev/get-earthly) and run: +```sh +earthly +all +``` diff --git a/vendor/github.com/compose-spec/godotenv/go.mod b/vendor/github.com/compose-spec/godotenv/go.mod new file mode 100644 index 00000000000..72e37f6e775 --- /dev/null +++ b/vendor/github.com/compose-spec/godotenv/go.mod @@ -0,0 +1,3 @@ +module github.com/compose-spec/godotenv + +go 1.16 diff --git a/vendor/github.com/compose-spec/godotenv/go.sum b/vendor/github.com/compose-spec/godotenv/go.sum new file mode 100644 index 00000000000..e69de29bb2d diff --git a/vendor/github.com/joho/godotenv/godotenv.go b/vendor/github.com/compose-spec/godotenv/godotenv.go similarity index 62% rename from vendor/github.com/joho/godotenv/godotenv.go rename to vendor/github.com/compose-spec/godotenv/godotenv.go index 29b436c77c0..7c44ac53538 100644 --- a/vendor/github.com/joho/godotenv/godotenv.go +++ b/vendor/github.com/compose-spec/godotenv/godotenv.go @@ -14,19 +14,42 @@ package godotenv import ( - "bufio" "errors" "fmt" "io" + "io/ioutil" "os" "os/exec" "regexp" "sort" + "strconv" "strings" ) const doubleQuoteSpecialChars = "\\\n\r\"!$`" +// LookupFn represents a lookup function to resolve variables from +type LookupFn func(string) (string, bool) + +var noLookupFn = func(s string) (string, bool) { + return "", false +} + +// Parse reads an env file from io.Reader, returning a map of keys and values. +func Parse(r io.Reader) (map[string]string, error) { + return ParseWithLookup(r, nil) +} + +// ParseWithLookup reads an env file from io.Reader, returning a map of keys and values. +func ParseWithLookup(r io.Reader, lookupFn LookupFn) (map[string]string, error) { + data, err := ioutil.ReadAll(r) + if err != nil { + return nil, err + } + + return UnmarshalBytesWithLookup(data, lookupFn) +} + // Load will read your env file(s) and load them into ENV for this process. // // Call this function as close as possible to the start of your program (ideally in main) @@ -39,15 +62,7 @@ const doubleQuoteSpecialChars = "\\\n\r\"!$`" // // It's important to note that it WILL NOT OVERRIDE an env variable that already exists - consider the .env file to set dev vars or sensible defaults func Load(filenames ...string) (err error) { - filenames = filenamesOrDefault(filenames) - - for _, filename := range filenames { - err = loadFile(filename, false) - if err != nil { - return // return early on a spazout - } - } - return + return load(false, filenames...) } // Overload will read your env file(s) and load them into ENV for this process. @@ -62,10 +77,14 @@ func Load(filenames ...string) (err error) { // // It's important to note this WILL OVERRIDE an env variable that already exists - consider the .env file to forcefilly set all vars. func Overload(filenames ...string) (err error) { + return load(true, filenames...) +} + +func load(overload bool, filenames ...string) (err error) { filenames = filenamesOrDefault(filenames) for _, filename := range filenames { - err = loadFile(filename, true) + err = loadFile(filename, overload) if err != nil { return // return early on a spazout } @@ -73,14 +92,14 @@ func Overload(filenames ...string) (err error) { return } -// Read all env (with same file loading semantics as Load) but return values as +// ReadWithLookup gets all env vars from the files and/or lookup function and return values as // a map rather than automatically writing values into env -func Read(filenames ...string) (envMap map[string]string, err error) { +func ReadWithLookup(lookupFn LookupFn, filenames ...string) (envMap map[string]string, err error) { filenames = filenamesOrDefault(filenames) envMap = make(map[string]string) for _, filename := range filenames { - individualEnvMap, individualErr := readFile(filename) + individualEnvMap, individualErr := readFile(filename, lookupFn) if individualErr != nil { err = individualErr @@ -95,37 +114,27 @@ func Read(filenames ...string) (envMap map[string]string, err error) { return } -// Parse reads an env file from io.Reader, returning a map of keys and values. -func Parse(r io.Reader) (envMap map[string]string, err error) { - envMap = make(map[string]string) - - var lines []string - scanner := bufio.NewScanner(r) - for scanner.Scan() { - lines = append(lines, scanner.Text()) - } - - if err = scanner.Err(); err != nil { - return - } +// Read all env (with same file loading semantics as Load) but return values as +// a map rather than automatically writing values into env +func Read(filenames ...string) (envMap map[string]string, err error) { + return ReadWithLookup(nil, filenames...) +} - for _, fullLine := range lines { - if !isIgnoredLine(fullLine) { - var key, value string - key, value, err = parseLine(fullLine, envMap) +// Unmarshal reads an env file from a string, returning a map of keys and values. +func Unmarshal(str string) (envMap map[string]string, err error) { + return UnmarshalBytes([]byte(str)) +} - if err != nil { - return - } - envMap[key] = value - } - } - return +// UnmarshalBytes parses env file from byte slice of chars, returning a map of keys and values. +func UnmarshalBytes(src []byte) (map[string]string, error) { + return UnmarshalBytesWithLookup(src, nil) } -//Unmarshal reads an env file from a string, returning a map of keys and values. -func Unmarshal(str string) (envMap map[string]string, err error) { - return Parse(strings.NewReader(str)) +// UnmarshalBytesWithLookup parses env file from byte slice of chars, returning a map of keys and values. +func UnmarshalBytesWithLookup(src []byte, lookupFn LookupFn) (map[string]string, error) { + out := make(map[string]string) + err := parseBytes(src, out, lookupFn) + return out, err } // Exec loads env vars from the specified filenames (empty map falls back to default) @@ -136,7 +145,9 @@ func Unmarshal(str string) (envMap map[string]string, err error) { // If you want more fine grained control over your command it's recommended // that you use `Load()` or `Read()` and the `os/exec` package yourself. func Exec(filenames []string, cmd string, cmdArgs []string) error { - Load(filenames...) + if err := Load(filenames...); err != nil { + return err + } command := exec.Command(cmd, cmdArgs...) command.Stdin = os.Stdin @@ -147,16 +158,20 @@ func Exec(filenames []string, cmd string, cmdArgs []string) error { // Write serializes the given environment and writes it to a file func Write(envMap map[string]string, filename string) error { - content, error := Marshal(envMap) - if error != nil { - return error + content, err := Marshal(envMap) + if err != nil { + return err + } + file, err := os.Create(filename) + if err != nil { + return err } - file, error := os.Create(filename) - if error != nil { - return error + defer file.Close() + _, err = file.WriteString(content + "\n") + if err != nil { + return err } - _, err := file.WriteString(content) - return err + return file.Sync() } // Marshal outputs the given environment as a dotenv-formatted environment file. @@ -164,7 +179,11 @@ func Write(envMap map[string]string, filename string) error { func Marshal(envMap map[string]string) (string, error) { lines := make([]string, 0, len(envMap)) for k, v := range envMap { - lines = append(lines, fmt.Sprintf(`%s="%s"`, k, doubleQuoteEscape(v))) + if d, err := strconv.Atoi(v); err == nil { + lines = append(lines, fmt.Sprintf(`%s=%d`, k, d)) + } else { + lines = append(lines, fmt.Sprintf(`%s="%s"`, k, doubleQuoteEscape(v))) + } } sort.Strings(lines) return strings.Join(lines, "\n"), nil @@ -178,7 +197,7 @@ func filenamesOrDefault(filenames []string) []string { } func loadFile(filename string, overload bool) error { - envMap, err := readFile(filename) + envMap, err := readFile(filename, nil) if err != nil { return err } @@ -192,24 +211,29 @@ func loadFile(filename string, overload bool) error { for key, value := range envMap { if !currentEnv[key] || overload { - os.Setenv(key, value) + _ = os.Setenv(key, value) } } return nil } -func readFile(filename string) (envMap map[string]string, err error) { +func readFile(filename string, lookupFn LookupFn) (envMap map[string]string, err error) { file, err := os.Open(filename) if err != nil { return } defer file.Close() - return Parse(file) + return ParseWithLookup(file, lookupFn) } +var exportRegex = regexp.MustCompile(`^\s*(?:export\s+)?(.*?)\s*$`) + func parseLine(line string, envMap map[string]string) (key string, value string, err error) { + return parseLineWithLookup(line, envMap, nil) +} +func parseLineWithLookup(line string, envMap map[string]string, lookupFn LookupFn) (key string, value string, err error) { if len(line) == 0 { err = errors.New("zero length string") return @@ -250,31 +274,30 @@ func parseLine(line string, envMap map[string]string) (key string, value string, err = errors.New("Can't separate key from value") return } - - // Parse the key - key = splitString[0] - if strings.HasPrefix(key, "export") { - key = strings.TrimPrefix(key, "export") - } - key = strings.Trim(key, " ") + key = exportRegex.ReplaceAllString(splitString[0], "$1") // Parse the value - value = parseValue(splitString[1], envMap) + value = parseValue(splitString[1], envMap, lookupFn) return } -func parseValue(value string, envMap map[string]string) string { +var ( + singleQuotesRegex = regexp.MustCompile(`\A'(.*)'\z`) + doubleQuotesRegex = regexp.MustCompile(`\A"(.*)"\z`) + escapeRegex = regexp.MustCompile(`\\.`) + unescapeCharsRegex = regexp.MustCompile(`\\([^$])`) +) + +func parseValue(value string, envMap map[string]string, lookupFn LookupFn) string { // trim value = strings.Trim(value, " ") // check if we've got quoted values or possible escapes if len(value) > 1 { - rs := regexp.MustCompile(`\A'(.*)'\z`) - singleQuotes := rs.FindStringSubmatch(value) + singleQuotes := singleQuotesRegex.FindStringSubmatch(value) - rd := regexp.MustCompile(`\A"(.*)"\z`) - doubleQuotes := rd.FindStringSubmatch(value) + doubleQuotes := doubleQuotesRegex.FindStringSubmatch(value) if singleQuotes != nil || doubleQuotes != nil { // pull the quotes off the edges @@ -283,7 +306,6 @@ func parseValue(value string, envMap map[string]string) string { if doubleQuotes != nil { // expand newlines - escapeRegex := regexp.MustCompile(`\\.`) value = escapeRegex.ReplaceAllStringFunc(value, func(match string) string { c := strings.TrimPrefix(match, `\`) switch c { @@ -296,23 +318,22 @@ func parseValue(value string, envMap map[string]string) string { } }) // unescape characters - e := regexp.MustCompile(`\\([^$])`) - value = e.ReplaceAllString(value, "$1") + value = unescapeCharsRegex.ReplaceAllString(value, "$1") } if singleQuotes == nil { - value = expandVariables(value, envMap) + value = expandVariables(value, envMap, lookupFn) } } return value } -func expandVariables(v string, m map[string]string) string { - r := regexp.MustCompile(`(\\)?(\$)(\()?\{?([A-Z0-9_]+)?\}?`) +var expandVarRegex = regexp.MustCompile(`(\\)?(\$)(\()?\{?([A-Z0-9_]+)?\}?`) - return r.ReplaceAllStringFunc(v, func(s string) string { - submatch := r.FindStringSubmatch(s) +func expandVariables(v string, envMap map[string]string, lookupFn LookupFn) string { + return expandVarRegex.ReplaceAllStringFunc(v, func(s string) string { + submatch := expandVarRegex.FindStringSubmatch(s) if submatch == nil { return s @@ -320,17 +341,25 @@ func expandVariables(v string, m map[string]string) string { if submatch[1] == "\\" || submatch[2] == "(" { return submatch[0][1:] } else if submatch[4] != "" { - return m[submatch[4]] + //first check if we have defined this already earlier + if envMap[submatch[4]] != "" { + return envMap[submatch[4]] + } + if lookupFn == nil { + return "" + } + //if we have not defined it, check the lookup function provided + //by the user + s2, ok := lookupFn(submatch[4]) + if ok { + return s2 + } + return "" } return s }) } -func isIgnoredLine(line string) bool { - trimmedLine := strings.Trim(line, " \n\t") - return len(trimmedLine) == 0 || strings.HasPrefix(trimmedLine, "#") -} - func doubleQuoteEscape(line string) string { for _, c := range doubleQuoteSpecialChars { toReplace := "\\" + string(c) diff --git a/vendor/github.com/compose-spec/godotenv/parser.go b/vendor/github.com/compose-spec/godotenv/parser.go new file mode 100644 index 00000000000..dd8c4b56ea5 --- /dev/null +++ b/vendor/github.com/compose-spec/godotenv/parser.go @@ -0,0 +1,223 @@ +package godotenv + +import ( + "bytes" + "errors" + "fmt" + "strings" + "unicode" +) + +const ( + charComment = '#' + prefixSingleQuote = '\'' + prefixDoubleQuote = '"' + + exportPrefix = "export" +) + +func parseBytes(src []byte, out map[string]string, lookupFn LookupFn) error { + cutset := src + for { + cutset = getStatementStart(cutset) + if cutset == nil { + // reached end of file + break + } + + key, left, inherited, err := locateKeyName(cutset) + if err != nil { + return err + } + if strings.Contains(key, " ") { + return errors.New("key cannot contain a space") + } + + if inherited { + if lookupFn == nil { + lookupFn = noLookupFn + } + + value, ok := lookupFn(key) + if ok { + out[key] = value + cutset = left + continue + } + } + + value, left, err := extractVarValue(left, out, lookupFn) + if err != nil { + return err + } + + out[key] = value + cutset = left + } + + return nil +} + +// getStatementPosition returns position of statement begin. +// +// It skips any comment line or non-whitespace character. +func getStatementStart(src []byte) []byte { + pos := indexOfNonSpaceChar(src) + if pos == -1 { + return nil + } + + src = src[pos:] + if src[0] != charComment { + return src + } + + // skip comment section + pos = bytes.IndexFunc(src, isCharFunc('\n')) + if pos == -1 { + return nil + } + + return getStatementStart(src[pos:]) +} + +// locateKeyName locates and parses key name and returns rest of slice +func locateKeyName(src []byte) (key string, cutset []byte, inherited bool, err error) { + // trim "export" and space at beginning + src = bytes.TrimLeftFunc(bytes.TrimPrefix(src, []byte(exportPrefix)), isSpace) + + // locate key name end and validate it in single loop + offset := 0 +loop: + for i, char := range src { + rchar := rune(char) + if isSpace(rchar) { + continue + } + + switch char { + case '=', ':', '\n': + // library also supports yaml-style value declaration + key = string(src[0:i]) + offset = i + 1 + inherited = char == '\n' + break loop + case '_': + default: + // variable name should match [A-Za-z0-9_] + if unicode.IsLetter(rchar) || unicode.IsNumber(rchar) { + continue + } + + return "", nil, inherited, fmt.Errorf( + `unexpected character %q in variable name near %q`, + string(char), string(src)) + } + } + + if len(src) == 0 { + return "", nil, inherited, errors.New("zero length string") + } + + // trim whitespace + key = strings.TrimRightFunc(key, unicode.IsSpace) + cutset = bytes.TrimLeftFunc(src[offset:], isSpace) + return key, cutset, inherited, nil +} + +// 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) { + quote, hasPrefix := hasQuotePrefix(src) + if !hasPrefix { + // unquoted value - read until whitespace + end := bytes.IndexFunc(src, unicode.IsSpace) + if end == -1 { + return expandVariables(string(src), envMap, lookupFn), nil, nil + } + + return expandVariables(string(src[0:end]), envMap, lookupFn), src[end:], nil + } + + // lookup quoted string terminator + for i := 1; i < len(src); i++ { + if char := src[i]; char != quote { + continue + } + + // skip escaped quote symbol (\" or \', depends on quote) + if prevChar := src[i-1]; prevChar == '\\' { + continue + } + + // trim quotes + trimFunc := isCharFunc(rune(quote)) + value = string(bytes.TrimLeftFunc(bytes.TrimRightFunc(src[0:i], trimFunc), trimFunc)) + if quote == prefixDoubleQuote { + // unescape newlines for double quote (this is compat feature) + // and expand environment variables + value = expandVariables(expandEscapes(value), envMap, lookupFn) + } + + return value, src[i+1:], nil + } + + // return formatted error if quoted string is not terminated + valEndIndex := bytes.IndexFunc(src, isCharFunc('\n')) + if valEndIndex == -1 { + valEndIndex = len(src) + } + + return "", nil, fmt.Errorf("unterminated quoted value %s", src[:valEndIndex]) +} + +func expandEscapes(str string) string { + out := escapeRegex.ReplaceAllStringFunc(str, func(match string) string { + c := strings.TrimPrefix(match, `\`) + switch c { + case "n": + return "\n" + case "r": + return "\r" + default: + return match + } + }) + return unescapeCharsRegex.ReplaceAllString(out, "$1") +} + +func indexOfNonSpaceChar(src []byte) int { + return bytes.IndexFunc(src, func(r rune) bool { + return !unicode.IsSpace(r) + }) +} + +// hasQuotePrefix reports whether charset starts with single or double quote and returns quote character +func hasQuotePrefix(src []byte) (prefix byte, isQuored bool) { + if len(src) == 0 { + return 0, false + } + + switch prefix := src[0]; prefix { + case prefixDoubleQuote, prefixSingleQuote: + return prefix, true + default: + return 0, false + } +} + +func isCharFunc(char rune) func(rune) bool { + return func(v rune) bool { + return v == char + } +} + +// isSpace reports whether the rune is a space character but not line break character +// +// this differs from unicode.IsSpace, which also applies line break as space +func isSpace(r rune) bool { + switch r { + case '\t', '\v', '\f', '\r', ' ', 0x85, 0xA0: + return true + } + return false +} diff --git a/vendor/github.com/joho/godotenv/.travis.yml b/vendor/github.com/joho/godotenv/.travis.yml deleted file mode 100644 index f0db1adcdb1..00000000000 --- a/vendor/github.com/joho/godotenv/.travis.yml +++ /dev/null @@ -1,8 +0,0 @@ -language: go - -go: - - 1.x - -os: - - linux - - osx diff --git a/vendor/github.com/joho/godotenv/README.md b/vendor/github.com/joho/godotenv/README.md deleted file mode 100644 index 4e8fcf2e9cd..00000000000 --- a/vendor/github.com/joho/godotenv/README.md +++ /dev/null @@ -1,163 +0,0 @@ -# GoDotEnv [![Build Status](https://travis-ci.org/joho/godotenv.svg?branch=master)](https://travis-ci.org/joho/godotenv) [![Build status](https://ci.appveyor.com/api/projects/status/9v40vnfvvgde64u4?svg=true)](https://ci.appveyor.com/project/joho/godotenv) [![Go Report Card](https://goreportcard.com/badge/github.com/joho/godotenv)](https://goreportcard.com/report/github.com/joho/godotenv) - -A Go (golang) port of the Ruby dotenv project (which loads env vars from a .env file) - -From the original Library: - -> Storing configuration in the environment is one of the tenets of a twelve-factor app. Anything that is likely to change between deployment environments–such as resource handles for databases or credentials for external services–should be extracted from the code into environment variables. -> -> But it is not always practical to set environment variables on development machines or continuous integration servers where multiple projects are run. Dotenv load variables from a .env file into ENV when the environment is bootstrapped. - -It can be used as a library (for loading in env for your own daemons etc) or as a bin command. - -There is test coverage and CI for both linuxish and windows environments, but I make no guarantees about the bin version working on windows. - -## Installation - -As a library - -```shell -go get github.com/joho/godotenv -``` - -or if you want to use it as a bin command -```shell -go get github.com/joho/godotenv/cmd/godotenv -``` - -## Usage - -Add your application configuration to your `.env` file in the root of your project: - -```shell -S3_BUCKET=YOURS3BUCKET -SECRET_KEY=YOURSECRETKEYGOESHERE -``` - -Then in your Go app you can do something like - -```go -package main - -import ( - "github.com/joho/godotenv" - "log" - "os" -) - -func main() { - err := godotenv.Load() - if err != nil { - log.Fatal("Error loading .env file") - } - - s3Bucket := os.Getenv("S3_BUCKET") - secretKey := os.Getenv("SECRET_KEY") - - // now do something with s3 or whatever -} -``` - -If you're even lazier than that, you can just take advantage of the autoload package which will read in `.env` on import - -```go -import _ "github.com/joho/godotenv/autoload" -``` - -While `.env` in the project root is the default, you don't have to be constrained, both examples below are 100% legit - -```go -_ = godotenv.Load("somerandomfile") -_ = godotenv.Load("filenumberone.env", "filenumbertwo.env") -``` - -If you want to be really fancy with your env file you can do comments and exports (below is a valid env file) - -```shell -# I am a comment and that is OK -SOME_VAR=someval -FOO=BAR # comments at line end are OK too -export BAR=BAZ -``` - -Or finally you can do YAML(ish) style - -```yaml -FOO: bar -BAR: baz -``` - -as a final aside, if you don't want godotenv munging your env you can just get a map back instead - -```go -var myEnv map[string]string -myEnv, err := godotenv.Read() - -s3Bucket := myEnv["S3_BUCKET"] -``` - -... or from an `io.Reader` instead of a local file - -```go -reader := getRemoteFile() -myEnv, err := godotenv.Parse(reader) -``` - -... or from a `string` if you so desire - -```go -content := getRemoteFileContent() -myEnv, err := godotenv.Unmarshal(content) -``` - -### Command Mode - -Assuming you've installed the command as above and you've got `$GOPATH/bin` in your `$PATH` - -``` -godotenv -f /some/path/to/.env some_command with some args -``` - -If you don't specify `-f` it will fall back on the default of loading `.env` in `PWD` - -### Writing Env Files - -Godotenv can also write a map representing the environment to a correctly-formatted and escaped file - -```go -env, err := godotenv.Unmarshal("KEY=value") -err := godotenv.Write(env, "./.env") -``` - -... or to a string - -```go -env, err := godotenv.Unmarshal("KEY=value") -content, err := godotenv.Marshal(env) -``` - -## Contributing - -Contributions are most welcome! The parser itself is pretty stupidly naive and I wouldn't be surprised if it breaks with edge cases. - -*code changes without tests will not be accepted* - -1. Fork it -2. Create your feature branch (`git checkout -b my-new-feature`) -3. Commit your changes (`git commit -am 'Added some feature'`) -4. Push to the branch (`git push origin my-new-feature`) -5. Create new Pull Request - -## Releases - -Releases should follow [Semver](http://semver.org/) though the first couple of releases are `v1` and `v1.1`. - -Use [annotated tags for all releases](https://github.com/joho/godotenv/issues/30). Example `git tag -a v1.2.1` - -## CI - -Linux: [![Build Status](https://travis-ci.org/joho/godotenv.svg?branch=master)](https://travis-ci.org/joho/godotenv) Windows: [![Build status](https://ci.appveyor.com/api/projects/status/9v40vnfvvgde64u4)](https://ci.appveyor.com/project/joho/godotenv) - -## Who? - -The original library [dotenv](https://github.com/bkeepers/dotenv) was written by [Brandon Keepers](http://opensoul.org/), and this port was done by [John Barton](https://johnbarton.co/) based off the tests/fixtures in the original library. diff --git a/vendor/github.com/mitchellh/mapstructure/CHANGELOG.md b/vendor/github.com/mitchellh/mapstructure/CHANGELOG.md index 1955f2878c8..9fe803a5e90 100644 --- a/vendor/github.com/mitchellh/mapstructure/CHANGELOG.md +++ b/vendor/github.com/mitchellh/mapstructure/CHANGELOG.md @@ -1,6 +1,12 @@ -## unreleased +## 1.4.2 -* Fix regression where `*time.Time` value would be set to empty and not be sent +* Custom name matchers to support any sort of casing, formatting, etc. for + field names. [GH-250] +* Fix possible panic in ComposeDecodeHookFunc [GH-251] + +## 1.4.1 + +* Fix regression where `*time.Time` value would be set to empty and not be sent to decode hooks properly [GH-232] ## 1.4.0 diff --git a/vendor/github.com/mitchellh/mapstructure/decode_hooks.go b/vendor/github.com/mitchellh/mapstructure/decode_hooks.go index 92e6f76fff4..4d4bbc733ba 100644 --- a/vendor/github.com/mitchellh/mapstructure/decode_hooks.go +++ b/vendor/github.com/mitchellh/mapstructure/decode_hooks.go @@ -62,7 +62,8 @@ func DecodeHookExec( func ComposeDecodeHookFunc(fs ...DecodeHookFunc) DecodeHookFunc { return func(f reflect.Value, t reflect.Value) (interface{}, error) { var err error - var data interface{} + data := f.Interface() + newFrom := f for _, f1 := range fs { data, err = DecodeHookExec(f1, newFrom, t) diff --git a/vendor/github.com/mitchellh/mapstructure/mapstructure.go b/vendor/github.com/mitchellh/mapstructure/mapstructure.go index 3643901f55f..dcee0f2d634 100644 --- a/vendor/github.com/mitchellh/mapstructure/mapstructure.go +++ b/vendor/github.com/mitchellh/mapstructure/mapstructure.go @@ -192,7 +192,7 @@ type DecodeHookFuncType func(reflect.Type, reflect.Type, interface{}) (interface // source and target types. type DecodeHookFuncKind func(reflect.Kind, reflect.Kind, interface{}) (interface{}, error) -// DecodeHookFuncRaw is a DecodeHookFunc which has complete access to both the source and target +// DecodeHookFuncValue is a DecodeHookFunc which has complete access to both the source and target // values. type DecodeHookFuncValue func(from reflect.Value, to reflect.Value) (interface{}, error) @@ -258,6 +258,11 @@ type DecoderConfig struct { // The tag name that mapstructure reads for field names. This // defaults to "mapstructure" TagName string + + // MatchName is the function used to match the map key to the struct + // field name or tag. Defaults to `strings.EqualFold`. This can be used + // to implement case-sensitive tag values, support snake casing, etc. + MatchName func(mapKey, fieldName string) bool } // A Decoder takes a raw interface value and turns it into structured @@ -376,6 +381,10 @@ func NewDecoder(config *DecoderConfig) (*Decoder, error) { config.TagName = "mapstructure" } + if config.MatchName == nil { + config.MatchName = strings.EqualFold + } + result := &Decoder{ config: config, } @@ -1340,7 +1349,7 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e continue } - if strings.EqualFold(mK, fieldName) { + if d.config.MatchName(mK, fieldName) { rawMapKey = dataValKey rawMapVal = dataVal.MapIndex(dataValKey) break diff --git a/vendor/modules.txt b/vendor/modules.txt index 31e79da15d5..49954d2aa50 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -30,7 +30,7 @@ github.com/cenkalti/backoff/v4 github.com/cespare/xxhash/v2 # github.com/cloudflare/cfssl v0.0.0-20181213083726-b94e044bb51e ## explicit -# github.com/compose-spec/compose-go v0.0.0-20210729195839-de56f4f0cb3c +# github.com/compose-spec/compose-go v1.0.5 ## explicit github.com/compose-spec/compose-go/errdefs github.com/compose-spec/compose-go/interpolation @@ -38,6 +38,8 @@ github.com/compose-spec/compose-go/loader github.com/compose-spec/compose-go/schema github.com/compose-spec/compose-go/template github.com/compose-spec/compose-go/types +# github.com/compose-spec/godotenv v1.1.0 +github.com/compose-spec/godotenv # github.com/containerd/console v1.0.3 ## explicit github.com/containerd/console @@ -263,8 +265,6 @@ github.com/inconshreveable/mousetrap ## explicit # github.com/jinzhu/inflection v0.0.0-20180308033659-04140366298a ## explicit -# github.com/joho/godotenv v1.3.0 -github.com/joho/godotenv # github.com/json-iterator/go v1.1.11 github.com/json-iterator/go # github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 @@ -284,7 +284,7 @@ github.com/matttproud/golang_protobuf_extensions/pbutil github.com/miekg/pkcs11 # github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 github.com/mitchellh/go-wordwrap -# github.com/mitchellh/mapstructure v1.4.1 +# github.com/mitchellh/mapstructure v1.4.2 github.com/mitchellh/mapstructure # github.com/moby/buildkit v0.9.1-0.20211019185819-8778943ac3da ## explicit