mirror of
				https://gitea.com/Lydanne/buildx.git
				synced 2025-11-01 00:23:56 +08:00 
			
		
		
		
	update github.com/compose-spec/compose-go to v1.0.5
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
This commit is contained in:
		
							
								
								
									
										64
									
								
								vendor/github.com/compose-spec/compose-go/loader/interpolate.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										64
									
								
								vendor/github.com/compose-spec/compose-go/loader/interpolate.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -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("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("privileged"):                                        toBoolean, | ||||
| 	servicePath("read_only"):                                         toBoolean, | ||||
| 	servicePath("stdin_open"):                                        toBoolean, | ||||
| 	servicePath("tty"):                                               toBoolean, | ||||
| 	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) { | ||||
|   | ||||
							
								
								
									
										163
									
								
								vendor/github.com/compose-spec/compose-go/loader/loader.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										163
									
								
								vendor/github.com/compose-spec/compose-go/loader/loader.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -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) { | ||||
| 	yaml, err := ParseYAML(b) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	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 interp.Interpolate(yaml, *opts.Interpolate) | ||||
| 	} | ||||
|  | ||||
| 	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) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	b, err = yaml.Marshal(cfg) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	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 | ||||
| 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) | ||||
| 	} | ||||
| 	return nil | ||||
| 	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) { | ||||
|   | ||||
							
								
								
									
										74
									
								
								vendor/github.com/compose-spec/compose-go/loader/merge.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										74
									
								
								vendor/github.com/compose-spec/compose-go/loader/merge.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -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 | ||||
| } | ||||
|   | ||||
							
								
								
									
										43
									
								
								vendor/github.com/compose-spec/compose-go/loader/normalize.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										43
									
								
								vendor/github.com/compose-spec/compose-go/loader/normalize.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -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) | ||||
| 		err = relocateLogOpt(&s) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
|  | ||||
| 		err = relocateDockerfile(s) | ||||
| 		err = relocateDockerfile(&s) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
|  | ||||
| 		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 { | ||||
|   | ||||
							
								
								
									
										11
									
								
								vendor/github.com/compose-spec/compose-go/loader/validate.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										11
									
								
								vendor/github.com/compose-spec/compose-go/loader/validate.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -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: | ||||
|   | ||||
							
								
								
									
										25
									
								
								vendor/github.com/compose-spec/compose-go/schema/compose-spec.json
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										25
									
								
								vendor/github.com/compose-spec/compose-go/schema/compose-spec.json
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -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 @@ | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| } | ||||
|   | ||||
							
								
								
									
										33
									
								
								vendor/github.com/compose-spec/compose-go/template/template.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										33
									
								
								vendor/github.com/compose-spec/compose-go/template/template.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -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<escaped>%s)|(?P<named>%s)|{(?P<braced>%s)}|(?P<invalid>))", | ||||
| 	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 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 | ||||
| 			} | ||||
| 			if !applied { | ||||
| 				continue | ||||
| 			} | ||||
| 			return value | ||||
| 		} | ||||
|  | ||||
| 		value, ok := mapping(substitution) | ||||
|   | ||||
							
								
								
									
										23
									
								
								vendor/github.com/compose-spec/compose-go/types/project.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										23
									
								
								vendor/github.com/compose-spec/compose-go/types/project.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -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) { | ||||
|   | ||||
							
								
								
									
										38
									
								
								vendor/github.com/compose-spec/compose-go/types/types.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										38
									
								
								vendor/github.com/compose-spec/compose-go/types/types.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -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:"-"` | ||||
| } | ||||
|  | ||||
|   | ||||
							
								
								
									
										1
									
								
								vendor/github.com/compose-spec/godotenv/.gitignore
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								vendor/github.com/compose-spec/godotenv/.gitignore
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| .DS_Store | ||||
							
								
								
									
										27
									
								
								vendor/github.com/compose-spec/godotenv/Earthfile
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								vendor/github.com/compose-spec/godotenv/Earthfile
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -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 | ||||
							
								
								
									
										23
									
								
								vendor/github.com/compose-spec/godotenv/LICENCE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								vendor/github.com/compose-spec/godotenv/LICENCE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| Copyright (c) 2013 John Barton | ||||
|  | ||||
| MIT License | ||||
|  | ||||
| Permission is hereby granted, free of charge, to any person obtaining | ||||
| a copy of this software and associated documentation files (the | ||||
| "Software"), to deal in the Software without restriction, including | ||||
| without limitation the rights to use, copy, modify, merge, publish, | ||||
| distribute, sublicense, and/or sell copies of the Software, and to | ||||
| permit persons to whom the Software is furnished to do so, subject to | ||||
| the following conditions: | ||||
|  | ||||
| The above copyright notice and this permission notice shall be | ||||
| included in all copies or substantial portions of the Software. | ||||
|  | ||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||||
| EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | ||||
| MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||||
| NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE | ||||
| LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION | ||||
| OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | ||||
| WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||||
|  | ||||
							
								
								
									
										18
									
								
								vendor/github.com/compose-spec/godotenv/README.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								vendor/github.com/compose-spec/godotenv/README.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -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 | ||||
| ``` | ||||
							
								
								
									
										3
									
								
								vendor/github.com/compose-spec/godotenv/go.mod
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								vendor/github.com/compose-spec/godotenv/go.mod
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| module github.com/compose-spec/godotenv | ||||
|  | ||||
| go 1.16 | ||||
							
								
								
									
										0
									
								
								vendor/github.com/compose-spec/godotenv/go.sum
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								vendor/github.com/compose-spec/godotenv/go.sum
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
								
								
									
										375
									
								
								vendor/github.com/compose-spec/godotenv/godotenv.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										375
									
								
								vendor/github.com/compose-spec/godotenv/godotenv.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,375 @@ | ||||
| // Package godotenv is a go port of the ruby dotenv library (https://github.com/bkeepers/dotenv) | ||||
| // | ||||
| // Examples/readme can be found on the github page at https://github.com/joho/godotenv | ||||
| // | ||||
| // The TL;DR is that you make a .env file that looks something like | ||||
| // | ||||
| // 		SOME_ENV_VAR=somevalue | ||||
| // | ||||
| // and then in your go code you can call | ||||
| // | ||||
| // 		godotenv.Load() | ||||
| // | ||||
| // and all the env vars declared in .env will be available through os.Getenv("SOME_ENV_VAR") | ||||
| package godotenv | ||||
|  | ||||
| import ( | ||||
| 	"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) | ||||
| // | ||||
| // If you call Load without any args it will default to loading .env in the current path | ||||
| // | ||||
| // You can otherwise tell it which files to load (there can be more than one) like | ||||
| // | ||||
| //		godotenv.Load("fileone", "filetwo") | ||||
| // | ||||
| // 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) { | ||||
| 	return load(false, filenames...) | ||||
| } | ||||
|  | ||||
| // Overload 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) | ||||
| // | ||||
| // If you call Overload without any args it will default to loading .env in the current path | ||||
| // | ||||
| // You can otherwise tell it which files to load (there can be more than one) like | ||||
| // | ||||
| //		godotenv.Overload("fileone", "filetwo") | ||||
| // | ||||
| // 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, overload) | ||||
| 		if err != nil { | ||||
| 			return // return early on a spazout | ||||
| 		} | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // 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 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, lookupFn) | ||||
|  | ||||
| 		if individualErr != nil { | ||||
| 			err = individualErr | ||||
| 			return // return early on a spazout | ||||
| 		} | ||||
|  | ||||
| 		for key, value := range individualEnvMap { | ||||
| 			envMap[key] = value | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	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...) | ||||
| } | ||||
|  | ||||
| // 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)) | ||||
| } | ||||
|  | ||||
| // 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) | ||||
| } | ||||
|  | ||||
| // 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) | ||||
| // then executes the cmd specified. | ||||
| // | ||||
| // Simply hooks up os.Stdin/err/out to the command and calls Run() | ||||
| // | ||||
| // 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 { | ||||
| 	if err := Load(filenames...); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	command := exec.Command(cmd, cmdArgs...) | ||||
| 	command.Stdin = os.Stdin | ||||
| 	command.Stdout = os.Stdout | ||||
| 	command.Stderr = os.Stderr | ||||
| 	return command.Run() | ||||
| } | ||||
|  | ||||
| // Write serializes the given environment and writes it to a file | ||||
| func Write(envMap map[string]string, filename string) error { | ||||
| 	content, err := Marshal(envMap) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	file, err := os.Create(filename) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	defer file.Close() | ||||
| 	_, err = file.WriteString(content + "\n") | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return file.Sync() | ||||
| } | ||||
|  | ||||
| // Marshal outputs the given environment as a dotenv-formatted environment file. | ||||
| // Each line is in the format: KEY="VALUE" where VALUE is backslash-escaped. | ||||
| func Marshal(envMap map[string]string) (string, error) { | ||||
| 	lines := make([]string, 0, len(envMap)) | ||||
| 	for k, v := range envMap { | ||||
| 		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 | ||||
| } | ||||
|  | ||||
| func filenamesOrDefault(filenames []string) []string { | ||||
| 	if len(filenames) == 0 { | ||||
| 		return []string{".env"} | ||||
| 	} | ||||
| 	return filenames | ||||
| } | ||||
|  | ||||
| func loadFile(filename string, overload bool) error { | ||||
| 	envMap, err := readFile(filename, nil) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	currentEnv := map[string]bool{} | ||||
| 	rawEnv := os.Environ() | ||||
| 	for _, rawEnvLine := range rawEnv { | ||||
| 		key := strings.Split(rawEnvLine, "=")[0] | ||||
| 		currentEnv[key] = true | ||||
| 	} | ||||
|  | ||||
| 	for key, value := range envMap { | ||||
| 		if !currentEnv[key] || overload { | ||||
| 			_ = os.Setenv(key, value) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| 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 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 | ||||
| 	} | ||||
|  | ||||
| 	// ditch the comments (but keep quoted hashes) | ||||
| 	if strings.Contains(line, "#") { | ||||
| 		segmentsBetweenHashes := strings.Split(line, "#") | ||||
| 		quotesAreOpen := false | ||||
| 		var segmentsToKeep []string | ||||
| 		for _, segment := range segmentsBetweenHashes { | ||||
| 			if strings.Count(segment, "\"") == 1 || strings.Count(segment, "'") == 1 { | ||||
| 				if quotesAreOpen { | ||||
| 					quotesAreOpen = false | ||||
| 					segmentsToKeep = append(segmentsToKeep, segment) | ||||
| 				} else { | ||||
| 					quotesAreOpen = true | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			if len(segmentsToKeep) == 0 || quotesAreOpen { | ||||
| 				segmentsToKeep = append(segmentsToKeep, segment) | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		line = strings.Join(segmentsToKeep, "#") | ||||
| 	} | ||||
|  | ||||
| 	firstEquals := strings.Index(line, "=") | ||||
| 	firstColon := strings.Index(line, ":") | ||||
| 	splitString := strings.SplitN(line, "=", 2) | ||||
| 	if firstColon != -1 && (firstColon < firstEquals || firstEquals == -1) { | ||||
| 		//this is a yaml-style line | ||||
| 		splitString = strings.SplitN(line, ":", 2) | ||||
| 	} | ||||
|  | ||||
| 	if len(splitString) != 2 { | ||||
| 		err = errors.New("Can't separate key from value") | ||||
| 		return | ||||
| 	} | ||||
| 	key = exportRegex.ReplaceAllString(splitString[0], "$1") | ||||
|  | ||||
| 	// Parse the value | ||||
| 	value = parseValue(splitString[1], envMap, lookupFn) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| 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 { | ||||
| 		singleQuotes := singleQuotesRegex.FindStringSubmatch(value) | ||||
|  | ||||
| 		doubleQuotes := doubleQuotesRegex.FindStringSubmatch(value) | ||||
|  | ||||
| 		if singleQuotes != nil || doubleQuotes != nil { | ||||
| 			// pull the quotes off the edges | ||||
| 			value = value[1 : len(value)-1] | ||||
| 		} | ||||
|  | ||||
| 		if doubleQuotes != nil { | ||||
| 			// expand newlines | ||||
| 			value = escapeRegex.ReplaceAllStringFunc(value, func(match string) string { | ||||
| 				c := strings.TrimPrefix(match, `\`) | ||||
| 				switch c { | ||||
| 				case "n": | ||||
| 					return "\n" | ||||
| 				case "r": | ||||
| 					return "\r" | ||||
| 				default: | ||||
| 					return match | ||||
| 				} | ||||
| 			}) | ||||
| 			// unescape characters | ||||
| 			value = unescapeCharsRegex.ReplaceAllString(value, "$1") | ||||
| 		} | ||||
|  | ||||
| 		if singleQuotes == nil { | ||||
| 			value = expandVariables(value, envMap, lookupFn) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return value | ||||
| } | ||||
|  | ||||
| var expandVarRegex = regexp.MustCompile(`(\\)?(\$)(\()?\{?([A-Z0-9_]+)?\}?`) | ||||
|  | ||||
| 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 | ||||
| 		} | ||||
| 		if submatch[1] == "\\" || submatch[2] == "(" { | ||||
| 			return submatch[0][1:] | ||||
| 		} else if 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 doubleQuoteEscape(line string) string { | ||||
| 	for _, c := range doubleQuoteSpecialChars { | ||||
| 		toReplace := "\\" + string(c) | ||||
| 		if c == '\n' { | ||||
| 			toReplace = `\n` | ||||
| 		} | ||||
| 		if c == '\r' { | ||||
| 			toReplace = `\r` | ||||
| 		} | ||||
| 		line = strings.Replace(line, string(c), toReplace, -1) | ||||
| 	} | ||||
| 	return line | ||||
| } | ||||
							
								
								
									
										223
									
								
								vendor/github.com/compose-spec/godotenv/parser.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										223
									
								
								vendor/github.com/compose-spec/godotenv/parser.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -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 | ||||
| } | ||||
		Reference in New Issue
	
	Block a user
	 CrazyMax
					CrazyMax