mirror of
				https://gitea.com/Lydanne/buildx.git
				synced 2025-11-01 00:23:56 +08:00 
			
		
		
		
	vendor: update to compose-go 1.13.4
Signed-off-by: Nick Sieger <nick@nicksieger.com>
This commit is contained in:
		
							
								
								
									
										167
									
								
								vendor/github.com/compose-spec/compose-go/cli/options.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										167
									
								
								vendor/github.com/compose-spec/compose-go/cli/options.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -17,29 +17,65 @@ | ||||
| package cli | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"bytes" | ||||
| 	"io" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/pkg/errors" | ||||
| 	"github.com/sirupsen/logrus" | ||||
|  | ||||
| 	"github.com/compose-spec/compose-go/consts" | ||||
| 	"github.com/compose-spec/compose-go/dotenv" | ||||
| 	"github.com/compose-spec/compose-go/errdefs" | ||||
| 	"github.com/compose-spec/compose-go/loader" | ||||
| 	"github.com/compose-spec/compose-go/types" | ||||
| 	"github.com/compose-spec/compose-go/utils" | ||||
| 	"github.com/pkg/errors" | ||||
| 	"github.com/sirupsen/logrus" | ||||
| ) | ||||
|  | ||||
| // ProjectOptions groups the command line options recommended for a Compose implementation | ||||
| // ProjectOptions provides common configuration for loading a project. | ||||
| type ProjectOptions struct { | ||||
| 	Name        string | ||||
| 	WorkingDir  string | ||||
| 	// Name is a valid Compose project name to be used or empty. | ||||
| 	// | ||||
| 	// If empty, the project loader will automatically infer a reasonable | ||||
| 	// project name if possible. | ||||
| 	Name string | ||||
|  | ||||
| 	// WorkingDir is a file path to use as the project directory or empty. | ||||
| 	// | ||||
| 	// If empty, the project loader will automatically infer a reasonable | ||||
| 	// working directory if possible. | ||||
| 	WorkingDir string | ||||
|  | ||||
| 	// ConfigPaths are file paths to one or more Compose files. | ||||
| 	// | ||||
| 	// These are applied in order by the loader following the merge logic | ||||
| 	// as described in the spec. | ||||
| 	// | ||||
| 	// The first entry is required and is the primary Compose file. | ||||
| 	// For convenience, WithConfigFileEnv and WithDefaultConfigPath | ||||
| 	// are provided to populate this in a predictable manner. | ||||
| 	ConfigPaths []string | ||||
|  | ||||
| 	// Environment are additional environment variables to make available | ||||
| 	// for interpolation. | ||||
| 	// | ||||
| 	// NOTE: For security, the loader does not automatically expose any | ||||
| 	// process environment variables. For convenience, WithOsEnv can be | ||||
| 	// used if appropriate. | ||||
| 	Environment map[string]string | ||||
| 	EnvFile     string | ||||
|  | ||||
| 	// EnvFiles are file paths to ".env" files with additional environment | ||||
| 	// variable data. | ||||
| 	// | ||||
| 	// These are loaded in-order, so it is possible to override variables or | ||||
| 	// in subsequent files. | ||||
| 	// | ||||
| 	// This field is optional, but any file paths that are included here must | ||||
| 	// exist or an error will be returned during load. | ||||
| 	EnvFiles []string | ||||
|  | ||||
| 	loadOptions []func(*loader.Options) | ||||
| } | ||||
|  | ||||
| @@ -63,8 +99,12 @@ func NewProjectOptions(configs []string, opts ...ProjectOptionsFn) (*ProjectOpti | ||||
| // WithName defines ProjectOptions' name | ||||
| func WithName(name string) ProjectOptionsFn { | ||||
| 	return func(o *ProjectOptions) error { | ||||
| 		// a project (once loaded) cannot have an empty name | ||||
| 		// however, on the options object, the name is optional: if unset, | ||||
| 		// a name will be inferred by the loader, so it's legal to set the | ||||
| 		// name to an empty string here | ||||
| 		if name != loader.NormalizeProjectName(name) { | ||||
| 			return fmt.Errorf("%q is not a valid project name", name) | ||||
| 			return loader.InvalidProjectNameErr(name) | ||||
| 		} | ||||
| 		o.Name = name | ||||
| 		return nil | ||||
| @@ -187,9 +227,19 @@ func WithOsEnv(o *ProjectOptions) error { | ||||
| } | ||||
|  | ||||
| // WithEnvFile set an alternate env file | ||||
| // deprecated - use WithEnvFiles | ||||
| func WithEnvFile(file string) ProjectOptionsFn { | ||||
| 	var files []string | ||||
| 	if file != "" { | ||||
| 		files = []string{file} | ||||
| 	} | ||||
| 	return WithEnvFiles(files...) | ||||
| } | ||||
|  | ||||
| // WithEnvFiles set alternate env files | ||||
| func WithEnvFiles(file ...string) ProjectOptionsFn { | ||||
| 	return func(options *ProjectOptions) error { | ||||
| 		options.EnvFile = file | ||||
| 		options.EnvFiles = file | ||||
| 		return nil | ||||
| 	} | ||||
| } | ||||
| @@ -200,7 +250,7 @@ func WithDotEnv(o *ProjectOptions) error { | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	envMap, err := GetEnvFromFile(o.Environment, wd, o.EnvFile) | ||||
| 	envMap, err := GetEnvFromFile(o.Environment, wd, o.EnvFiles) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| @@ -213,55 +263,63 @@ func WithDotEnv(o *ProjectOptions) error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func GetEnvFromFile(currentEnv map[string]string, workingDir string, filename string) (map[string]string, error) { | ||||
| func GetEnvFromFile(currentEnv map[string]string, workingDir string, filenames []string) (map[string]string, error) { | ||||
| 	envMap := make(map[string]string) | ||||
|  | ||||
| 	dotEnvFile := filename | ||||
| 	if dotEnvFile == "" { | ||||
| 		dotEnvFile = filepath.Join(workingDir, ".env") | ||||
| 	dotEnvFiles := filenames | ||||
| 	if len(dotEnvFiles) == 0 { | ||||
| 		dotEnvFiles = append(dotEnvFiles, filepath.Join(workingDir, ".env")) | ||||
| 	} | ||||
| 	abs, err := filepath.Abs(dotEnvFile) | ||||
| 	if err != nil { | ||||
| 		return envMap, err | ||||
| 	} | ||||
| 	dotEnvFile = abs | ||||
|  | ||||
| 	s, err := os.Stat(dotEnvFile) | ||||
| 	if os.IsNotExist(err) { | ||||
| 		if filename != "" { | ||||
| 			return nil, errors.Errorf("Couldn't find env file: %s", filename) | ||||
| 	for _, dotEnvFile := range dotEnvFiles { | ||||
| 		abs, err := filepath.Abs(dotEnvFile) | ||||
| 		if err != nil { | ||||
| 			return envMap, err | ||||
| 		} | ||||
| 		return envMap, nil | ||||
| 	} | ||||
| 	if err != nil { | ||||
| 		return envMap, err | ||||
| 	} | ||||
| 		dotEnvFile = abs | ||||
|  | ||||
| 	if s.IsDir() { | ||||
| 		if filename == "" { | ||||
| 			return envMap, nil | ||||
| 		s, err := os.Stat(dotEnvFile) | ||||
| 		if os.IsNotExist(err) { | ||||
| 			if len(filenames) == 0 { | ||||
| 				return envMap, nil | ||||
| 			} | ||||
| 			return envMap, errors.Errorf("Couldn't find env file: %s", dotEnvFile) | ||||
| 		} | ||||
| 		return envMap, errors.Errorf("%s is a directory", dotEnvFile) | ||||
| 	} | ||||
|  | ||||
| 	file, err := os.Open(dotEnvFile) | ||||
| 	if err != nil { | ||||
| 		return envMap, errors.Wrapf(err, "failed to read %s", dotEnvFile) | ||||
| 	} | ||||
| 	defer file.Close() | ||||
|  | ||||
| 	env, err := dotenv.ParseWithLookup(file, func(k string) (string, bool) { | ||||
| 		v, ok := currentEnv[k] | ||||
| 		if !ok { | ||||
| 			return "", false | ||||
| 		if err != nil { | ||||
| 			return envMap, err | ||||
| 		} | ||||
|  | ||||
| 		if s.IsDir() { | ||||
| 			if len(filenames) == 0 { | ||||
| 				return envMap, nil | ||||
| 			} | ||||
| 			return envMap, errors.Errorf("%s is a directory", dotEnvFile) | ||||
| 		} | ||||
|  | ||||
| 		b, err := os.ReadFile(dotEnvFile) | ||||
| 		if os.IsNotExist(err) { | ||||
| 			return nil, errors.Errorf("Couldn't read env file: %s", dotEnvFile) | ||||
| 		} | ||||
| 		if err != nil { | ||||
| 			return envMap, err | ||||
| 		} | ||||
|  | ||||
| 		env, err := dotenv.ParseWithLookup(bytes.NewReader(b), func(k string) (string, bool) { | ||||
| 			v, ok := envMap[k] | ||||
| 			if ok { | ||||
| 				return v, true | ||||
| 			} | ||||
| 			v, ok = currentEnv[k] | ||||
| 			if !ok { | ||||
| 				return "", false | ||||
| 			} | ||||
| 			return v, true | ||||
| 		}) | ||||
| 		if err != nil { | ||||
| 			return envMap, errors.Wrapf(err, "failed to read %s", dotEnvFile) | ||||
| 		} | ||||
| 		for k, v := range env { | ||||
| 			envMap[k] = v | ||||
| 		} | ||||
| 		return v, true | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		return envMap, errors.Wrapf(err, "failed to read %s", dotEnvFile) | ||||
| 	} | ||||
| 	for k, v := range env { | ||||
| 		envMap[k] = v | ||||
| 	} | ||||
|  | ||||
| 	return envMap, nil | ||||
| @@ -393,7 +451,10 @@ func withNamePrecedenceLoad(absWorkingDir string, options *ProjectOptions) func( | ||||
| 		} else if nameFromEnv, ok := options.Environment[consts.ComposeProjectName]; ok && nameFromEnv != "" { | ||||
| 			opts.SetProjectName(nameFromEnv, true) | ||||
| 		} else { | ||||
| 			opts.SetProjectName(filepath.Base(absWorkingDir), false) | ||||
| 			opts.SetProjectName( | ||||
| 				loader.NormalizeProjectName(filepath.Base(absWorkingDir)), | ||||
| 				false, | ||||
| 			) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|   | ||||
							
								
								
									
										1
									
								
								vendor/github.com/compose-spec/compose-go/consts/consts.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								vendor/github.com/compose-spec/compose-go/consts/consts.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -20,4 +20,5 @@ const ( | ||||
| 	ComposeProjectName   = "COMPOSE_PROJECT_NAME" | ||||
| 	ComposePathSeparator = "COMPOSE_PATH_SEPARATOR" | ||||
| 	ComposeFilePath      = "COMPOSE_FILE" | ||||
| 	ComposeProfiles      = "COMPOSE_PROFILES" | ||||
| ) | ||||
|   | ||||
							
								
								
									
										7
									
								
								vendor/github.com/compose-spec/compose-go/dotenv/godotenv.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								vendor/github.com/compose-spec/compose-go/dotenv/godotenv.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -111,8 +111,13 @@ func Read(filenames ...string) (map[string]string, error) { | ||||
|  | ||||
| // 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) { | ||||
| 	return UnmarshalWithLookup(string(src), lookupFn) | ||||
| } | ||||
|  | ||||
| // UnmarshalWithLookup parses env file from string, returning a map of keys and values. | ||||
| func UnmarshalWithLookup(src string, lookupFn LookupFn) (map[string]string, error) { | ||||
| 	out := make(map[string]string) | ||||
| 	err := newParser().parseBytes(src, out, lookupFn) | ||||
| 	err := newParser().parse(src, out, lookupFn) | ||||
| 	return out, err | ||||
| } | ||||
|  | ||||
|   | ||||
							
								
								
									
										60
									
								
								vendor/github.com/compose-spec/compose-go/dotenv/parser.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										60
									
								
								vendor/github.com/compose-spec/compose-go/dotenv/parser.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -1,7 +1,6 @@ | ||||
| package dotenv | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"regexp" | ||||
| @@ -31,14 +30,14 @@ func newParser() *parser { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (p *parser) parseBytes(src []byte, out map[string]string, lookupFn LookupFn) error { | ||||
| func (p *parser) parse(src string, out map[string]string, lookupFn LookupFn) error { | ||||
| 	cutset := src | ||||
| 	if lookupFn == nil { | ||||
| 		lookupFn = noLookupFn | ||||
| 	} | ||||
| 	for { | ||||
| 		cutset = p.getStatementStart(cutset) | ||||
| 		if cutset == nil { | ||||
| 		if cutset == "" { | ||||
| 			// reached end of file | ||||
| 			break | ||||
| 		} | ||||
| @@ -75,10 +74,10 @@ func (p *parser) parseBytes(src []byte, out map[string]string, lookupFn LookupFn | ||||
| // getStatementPosition returns position of statement begin. | ||||
| // | ||||
| // It skips any comment line or non-whitespace character. | ||||
| func (p *parser) getStatementStart(src []byte) []byte { | ||||
| func (p *parser) getStatementStart(src string) string { | ||||
| 	pos := p.indexOfNonSpaceChar(src) | ||||
| 	if pos == -1 { | ||||
| 		return nil | ||||
| 		return "" | ||||
| 	} | ||||
|  | ||||
| 	src = src[pos:] | ||||
| @@ -87,70 +86,69 @@ func (p *parser) getStatementStart(src []byte) []byte { | ||||
| 	} | ||||
|  | ||||
| 	// skip comment section | ||||
| 	pos = bytes.IndexFunc(src, isCharFunc('\n')) | ||||
| 	pos = strings.IndexFunc(src, isCharFunc('\n')) | ||||
| 	if pos == -1 { | ||||
| 		return nil | ||||
| 		return "" | ||||
| 	} | ||||
| 	return p.getStatementStart(src[pos:]) | ||||
| } | ||||
|  | ||||
| // locateKeyName locates and parses key name and returns rest of slice | ||||
| func (p *parser) locateKeyName(src []byte) (string, []byte, bool, error) { | ||||
| func (p *parser) locateKeyName(src string) (string, string, bool, error) { | ||||
| 	var key string | ||||
| 	var inherited bool | ||||
| 	// trim "export" and space at beginning | ||||
| 	src = bytes.TrimLeftFunc(exportRegex.ReplaceAll(src, nil), isSpace) | ||||
| 	src = strings.TrimLeftFunc(exportRegex.ReplaceAllString(src, ""), 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) { | ||||
| 	for i, rune := range src { | ||||
| 		if isSpace(rune) { | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		switch char { | ||||
| 		switch rune { | ||||
| 		case '=', ':', '\n': | ||||
| 			// library also supports yaml-style value declaration | ||||
| 			key = string(src[0:i]) | ||||
| 			offset = i + 1 | ||||
| 			inherited = char == '\n' | ||||
| 			inherited = rune == '\n' | ||||
| 			break loop | ||||
| 		case '_', '.', '-', '[', ']': | ||||
| 		default: | ||||
| 			// variable name should match [A-Za-z0-9_.-] | ||||
| 			if unicode.IsLetter(rchar) || unicode.IsNumber(rchar) { | ||||
| 			if unicode.IsLetter(rune) || unicode.IsNumber(rune) { | ||||
| 				continue | ||||
| 			} | ||||
|  | ||||
| 			return "", nil, inherited, fmt.Errorf( | ||||
| 			return "", "", inherited, fmt.Errorf( | ||||
| 				`line %d: unexpected character %q in variable name`, | ||||
| 				p.line, string(char)) | ||||
| 				p.line, string(rune)) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if len(src) == 0 { | ||||
| 		return "", nil, inherited, errors.New("zero length string") | ||||
| 	if src == "" { | ||||
| 		return "", "", inherited, errors.New("zero length string") | ||||
| 	} | ||||
|  | ||||
| 	// trim whitespace | ||||
| 	key = strings.TrimRightFunc(key, unicode.IsSpace) | ||||
| 	cutset := bytes.TrimLeftFunc(src[offset:], isSpace) | ||||
| 	cutset := strings.TrimLeftFunc(src[offset:], isSpace) | ||||
| 	return key, cutset, inherited, nil | ||||
| } | ||||
|  | ||||
| // extractVarValue extracts variable value and returns rest of slice | ||||
| func (p *parser) extractVarValue(src []byte, envMap map[string]string, lookupFn LookupFn) (string, []byte, error) { | ||||
| func (p *parser) extractVarValue(src string, envMap map[string]string, lookupFn LookupFn) (string, string, error) { | ||||
| 	quote, isQuoted := hasQuotePrefix(src) | ||||
| 	if !isQuoted { | ||||
| 		// unquoted value - read until new line | ||||
| 		value, rest, _ := bytes.Cut(src, []byte("\n")) | ||||
| 		value, rest, _ := strings.Cut(src, "\n") | ||||
| 		p.line++ | ||||
|  | ||||
| 		// Remove inline comments on unquoted lines | ||||
| 		value, _, _ = bytes.Cut(value, []byte(" #")) | ||||
| 		value = bytes.TrimRightFunc(value, unicode.IsSpace) | ||||
| 		value, _, _ = strings.Cut(value, " #") | ||||
| 		value = strings.TrimRightFunc(value, unicode.IsSpace) | ||||
| 		retVal, err := expandVariables(string(value), envMap, lookupFn) | ||||
| 		return retVal, rest, err | ||||
| 	} | ||||
| @@ -176,7 +174,7 @@ func (p *parser) extractVarValue(src []byte, envMap map[string]string, lookupFn | ||||
| 			// variables on the result | ||||
| 			retVal, err := expandVariables(expandEscapes(value), envMap, lookupFn) | ||||
| 			if err != nil { | ||||
| 				return "", nil, err | ||||
| 				return "", "", err | ||||
| 			} | ||||
| 			value = retVal | ||||
| 		} | ||||
| @@ -185,12 +183,12 @@ func (p *parser) extractVarValue(src []byte, envMap map[string]string, lookupFn | ||||
| 	} | ||||
|  | ||||
| 	// return formatted error if quoted string is not terminated | ||||
| 	valEndIndex := bytes.IndexFunc(src, isCharFunc('\n')) | ||||
| 	valEndIndex := strings.IndexFunc(src, isCharFunc('\n')) | ||||
| 	if valEndIndex == -1 { | ||||
| 		valEndIndex = len(src) | ||||
| 	} | ||||
|  | ||||
| 	return "", nil, fmt.Errorf("line %d: unterminated quoted value %s", p.line, src[:valEndIndex]) | ||||
| 	return "", "", fmt.Errorf("line %d: unterminated quoted value %s", p.line, src[:valEndIndex]) | ||||
| } | ||||
|  | ||||
| func expandEscapes(str string) string { | ||||
| @@ -225,8 +223,8 @@ func expandEscapes(str string) string { | ||||
| 	return out | ||||
| } | ||||
|  | ||||
| func (p *parser) indexOfNonSpaceChar(src []byte) int { | ||||
| 	return bytes.IndexFunc(src, func(r rune) bool { | ||||
| func (p *parser) indexOfNonSpaceChar(src string) int { | ||||
| 	return strings.IndexFunc(src, func(r rune) bool { | ||||
| 		if r == '\n' { | ||||
| 			p.line++ | ||||
| 		} | ||||
| @@ -235,8 +233,8 @@ func (p *parser) indexOfNonSpaceChar(src []byte) int { | ||||
| } | ||||
|  | ||||
| // hasQuotePrefix reports whether charset starts with single or double quote and returns quote character | ||||
| func hasQuotePrefix(src []byte) (byte, bool) { | ||||
| 	if len(src) == 0 { | ||||
| func hasQuotePrefix(src string) (byte, bool) { | ||||
| 	if src == "" { | ||||
| 		return 0, false | ||||
| 	} | ||||
|  | ||||
|   | ||||
							
								
								
									
										2
									
								
								vendor/github.com/compose-spec/compose-go/interpolation/interpolation.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								vendor/github.com/compose-spec/compose-go/interpolation/interpolation.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -72,7 +72,7 @@ func recursiveInterpolate(value interface{}, path Path, opts Options) (interface | ||||
| 	switch value := value.(type) { | ||||
| 	case string: | ||||
| 		newValue, err := opts.Substitute(value, template.Mapping(opts.LookupValue)) | ||||
| 		if err != nil || newValue == value { | ||||
| 		if err != nil { | ||||
| 			return value, newPathError(path, err) | ||||
| 		} | ||||
| 		caster, ok := opts.getCasterForPath(path) | ||||
|   | ||||
							
								
								
									
										12
									
								
								vendor/github.com/compose-spec/compose-go/loader/full-example.yml
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										12
									
								
								vendor/github.com/compose-spec/compose-go/loader/full-example.yml
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -1,7 +1,13 @@ | ||||
| name: Full_Example_project_name | ||||
| name: full_example_project_name | ||||
| services: | ||||
|   foo: | ||||
|  | ||||
|   bar: | ||||
|     build: | ||||
|       dockerfile_inline: | | ||||
|         FROM alpine | ||||
|         RUN echo "hello" > /world.txt | ||||
|  | ||||
|   foo: | ||||
|     build: | ||||
|       context: ./dir | ||||
|       dockerfile: Dockerfile | ||||
| @@ -15,6 +21,8 @@ services: | ||||
|         - foo | ||||
|         - bar | ||||
|       labels: [FOO=BAR] | ||||
|       additional_contexts: | ||||
|         foo: /bar | ||||
|       secrets: | ||||
|         - secret1 | ||||
|         - source: secret2 | ||||
|   | ||||
							
								
								
									
										11
									
								
								vendor/github.com/compose-spec/compose-go/loader/interpolate.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										11
									
								
								vendor/github.com/compose-spec/compose-go/loader/interpolate.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -22,6 +22,7 @@ import ( | ||||
|  | ||||
| 	interp "github.com/compose-spec/compose-go/interpolation" | ||||
| 	"github.com/pkg/errors" | ||||
| 	"github.com/sirupsen/logrus" | ||||
| ) | ||||
|  | ||||
| var interpolateTypeCastMapping = map[interp.Path]interp.Cast{ | ||||
| @@ -114,9 +115,15 @@ func toFloat32(value string) (interface{}, error) { | ||||
| // should match http://yaml.org/type/bool.html | ||||
| func toBoolean(value string) (interface{}, error) { | ||||
| 	switch strings.ToLower(value) { | ||||
| 	case "y", "yes", "true", "on": | ||||
| 	case "true": | ||||
| 		return true, nil | ||||
| 	case "n", "no", "false", "off": | ||||
| 	case "false": | ||||
| 		return false, nil | ||||
| 	case "y", "yes", "on": | ||||
| 		logrus.Warnf("%q for boolean is not supported by YAML 1.2, please use `true`", value) | ||||
| 		return true, nil | ||||
| 	case "n", "no", "off": | ||||
| 		logrus.Warnf("%q for boolean is not supported by YAML 1.2, please use `false`", value) | ||||
| 		return false, nil | ||||
| 	default: | ||||
| 		return nil, errors.Errorf("invalid boolean: %s", value) | ||||
|   | ||||
							
								
								
									
										153
									
								
								vendor/github.com/compose-spec/compose-go/loader/loader.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										153
									
								
								vendor/github.com/compose-spec/compose-go/loader/loader.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -37,7 +37,7 @@ import ( | ||||
| 	"github.com/mitchellh/mapstructure" | ||||
| 	"github.com/pkg/errors" | ||||
| 	"github.com/sirupsen/logrus" | ||||
| 	"gopkg.in/yaml.v2" | ||||
| 	"gopkg.in/yaml.v3" | ||||
| ) | ||||
|  | ||||
| // Options supported by Load | ||||
| @@ -69,7 +69,7 @@ type Options struct { | ||||
| } | ||||
|  | ||||
| func (o *Options) SetProjectName(name string, imperativelySet bool) { | ||||
| 	o.projectName = NormalizeProjectName(name) | ||||
| 	o.projectName = name | ||||
| 	o.projectNameImperativelySet = imperativelySet | ||||
| } | ||||
|  | ||||
| @@ -138,6 +138,14 @@ func ParseYAML(source []byte) (map[string]interface{}, error) { | ||||
| 	if err := yaml.Unmarshal(source, &cfg); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	stringMap, ok := cfg.(map[string]interface{}) | ||||
| 	if ok { | ||||
| 		converted, err := convertToStringKeysRecursive(stringMap, "") | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		return converted.(map[string]interface{}), nil | ||||
| 	} | ||||
| 	cfgMap, ok := cfg.(map[interface{}]interface{}) | ||||
| 	if !ok { | ||||
| 		return nil, errors.Errorf("Top-level object must be a mapping") | ||||
| @@ -185,7 +193,7 @@ func Load(configDetails types.ConfigDetails, options ...func(*Options)) (*types. | ||||
| 			} | ||||
| 			dict, err := parseConfig(file.Content, opts) | ||||
| 			if err != nil { | ||||
| 				return nil, err | ||||
| 				return nil, fmt.Errorf("parsing %s: %w", file.Filename, err) | ||||
| 			} | ||||
| 			configDict = dict | ||||
| 			file.Config = dict | ||||
| @@ -194,7 +202,7 @@ func Load(configDetails types.ConfigDetails, options ...func(*Options)) (*types. | ||||
|  | ||||
| 		if !opts.SkipValidation { | ||||
| 			if err := schema.Validate(configDict); err != nil { | ||||
| 				return nil, err | ||||
| 				return nil, fmt.Errorf("validating %s: %w", file.Filename, err) | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| @@ -233,7 +241,7 @@ func Load(configDetails types.ConfigDetails, options ...func(*Options)) (*types. | ||||
| 	} | ||||
|  | ||||
| 	if !opts.SkipNormalization { | ||||
| 		err = normalize(project, opts.ResolvePaths) | ||||
| 		err = Normalize(project, opts.ResolvePaths) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| @@ -246,40 +254,82 @@ func Load(configDetails types.ConfigDetails, options ...func(*Options)) (*types. | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if len(opts.Profiles) > 0 { | ||||
| 		project.ApplyProfiles(opts.Profiles) | ||||
| 	if profiles, ok := project.Environment[consts.ComposeProfiles]; ok && len(opts.Profiles) == 0 { | ||||
| 		opts.Profiles = strings.Split(profiles, ",") | ||||
| 	} | ||||
| 	project.ApplyProfiles(opts.Profiles) | ||||
|  | ||||
| 	err = project.ResolveServicesEnvironment(opts.discardEnvFiles) | ||||
|  | ||||
| 	return project, err | ||||
| } | ||||
|  | ||||
| func InvalidProjectNameErr(v string) error { | ||||
| 	return fmt.Errorf( | ||||
| 		"%q is not a valid project name: it must contain only "+ | ||||
| 			"characters from [a-z0-9_-] and start with [a-z0-9]", v, | ||||
| 	) | ||||
| } | ||||
|  | ||||
| // projectName determines the canonical name to use for the project considering | ||||
| // the loader Options as well as `name` fields in Compose YAML fields (which | ||||
| // also support interpolation). | ||||
| // | ||||
| // TODO(milas): restructure loading so that we don't need to re-parse the YAML | ||||
| // here, as it's both wasteful and makes this code error-prone. | ||||
| func projectName(details types.ConfigDetails, opts *Options) (string, error) { | ||||
| 	projectName, projectNameImperativelySet := opts.GetProjectName() | ||||
| 	var pjNameFromConfigFile string | ||||
|  | ||||
| 	for _, configFile := range details.ConfigFiles { | ||||
| 		yml, err := ParseYAML(configFile.Content) | ||||
| 		if err != nil { | ||||
| 			return "", nil | ||||
| 	// if user did NOT provide a name explicitly, then see if one is defined | ||||
| 	// in any of the config files | ||||
| 	if !projectNameImperativelySet { | ||||
| 		var pjNameFromConfigFile string | ||||
| 		for _, configFile := range details.ConfigFiles { | ||||
| 			yml, err := ParseYAML(configFile.Content) | ||||
| 			if err != nil { | ||||
| 				// HACK: the way that loading is currently structured, this is | ||||
| 				// a duplicative parse just for the `name`. if it fails, we | ||||
| 				// give up but don't return the error, knowing that it'll get | ||||
| 				// caught downstream for us | ||||
| 				return "", nil | ||||
| 			} | ||||
| 			if val, ok := yml["name"]; ok && val != "" { | ||||
| 				sVal, ok := val.(string) | ||||
| 				if !ok { | ||||
| 					// HACK: see above - this is a temporary parsed version | ||||
| 					// that hasn't been schema-validated, but we don't want | ||||
| 					// to be the ones to actually report that, so give up, | ||||
| 					// knowing that it'll get caught downstream for us | ||||
| 					return "", nil | ||||
| 				} | ||||
| 				pjNameFromConfigFile = sVal | ||||
| 			} | ||||
| 		} | ||||
| 		if val, ok := yml["name"]; ok && val != "" { | ||||
| 			pjNameFromConfigFile = yml["name"].(string) | ||||
| 		if !opts.SkipInterpolation { | ||||
| 			interpolated, err := interp.Interpolate( | ||||
| 				map[string]interface{}{"name": pjNameFromConfigFile}, | ||||
| 				*opts.Interpolate, | ||||
| 			) | ||||
| 			if err != nil { | ||||
| 				return "", err | ||||
| 			} | ||||
| 			pjNameFromConfigFile = interpolated["name"].(string) | ||||
| 		} | ||||
| 	} | ||||
| 	if !opts.SkipInterpolation { | ||||
| 		interpolated, err := interp.Interpolate(map[string]interface{}{"name": pjNameFromConfigFile}, *opts.Interpolate) | ||||
| 		if err != nil { | ||||
| 			return "", err | ||||
| 		pjNameFromConfigFile = NormalizeProjectName(pjNameFromConfigFile) | ||||
| 		if pjNameFromConfigFile != "" { | ||||
| 			projectName = pjNameFromConfigFile | ||||
| 		} | ||||
| 		pjNameFromConfigFile = interpolated["name"].(string) | ||||
| 	} | ||||
| 	pjNameFromConfigFile = NormalizeProjectName(pjNameFromConfigFile) | ||||
| 	if !projectNameImperativelySet && pjNameFromConfigFile != "" { | ||||
| 		projectName = pjNameFromConfigFile | ||||
| 	} | ||||
|  | ||||
| 	if projectName == "" { | ||||
| 		return "", errors.New("project name must not be empty") | ||||
| 	} | ||||
|  | ||||
| 	if NormalizeProjectName(projectName) != projectName { | ||||
| 		return "", InvalidProjectNameErr(projectName) | ||||
| 	} | ||||
|  | ||||
| 	// TODO(milas): this should probably ALWAYS set (overriding any existing) | ||||
| 	if _, ok := details.Environment[consts.ComposeProjectName]; !ok && projectName != "" { | ||||
| 		details.Environment[consts.ComposeProjectName] = projectName | ||||
| 	} | ||||
| @@ -304,6 +354,8 @@ func parseConfig(b []byte, opts *Options) (map[string]interface{}, error) { | ||||
| 	return yml, err | ||||
| } | ||||
|  | ||||
| const extensions = "#extensions" // Using # prefix, we prevent risk to conflict with an actual yaml key | ||||
|  | ||||
| func groupXFieldsIntoExtensions(dict map[string]interface{}) map[string]interface{} { | ||||
| 	extras := map[string]interface{}{} | ||||
| 	for key, value := range dict { | ||||
| @@ -316,7 +368,7 @@ func groupXFieldsIntoExtensions(dict map[string]interface{}) map[string]interfac | ||||
| 		} | ||||
| 	} | ||||
| 	if len(extras) > 0 { | ||||
| 		dict["extensions"] = extras | ||||
| 		dict[extensions] = extras | ||||
| 	} | ||||
| 	return dict | ||||
| } | ||||
| @@ -355,7 +407,7 @@ func loadSections(filename string, config map[string]interface{}, configDetails | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	extensions := getSection(config, "extensions") | ||||
| 	extensions := getSection(config, extensions) | ||||
| 	if len(extensions) > 0 { | ||||
| 		cfg.Extensions = extensions | ||||
| 	} | ||||
| @@ -450,6 +502,22 @@ func createTransformHook(additionalTransformers ...Transformer) mapstructure.Dec | ||||
|  | ||||
| // keys need to be converted to strings for jsonschema | ||||
| func convertToStringKeysRecursive(value interface{}, keyPrefix string) (interface{}, error) { | ||||
| 	if mapping, ok := value.(map[string]interface{}); ok { | ||||
| 		for key, entry := range mapping { | ||||
| 			var newKeyPrefix string | ||||
| 			if keyPrefix == "" { | ||||
| 				newKeyPrefix = key | ||||
| 			} else { | ||||
| 				newKeyPrefix = fmt.Sprintf("%s.%s", keyPrefix, key) | ||||
| 			} | ||||
| 			convertedEntry, err := convertToStringKeysRecursive(entry, newKeyPrefix) | ||||
| 			if err != nil { | ||||
| 				return nil, err | ||||
| 			} | ||||
| 			mapping[key] = convertedEntry | ||||
| 		} | ||||
| 		return mapping, nil | ||||
| 	} | ||||
| 	if mapping, ok := value.(map[interface{}]interface{}); ok { | ||||
| 		dict := make(map[string]interface{}) | ||||
| 		for key, entry := range mapping { | ||||
| @@ -501,7 +569,7 @@ 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"] | ||||
| 	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{}) { | ||||
| @@ -541,16 +609,17 @@ func loadServiceWithExtends(filename, name string, servicesDict map[string]inter | ||||
| 	} | ||||
|  | ||||
| 	if serviceConfig.Extends != nil && !opts.SkipExtends { | ||||
| 		baseServiceName := *serviceConfig.Extends["service"] | ||||
| 		baseServiceName := serviceConfig.Extends.Service | ||||
| 		var baseService *types.ServiceConfig | ||||
| 		if file := serviceConfig.Extends["file"]; file == nil { | ||||
| 		file := serviceConfig.Extends.File | ||||
| 		if file == "" { | ||||
| 			baseService, err = loadServiceWithExtends(filename, baseServiceName, servicesDict, workingDir, lookupEnv, opts, ct) | ||||
| 			if err != nil { | ||||
| 				return nil, err | ||||
| 			} | ||||
| 		} else { | ||||
| 			// Resolve the path to the imported file, and load it. | ||||
| 			baseFilePath := absPath(workingDir, *file) | ||||
| 			baseFilePath := absPath(workingDir, file) | ||||
|  | ||||
| 			b, err := os.ReadFile(baseFilePath) | ||||
| 			if err != nil { | ||||
| @@ -569,10 +638,10 @@ func loadServiceWithExtends(filename, name string, servicesDict map[string]inter | ||||
| 			} | ||||
|  | ||||
| 			// Make paths relative to the importing Compose file. Note that we | ||||
| 			// make the paths relative to `*file` rather than `baseFilePath` so | ||||
| 			// that the resulting paths won't be absolute if `*file` isn't an | ||||
| 			// make the paths relative to `file` rather than `baseFilePath` so | ||||
| 			// that the resulting paths won't be absolute if `file` isn't an | ||||
| 			// absolute path. | ||||
| 			baseFileParent := filepath.Dir(*file) | ||||
| 			baseFileParent := filepath.Dir(file) | ||||
| 			if baseService.Build != nil { | ||||
| 				baseService.Build.Context = resolveBuildContextPath(baseFileParent, baseService.Build.Context) | ||||
| 			} | ||||
| @@ -583,12 +652,17 @@ func loadServiceWithExtends(filename, name string, servicesDict map[string]inter | ||||
| 				} | ||||
| 				baseService.Volumes[i].Source = resolveMaybeUnixPath(vol.Source, baseFileParent, lookupEnv) | ||||
| 			} | ||||
|  | ||||
| 			for i, envFile := range baseService.EnvFile { | ||||
| 				baseService.EnvFile[i] = resolveMaybeUnixPath(envFile, baseFileParent, lookupEnv) | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		serviceConfig, err = _merge(baseService, serviceConfig) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		serviceConfig.Extends = nil | ||||
| 	} | ||||
|  | ||||
| 	return serviceConfig, nil | ||||
| @@ -996,14 +1070,15 @@ var transformDependsOnConfig TransformerFunc = func(data interface{}) (interface | ||||
| 	} | ||||
| } | ||||
|  | ||||
| var transformExtendsConfig TransformerFunc = func(data interface{}) (interface{}, error) { | ||||
| 	switch data.(type) { | ||||
| var transformExtendsConfig TransformerFunc = func(value interface{}) (interface{}, error) { | ||||
| 	switch value.(type) { | ||||
| 	case string: | ||||
| 		data = map[string]interface{}{ | ||||
| 			"service": data, | ||||
| 		} | ||||
| 		return map[string]interface{}{"service": value}, nil | ||||
| 	case map[string]interface{}: | ||||
| 		return value, nil | ||||
| 	default: | ||||
| 		return value, errors.Errorf("invalid type %T for extends", value) | ||||
| 	} | ||||
| 	return transformMappingOrListFunc("=", true)(data) | ||||
| } | ||||
|  | ||||
| var transformServiceVolumeConfig TransformerFunc = func(data interface{}) (interface{}, error) { | ||||
|   | ||||
							
								
								
									
										2
									
								
								vendor/github.com/compose-spec/compose-go/loader/merge.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								vendor/github.com/compose-spec/compose-go/loader/merge.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -130,7 +130,7 @@ func _merge(baseService *types.ServiceConfig, overrideService *types.ServiceConf | ||||
| 	if overrideService.Command != nil { | ||||
| 		baseService.Command = overrideService.Command | ||||
| 	} | ||||
| 	if overrideService.HealthCheck != nil { | ||||
| 	if overrideService.HealthCheck != nil && overrideService.HealthCheck.Test != nil { | ||||
| 		baseService.HealthCheck.Test = overrideService.HealthCheck.Test | ||||
| 	} | ||||
| 	if overrideService.Entrypoint != nil { | ||||
|   | ||||
							
								
								
									
										76
									
								
								vendor/github.com/compose-spec/compose-go/loader/normalize.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										76
									
								
								vendor/github.com/compose-spec/compose-go/loader/normalize.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -20,6 +20,7 @@ import ( | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/compose-spec/compose-go/errdefs" | ||||
| 	"github.com/compose-spec/compose-go/types" | ||||
| @@ -27,8 +28,8 @@ import ( | ||||
| 	"github.com/sirupsen/logrus" | ||||
| ) | ||||
|  | ||||
| // normalize compose project by moving deprecated attributes to their canonical position and injecting implicit defaults | ||||
| func normalize(project *types.Project, resolvePaths bool) error { | ||||
| // Normalize compose project by moving deprecated attributes to their canonical position and injecting implicit defaults | ||||
| func Normalize(project *types.Project, resolvePaths bool) error { | ||||
| 	absWorkingDir, err := filepath.Abs(project.WorkingDir) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| @@ -71,17 +72,26 @@ func normalize(project *types.Project, resolvePaths bool) error { | ||||
| 		} | ||||
|  | ||||
| 		if s.Build != nil { | ||||
| 			if s.Build.Dockerfile == "" { | ||||
| 			if s.Build.Dockerfile == "" && s.Build.DockerfileInline == "" { | ||||
| 				s.Build.Dockerfile = "Dockerfile" | ||||
| 			} | ||||
| 			localContext := absPath(project.WorkingDir, s.Build.Context) | ||||
| 			if _, err := os.Stat(localContext); err == nil { | ||||
| 				if resolvePaths { | ||||
| 			if resolvePaths { | ||||
| 				// Build context 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 | ||||
| 				localContext := absPath(project.WorkingDir, s.Build.Context) | ||||
| 				if _, err := os.Stat(localContext); err == nil { | ||||
| 					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 | ||||
| 				for name, path := range s.Build.AdditionalContexts { | ||||
| 					if strings.Contains(path, "://") { // `docker-image://` or any builder specific context type | ||||
| 						continue | ||||
| 					} | ||||
| 					path = absPath(project.WorkingDir, path) | ||||
| 					if _, err := os.Stat(path); err == nil { | ||||
| 						s.Build.AdditionalContexts[name] = path | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 			s.Build.Args = s.Build.Args.Resolve(fn) | ||||
| 		} | ||||
| @@ -90,6 +100,41 @@ func normalize(project *types.Project, resolvePaths bool) error { | ||||
| 		} | ||||
| 		s.Environment = s.Environment.Resolve(fn) | ||||
|  | ||||
| 		if s.Extends != nil && s.Extends.File != "" { | ||||
| 			s.Extends.File = absPath(project.WorkingDir, s.Extends.File) | ||||
| 		} | ||||
|  | ||||
| 		for _, link := range s.Links { | ||||
| 			parts := strings.Split(link, ":") | ||||
| 			if len(parts) == 2 { | ||||
| 				link = parts[0] | ||||
| 			} | ||||
| 			s.DependsOn = setIfMissing(s.DependsOn, link, types.ServiceDependency{ | ||||
| 				Condition: types.ServiceConditionStarted, | ||||
| 				Restart:   true, | ||||
| 			}) | ||||
| 		} | ||||
|  | ||||
| 		for _, namespace := range []string{s.NetworkMode, s.Ipc, s.Pid, s.Uts, s.Cgroup} { | ||||
| 			if strings.HasPrefix(namespace, types.ServicePrefix) { | ||||
| 				name := namespace[len(types.ServicePrefix):] | ||||
| 				s.DependsOn = setIfMissing(s.DependsOn, name, types.ServiceDependency{ | ||||
| 					Condition: types.ServiceConditionStarted, | ||||
| 					Restart:   true, | ||||
| 				}) | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		for _, vol := range s.VolumesFrom { | ||||
| 			if !strings.HasPrefix(vol, types.ContainerPrefix) { | ||||
| 				spec := strings.Split(vol, ":") | ||||
| 				s.DependsOn = setIfMissing(s.DependsOn, spec[0], types.ServiceDependency{ | ||||
| 					Condition: types.ServiceConditionStarted, | ||||
| 					Restart:   false, | ||||
| 				}) | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		err := relocateLogDriver(&s) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| @@ -126,9 +171,20 @@ func normalize(project *types.Project, resolvePaths bool) error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // setIfMissing adds a ServiceDependency for service if not already defined | ||||
| func setIfMissing(d types.DependsOnConfig, service string, dep types.ServiceDependency) types.DependsOnConfig { | ||||
| 	if d == nil { | ||||
| 		d = types.DependsOnConfig{} | ||||
| 	} | ||||
| 	if _, ok := d[service]; !ok { | ||||
| 		d[service] = dep | ||||
| 	} | ||||
| 	return d | ||||
| } | ||||
|  | ||||
| func relocateScale(s *types.ServiceConfig) error { | ||||
| 	scale := uint64(s.Scale) | ||||
| 	if scale != 1 { | ||||
| 	if scale > 1 { | ||||
| 		logrus.Warn("`scale` is deprecated. Use the `deploy.replicas` element") | ||||
| 		if s.Deploy == nil { | ||||
| 			s.Deploy = &types.DeployConfig{} | ||||
|   | ||||
							
								
								
									
										22
									
								
								vendor/github.com/compose-spec/compose-go/loader/validate.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										22
									
								
								vendor/github.com/compose-spec/compose-go/loader/validate.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -32,6 +32,28 @@ func checkConsistency(project *types.Project) error { | ||||
| 			return errors.Wrapf(errdefs.ErrInvalid, "service %q has neither an image nor a build context specified", s.Name) | ||||
| 		} | ||||
|  | ||||
| 		if s.Build != nil { | ||||
| 			if s.Build.DockerfileInline != "" && s.Build.Dockerfile != "" { | ||||
| 				return errors.Wrapf(errdefs.ErrInvalid, "service %q declares mutualy exclusive dockerfile and dockerfile_inline", s.Name) | ||||
| 			} | ||||
|  | ||||
| 			if len(s.Build.Platforms) > 0 && s.Platform != "" { | ||||
| 				var found bool | ||||
| 				for _, platform := range s.Build.Platforms { | ||||
| 					if platform == s.Platform { | ||||
| 						found = true | ||||
| 						break | ||||
| 					} | ||||
| 				} | ||||
| 				if !found { | ||||
| 					return errors.Wrapf(errdefs.ErrInvalid, "service.build.platforms MUST include service.platform %q ", s.Platform) | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		if s.NetworkMode != "" && len(s.Networks) > 0 { | ||||
| 			return errors.Wrap(errdefs.ErrInvalid, fmt.Sprintf("service %s declares mutually exclusive `network_mode` and `networks`", s.Name)) | ||||
| 		} | ||||
| 		for network := range s.Networks { | ||||
| 			if _, ok := project.Networks[network]; !ok { | ||||
| 				return errors.Wrap(errdefs.ErrInvalid, fmt.Sprintf("service %q refers to undefined network %s", s.Name, network)) | ||||
|   | ||||
							
								
								
									
										2
									
								
								vendor/github.com/compose-spec/compose-go/loader/windows_path.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								vendor/github.com/compose-spec/compose-go/loader/windows_path.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -44,7 +44,7 @@ func isAbs(path string) (b bool) { | ||||
|  | ||||
| // volumeNameLen returns length of the leading volume name on Windows. | ||||
| // It returns 0 elsewhere. | ||||
| //nolint: gocyclo | ||||
| // nolint: gocyclo | ||||
| func volumeNameLen(path string) int { | ||||
| 	if len(path) < 2 { | ||||
| 		return 0 | ||||
|   | ||||
							
								
								
									
										26
									
								
								vendor/github.com/compose-spec/compose-go/schema/compose-spec.json
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										26
									
								
								vendor/github.com/compose-spec/compose-go/schema/compose-spec.json
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -13,6 +13,7 @@ | ||||
|  | ||||
|     "name": { | ||||
|       "type": "string", | ||||
|       "pattern": "^[a-z0-9][a-z0-9_-]*$", | ||||
|       "description": "define the Compose project name, until user defines one explicitly." | ||||
|     }, | ||||
|  | ||||
| @@ -90,12 +91,14 @@ | ||||
|               "properties": { | ||||
|                 "context": {"type": "string"}, | ||||
|                 "dockerfile": {"type": "string"}, | ||||
|                 "dockerfile_inline": {"type": "string"}, | ||||
|                 "args": {"$ref": "#/definitions/list_or_dict"}, | ||||
|                 "ssh": {"$ref": "#/definitions/list_or_dict"}, | ||||
|                 "labels": {"$ref": "#/definitions/list_or_dict"}, | ||||
|                 "cache_from": {"type": "array", "items": {"type": "string"}}, | ||||
|                 "cache_to": {"type": "array", "items": {"type": "string"}}, | ||||
|                 "no_cache": {"type": "boolean"}, | ||||
|                 "additional_contexts": {"$ref": "#/definitions/list_or_dict"}, | ||||
|                 "network": {"type": "string"}, | ||||
|                 "pull": {"type": "boolean"}, | ||||
|                 "target": {"type": "string"}, | ||||
| @@ -143,12 +146,7 @@ | ||||
|         "cap_drop": {"type": "array", "items": {"type": "string"}, "uniqueItems": true}, | ||||
|         "cgroup": {"type": "string", "enum": ["host", "private"]}, | ||||
|         "cgroup_parent": {"type": "string"}, | ||||
|         "command": { | ||||
|           "oneOf": [ | ||||
|             {"type": "string"}, | ||||
|             {"type": "array", "items": {"type": "string"}} | ||||
|           ] | ||||
|         }, | ||||
|         "command": {"$ref": "#/definitions/command"}, | ||||
|         "configs": {"$ref": "#/definitions/service_config_or_secret"}, | ||||
|         "container_name": {"type": "string"}, | ||||
|         "cpu_count": {"type": "integer", "minimum": 0}, | ||||
| @@ -181,6 +179,7 @@ | ||||
|                   "type": "object", | ||||
|                   "additionalProperties": false, | ||||
|                   "properties": { | ||||
|                     "restart": {"type": "boolean"}, | ||||
|                     "condition": { | ||||
|                       "type": "string", | ||||
|                       "enum": ["service_started", "service_healthy", "service_completed_successfully"] | ||||
| @@ -198,12 +197,7 @@ | ||||
|         "dns_opt": {"type": "array","items": {"type": "string"}, "uniqueItems": true}, | ||||
|         "dns_search": {"$ref": "#/definitions/string_or_list"}, | ||||
|         "domainname": {"type": "string"}, | ||||
|         "entrypoint": { | ||||
|           "oneOf": [ | ||||
|             {"type": "string"}, | ||||
|             {"type": "array", "items": {"type": "string"}} | ||||
|           ] | ||||
|         }, | ||||
|         "entrypoint": {"$ref": "#/definitions/command"}, | ||||
|         "env_file": {"$ref": "#/definitions/string_or_list"}, | ||||
|         "environment": {"$ref": "#/definitions/list_or_dict"}, | ||||
|  | ||||
| @@ -734,6 +728,14 @@ | ||||
|       "patternProperties": {"^x-": {}} | ||||
|     }, | ||||
|  | ||||
|     "command": { | ||||
|       "oneOf": [ | ||||
|         {"type": "null"}, | ||||
|         {"type": "string"}, | ||||
|         {"type": "array","items": {"type": "string"}} | ||||
|       ] | ||||
|     }, | ||||
|  | ||||
|     "string_or_list": { | ||||
|       "oneOf": [ | ||||
|         {"type": "string"}, | ||||
|   | ||||
							
								
								
									
										1
									
								
								vendor/github.com/compose-spec/compose-go/schema/schema.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								vendor/github.com/compose-spec/compose-go/schema/schema.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -52,6 +52,7 @@ func init() { | ||||
| } | ||||
|  | ||||
| // Schema is the compose-spec JSON schema | ||||
| // | ||||
| //go:embed compose-spec.json | ||||
| var Schema string | ||||
|  | ||||
|   | ||||
							
								
								
									
										18
									
								
								vendor/github.com/compose-spec/compose-go/template/template.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										18
									
								
								vendor/github.com/compose-spec/compose-go/template/template.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -47,6 +47,19 @@ func (e InvalidTemplateError) Error() string { | ||||
| 	return fmt.Sprintf("Invalid template: %#v", e.Template) | ||||
| } | ||||
|  | ||||
| // MissingRequiredError is returned when a variable template is missing | ||||
| type MissingRequiredError struct { | ||||
| 	Variable string | ||||
| 	Reason   string | ||||
| } | ||||
|  | ||||
| func (e MissingRequiredError) Error() string { | ||||
| 	if e.Reason != "" { | ||||
| 		return fmt.Sprintf("required variable %s is missing a value: %s", e.Variable, e.Reason) | ||||
| 	} | ||||
| 	return fmt.Sprintf("required variable %s is missing a value", e.Variable) | ||||
| } | ||||
|  | ||||
| // Mapping is a user-supplied function which maps from variable names to values. | ||||
| // Returns the value as a string and a bool indicating whether | ||||
| // the value is present, to distinguish between an empty string | ||||
| @@ -351,8 +364,9 @@ func withRequired(substitution string, mapping Mapping, sep string, valid func(s | ||||
| 	} | ||||
| 	value, ok := mapping(name) | ||||
| 	if !ok || !valid(value) { | ||||
| 		return "", true, &InvalidTemplateError{ | ||||
| 			Template: fmt.Sprintf("required variable %s is missing a value: %s", name, errorMessage), | ||||
| 		return "", true, &MissingRequiredError{ | ||||
| 			Reason:   errorMessage, | ||||
| 			Variable: name, | ||||
| 		} | ||||
| 	} | ||||
| 	return value, true, nil | ||||
|   | ||||
							
								
								
									
										160
									
								
								vendor/github.com/compose-spec/compose-go/types/project.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										160
									
								
								vendor/github.com/compose-spec/compose-go/types/project.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -18,6 +18,7 @@ package types | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| @@ -28,6 +29,7 @@ import ( | ||||
| 	godigest "github.com/opencontainers/go-digest" | ||||
| 	"github.com/pkg/errors" | ||||
| 	"golang.org/x/sync/errgroup" | ||||
| 	"gopkg.in/yaml.v3" | ||||
| ) | ||||
|  | ||||
| // Project is the result of loading a set of compose files | ||||
| @@ -39,16 +41,17 @@ type Project struct { | ||||
| 	Volumes      Volumes    `yaml:",omitempty" json:"volumes,omitempty"` | ||||
| 	Secrets      Secrets    `yaml:",omitempty" json:"secrets,omitempty"` | ||||
| 	Configs      Configs    `yaml:",omitempty" json:"configs,omitempty"` | ||||
| 	Extensions   Extensions `yaml:",inline" json:"-"` // https://github.com/golang/go/issues/6213 | ||||
| 	Extensions   Extensions `mapstructure:"#extensions" yaml:",inline" json:"-"` // https://github.com/golang/go/issues/6213 | ||||
| 	ComposeFiles []string   `yaml:"-" json:"-"` | ||||
| 	Environment  Mapping    `yaml:"-" json:"-"` | ||||
|  | ||||
| 	// DisabledServices track services which have been disable as profile is not active | ||||
| 	DisabledServices Services `yaml:"-" json:"-"` | ||||
| 	Profiles         []string `yaml:"-" json:"-"` | ||||
| } | ||||
|  | ||||
| // ServiceNames return names for all services in this Compose config | ||||
| func (p Project) ServiceNames() []string { | ||||
| func (p *Project) ServiceNames() []string { | ||||
| 	var names []string | ||||
| 	for _, s := range p.Services { | ||||
| 		names = append(names, s.Name) | ||||
| @@ -58,7 +61,7 @@ func (p Project) ServiceNames() []string { | ||||
| } | ||||
|  | ||||
| // VolumeNames return names for all volumes in this Compose config | ||||
| func (p Project) VolumeNames() []string { | ||||
| func (p *Project) VolumeNames() []string { | ||||
| 	var names []string | ||||
| 	for k := range p.Volumes { | ||||
| 		names = append(names, k) | ||||
| @@ -68,7 +71,7 @@ func (p Project) VolumeNames() []string { | ||||
| } | ||||
|  | ||||
| // NetworkNames return names for all volumes in this Compose config | ||||
| func (p Project) NetworkNames() []string { | ||||
| func (p *Project) NetworkNames() []string { | ||||
| 	var names []string | ||||
| 	for k := range p.Networks { | ||||
| 		names = append(names, k) | ||||
| @@ -78,7 +81,7 @@ func (p Project) NetworkNames() []string { | ||||
| } | ||||
|  | ||||
| // SecretNames return names for all secrets in this Compose config | ||||
| func (p Project) SecretNames() []string { | ||||
| func (p *Project) SecretNames() []string { | ||||
| 	var names []string | ||||
| 	for k := range p.Secrets { | ||||
| 		names = append(names, k) | ||||
| @@ -88,7 +91,7 @@ func (p Project) SecretNames() []string { | ||||
| } | ||||
|  | ||||
| // ConfigNames return names for all configs in this Compose config | ||||
| func (p Project) ConfigNames() []string { | ||||
| func (p *Project) ConfigNames() []string { | ||||
| 	var names []string | ||||
| 	for k := range p.Configs { | ||||
| 		names = append(names, k) | ||||
| @@ -98,7 +101,7 @@ func (p Project) ConfigNames() []string { | ||||
| } | ||||
|  | ||||
| // GetServices retrieve services by names, or return all services if no name specified | ||||
| func (p Project) GetServices(names ...string) (Services, error) { | ||||
| func (p *Project) GetServices(names ...string) (Services, error) { | ||||
| 	if len(names) == 0 { | ||||
| 		return p.Services, nil | ||||
| 	} | ||||
| @@ -119,8 +122,18 @@ func (p Project) GetServices(names ...string) (Services, error) { | ||||
| 	return services, nil | ||||
| } | ||||
|  | ||||
| // GetDisabledService retrieve disabled service by name | ||||
| func (p Project) GetDisabledService(name string) (ServiceConfig, error) { | ||||
| 	for _, config := range p.DisabledServices { | ||||
| 		if config.Name == name { | ||||
| 			return config, nil | ||||
| 		} | ||||
| 	} | ||||
| 	return ServiceConfig{}, fmt.Errorf("no such service: %s", name) | ||||
| } | ||||
|  | ||||
| // GetService retrieve a specific service by name | ||||
| func (p Project) GetService(name string) (ServiceConfig, error) { | ||||
| func (p *Project) GetService(name string) (ServiceConfig, error) { | ||||
| 	services, err := p.GetServices(name) | ||||
| 	if err != nil { | ||||
| 		return ServiceConfig{}, err | ||||
| @@ -131,7 +144,7 @@ func (p Project) GetService(name string) (ServiceConfig, error) { | ||||
| 	return services[0], nil | ||||
| } | ||||
|  | ||||
| func (p Project) AllServices() Services { | ||||
| func (p *Project) AllServices() Services { | ||||
| 	var all Services | ||||
| 	all = append(all, p.Services...) | ||||
| 	all = append(all, p.DisabledServices...) | ||||
| @@ -140,12 +153,16 @@ func (p Project) AllServices() Services { | ||||
|  | ||||
| type ServiceFunc func(service ServiceConfig) error | ||||
|  | ||||
| // WithServices run ServiceFunc on each service and dependencies in dependency order | ||||
| func (p Project) WithServices(names []string, fn ServiceFunc) error { | ||||
| 	return p.withServices(names, fn, map[string]bool{}) | ||||
| // WithServices run ServiceFunc on each service and dependencies according to DependencyPolicy | ||||
| func (p *Project) WithServices(names []string, fn ServiceFunc, options ...DependencyOption) error { | ||||
| 	if len(options) == 0 { | ||||
| 		// backward compatibility | ||||
| 		options = []DependencyOption{IncludeDependencies} | ||||
| 	} | ||||
| 	return p.withServices(names, fn, map[string]bool{}, options) | ||||
| } | ||||
|  | ||||
| func (p Project) withServices(names []string, fn ServiceFunc, seen map[string]bool) error { | ||||
| func (p *Project) withServices(names []string, fn ServiceFunc, seen map[string]bool, options []DependencyOption) error { | ||||
| 	services, err := p.GetServices(names...) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| @@ -155,9 +172,21 @@ func (p Project) withServices(names []string, fn ServiceFunc, seen map[string]bo | ||||
| 			continue | ||||
| 		} | ||||
| 		seen[service.Name] = true | ||||
| 		dependencies := service.GetDependencies() | ||||
| 		var dependencies []string | ||||
| 		for _, policy := range options { | ||||
| 			switch policy { | ||||
| 			case IncludeDependents: | ||||
| 				dependencies = append(dependencies, p.GetDependentsForService(service)...) | ||||
| 			case IncludeDependencies: | ||||
| 				dependencies = append(dependencies, service.GetDependencies()...) | ||||
| 			case IgnoreDependencies: | ||||
| 				// Noop | ||||
| 			default: | ||||
| 				return fmt.Errorf("unsupported dependency policy %d", policy) | ||||
| 			} | ||||
| 		} | ||||
| 		if len(dependencies) > 0 { | ||||
| 			err := p.withServices(dependencies, fn, seen) | ||||
| 			err := p.withServices(dependencies, fn, seen, options) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| @@ -169,6 +198,18 @@ func (p Project) withServices(names []string, fn ServiceFunc, seen map[string]bo | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (p *Project) GetDependentsForService(s ServiceConfig) []string { | ||||
| 	var dependent []string | ||||
| 	for _, service := range p.Services { | ||||
| 		for name := range service.DependsOn { | ||||
| 			if name == s.Name { | ||||
| 				dependent = append(dependent, service.Name) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return dependent | ||||
| } | ||||
|  | ||||
| // RelativePath resolve a relative path based project's working directory | ||||
| func (p *Project) RelativePath(path string) string { | ||||
| 	if path[0] == '~' { | ||||
| @@ -219,7 +260,7 @@ func (p *Project) ApplyProfiles(profiles []string) { | ||||
| 		} | ||||
| 	} | ||||
| 	var enabled, disabled Services | ||||
| 	for _, service := range p.Services { | ||||
| 	for _, service := range p.AllServices() { | ||||
| 		if service.HasProfile(profiles) { | ||||
| 			enabled = append(enabled, service) | ||||
| 		} else { | ||||
| @@ -228,6 +269,41 @@ func (p *Project) ApplyProfiles(profiles []string) { | ||||
| 	} | ||||
| 	p.Services = enabled | ||||
| 	p.DisabledServices = disabled | ||||
| 	p.Profiles = profiles | ||||
| } | ||||
|  | ||||
| // EnableServices ensure services are enabled and activate profiles accordingly | ||||
| func (p *Project) EnableServices(names ...string) error { | ||||
| 	if len(names) == 0 { | ||||
| 		return nil | ||||
| 	} | ||||
| 	var enabled []string | ||||
| 	for _, name := range names { | ||||
| 		_, err := p.GetService(name) | ||||
| 		if err == nil { | ||||
| 			// already enabled | ||||
| 			continue | ||||
| 		} | ||||
| 		def, err := p.GetDisabledService(name) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		enabled = append(enabled, def.Profiles...) | ||||
| 	} | ||||
|  | ||||
| 	profiles := p.Profiles | ||||
| PROFILES: | ||||
| 	for _, profile := range enabled { | ||||
| 		for _, p := range profiles { | ||||
| 			if p == profile { | ||||
| 				continue PROFILES | ||||
| 			} | ||||
| 		} | ||||
| 		profiles = append(profiles, profile) | ||||
| 	} | ||||
| 	p.ApplyProfiles(profiles) | ||||
|  | ||||
| 	return p.ResolveServicesEnvironment(true) | ||||
| } | ||||
|  | ||||
| // WithoutUnnecessaryResources drops networks/volumes/secrets/configs that are not referenced by active services | ||||
| @@ -292,8 +368,16 @@ func (p *Project) WithoutUnnecessaryResources() { | ||||
| 	p.Configs = configs | ||||
| } | ||||
|  | ||||
| // ForServices restrict the project model to a subset of services | ||||
| func (p *Project) ForServices(names []string) error { | ||||
| type DependencyOption int | ||||
|  | ||||
| const ( | ||||
| 	IncludeDependencies = iota | ||||
| 	IncludeDependents | ||||
| 	IgnoreDependencies | ||||
| ) | ||||
|  | ||||
| // ForServices restrict the project model to selected services and dependencies | ||||
| func (p *Project) ForServices(names []string, options ...DependencyOption) error { | ||||
| 	if len(names) == 0 { | ||||
| 		// All services | ||||
| 		return nil | ||||
| @@ -303,7 +387,7 @@ func (p *Project) ForServices(names []string) error { | ||||
| 	err := p.WithServices(names, func(service ServiceConfig) error { | ||||
| 		set[service.Name] = struct{}{} | ||||
| 		return nil | ||||
| 	}) | ||||
| 	}, options...) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| @@ -357,6 +441,44 @@ func (p *Project) ResolveImages(resolver func(named reference.Named) (godigest.D | ||||
| 	return eg.Wait() | ||||
| } | ||||
|  | ||||
| // MarshalYAML marshal Project into a yaml tree | ||||
| func (p *Project) MarshalYAML() ([]byte, error) { | ||||
| 	buf := bytes.NewBuffer([]byte{}) | ||||
| 	encoder := yaml.NewEncoder(buf) | ||||
| 	encoder.SetIndent(2) | ||||
| 	// encoder.CompactSeqIndent() FIXME https://github.com/go-yaml/yaml/pull/753 | ||||
| 	err := encoder.Encode(p) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return buf.Bytes(), nil | ||||
| } | ||||
|  | ||||
| // MarshalJSON makes Config implement json.Marshaler | ||||
| func (p *Project) MarshalJSON() ([]byte, error) { | ||||
| 	m := map[string]interface{}{ | ||||
| 		"name":     p.Name, | ||||
| 		"services": p.Services, | ||||
| 	} | ||||
|  | ||||
| 	if len(p.Networks) > 0 { | ||||
| 		m["networks"] = p.Networks | ||||
| 	} | ||||
| 	if len(p.Volumes) > 0 { | ||||
| 		m["volumes"] = p.Volumes | ||||
| 	} | ||||
| 	if len(p.Secrets) > 0 { | ||||
| 		m["secrets"] = p.Secrets | ||||
| 	} | ||||
| 	if len(p.Configs) > 0 { | ||||
| 		m["configs"] = p.Configs | ||||
| 	} | ||||
| 	for k, v := range p.Extensions { | ||||
| 		m[k] = v | ||||
| 	} | ||||
| 	return json.Marshal(m) | ||||
| } | ||||
|  | ||||
| // ResolveServicesEnvironment parse env_files set for services to resolve the actual environment map for services | ||||
| func (p Project) ResolveServicesEnvironment(discardEnvFiles bool) error { | ||||
| 	for i, service := range p.Services { | ||||
|   | ||||
							
								
								
									
										237
									
								
								vendor/github.com/compose-spec/compose-go/types/types.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										237
									
								
								vendor/github.com/compose-spec/compose-go/types/types.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -107,7 +107,7 @@ type ServiceConfig struct { | ||||
| 	// Command for the service containers. | ||||
| 	// If set, overrides COMMAND from the image. | ||||
| 	// | ||||
| 	// Set to `[]` or `''` to clear the command from the image. | ||||
| 	// Set to `[]` or an empty string to clear the command from the image. | ||||
| 	Command ShellCommand `yaml:",omitempty" json:"command"` // NOTE: we can NOT omitempty for JSON! see ShellCommand type for details. | ||||
|  | ||||
| 	Configs           []ServiceConfigObjConfig `yaml:",omitempty" json:"configs,omitempty"` | ||||
| @@ -126,13 +126,13 @@ type ServiceConfig struct { | ||||
| 	// Entrypoint for the service containers. | ||||
| 	// If set, overrides ENTRYPOINT from the image. | ||||
| 	// | ||||
| 	// Set to `[]` or `''` to clear the entrypoint from the image. | ||||
| 	// Set to `[]` or an empty string to clear the entrypoint from the image. | ||||
| 	Entrypoint ShellCommand `yaml:"entrypoint,omitempty" json:"entrypoint"` // NOTE: we can NOT omitempty for JSON! see ShellCommand type for details. | ||||
|  | ||||
| 	Environment     MappingWithEquals                `yaml:",omitempty" json:"environment,omitempty"` | ||||
| 	EnvFile         StringList                       `mapstructure:"env_file" yaml:"env_file,omitempty" json:"env_file,omitempty"` | ||||
| 	Expose          StringOrNumberList               `yaml:",omitempty" json:"expose,omitempty"` | ||||
| 	Extends         ExtendsConfig                    `yaml:"extends,omitempty" json:"extends,omitempty"` | ||||
| 	Extends         *ExtendsConfig                   `yaml:"extends,omitempty" json:"extends,omitempty"` | ||||
| 	ExternalLinks   []string                         `mapstructure:"external_links" yaml:"external_links,omitempty" json:"external_links,omitempty"` | ||||
| 	ExtraHosts      HostsList                        `mapstructure:"extra_hosts" yaml:"extra_hosts,omitempty" json:"extra_hosts,omitempty"` | ||||
| 	GroupAdd        []string                         `mapstructure:"group_add" yaml:"group_add,omitempty" json:"group_add,omitempty"` | ||||
| @@ -186,7 +186,7 @@ type ServiceConfig struct { | ||||
| 	VolumesFrom     []string                         `mapstructure:"volumes_from" yaml:"volumes_from,omitempty" json:"volumes_from,omitempty"` | ||||
| 	WorkingDir      string                           `mapstructure:"working_dir" yaml:"working_dir,omitempty" json:"working_dir,omitempty"` | ||||
|  | ||||
| 	Extensions map[string]interface{} `yaml:",inline" json:"-"` | ||||
| 	Extensions Extensions `mapstructure:"#extensions" yaml:",inline" json:"-"` | ||||
| } | ||||
|  | ||||
| // NetworksByPriority return the service networks IDs sorted according to Priority | ||||
| @@ -254,37 +254,26 @@ const ( | ||||
| 	NetworkModeContainerPrefix = ContainerPrefix | ||||
| ) | ||||
|  | ||||
| // GetDependencies retrieve all services this service depends on | ||||
| // GetDependencies retrieves all services this service depends on | ||||
| func (s ServiceConfig) GetDependencies() []string { | ||||
| 	dependencies := make(set) | ||||
| 	for dependency := range s.DependsOn { | ||||
| 		dependencies.append(dependency) | ||||
| 	} | ||||
| 	for _, link := range s.Links { | ||||
| 		parts := strings.Split(link, ":") | ||||
| 		if len(parts) == 2 { | ||||
| 			dependencies.append(parts[0]) | ||||
| 		} else { | ||||
| 			dependencies.append(link) | ||||
| 		} | ||||
| 	} | ||||
| 	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) { | ||||
| 			spec := strings.Split(vol, ":") | ||||
| 			dependencies.append(spec[0]) | ||||
| 		} | ||||
| 	var dependencies []string | ||||
| 	for service := range s.DependsOn { | ||||
| 		dependencies = append(dependencies, service) | ||||
| 	} | ||||
| 	return dependencies | ||||
| } | ||||
|  | ||||
| 	return dependencies.toSlice() | ||||
| // GetDependents retrieves all services which depend on this service | ||||
| func (s ServiceConfig) GetDependents(p *Project) []string { | ||||
| 	var dependent []string | ||||
| 	for _, service := range p.Services { | ||||
| 		for name := range service.DependsOn { | ||||
| 			if name == s.Name { | ||||
| 				dependent = append(dependent, service.Name) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return dependent | ||||
| } | ||||
|  | ||||
| type set map[string]struct{} | ||||
| @@ -305,25 +294,27 @@ func (s set) toSlice() []string { | ||||
|  | ||||
| // BuildConfig is a type for build | ||||
| type BuildConfig struct { | ||||
| 	Context    string                `yaml:",omitempty" json:"context,omitempty"` | ||||
| 	Dockerfile string                `yaml:",omitempty" json:"dockerfile,omitempty"` | ||||
| 	Args       MappingWithEquals     `yaml:",omitempty" json:"args,omitempty"` | ||||
| 	SSH        SSHConfig             `yaml:"ssh,omitempty" json:"ssh,omitempty"` | ||||
| 	Labels     Labels                `yaml:",omitempty" json:"labels,omitempty"` | ||||
| 	CacheFrom  StringList            `mapstructure:"cache_from" yaml:"cache_from,omitempty" json:"cache_from,omitempty"` | ||||
| 	CacheTo    StringList            `mapstructure:"cache_to" yaml:"cache_to,omitempty" json:"cache_to,omitempty"` | ||||
| 	NoCache    bool                  `mapstructure:"no_cache" yaml:"no_cache,omitempty" json:"no_cache,omitempty"` | ||||
| 	Pull       bool                  `mapstructure:"pull" yaml:"pull,omitempty" json:"pull,omitempty"` | ||||
| 	ExtraHosts HostsList             `mapstructure:"extra_hosts" yaml:"extra_hosts,omitempty" json:"extra_hosts,omitempty"` | ||||
| 	Isolation  string                `yaml:",omitempty" json:"isolation,omitempty"` | ||||
| 	Network    string                `yaml:",omitempty" json:"network,omitempty"` | ||||
| 	Target     string                `yaml:",omitempty" json:"target,omitempty"` | ||||
| 	Secrets    []ServiceSecretConfig `yaml:",omitempty" json:"secrets,omitempty"` | ||||
| 	Tags       StringList            `mapstructure:"tags" yaml:"tags,omitempty" json:"tags,omitempty"` | ||||
| 	Platforms  StringList            `mapstructure:"platforms" yaml:"platforms,omitempty" json:"platforms,omitempty"` | ||||
| 	Privileged bool                  `yaml:",omitempty" json:"privileged,omitempty"` | ||||
| 	Context            string                `yaml:",omitempty" json:"context,omitempty"` | ||||
| 	Dockerfile         string                `yaml:",omitempty" json:"dockerfile,omitempty"` | ||||
| 	DockerfileInline   string                `mapstructure:"dockerfile_inline,omitempty" yaml:"dockerfile_inline,omitempty" json:"dockerfile_inline,omitempty"` | ||||
| 	Args               MappingWithEquals     `yaml:",omitempty" json:"args,omitempty"` | ||||
| 	SSH                SSHConfig             `yaml:"ssh,omitempty" json:"ssh,omitempty"` | ||||
| 	Labels             Labels                `yaml:",omitempty" json:"labels,omitempty"` | ||||
| 	CacheFrom          StringList            `mapstructure:"cache_from" yaml:"cache_from,omitempty" json:"cache_from,omitempty"` | ||||
| 	CacheTo            StringList            `mapstructure:"cache_to" yaml:"cache_to,omitempty" json:"cache_to,omitempty"` | ||||
| 	NoCache            bool                  `mapstructure:"no_cache" yaml:"no_cache,omitempty" json:"no_cache,omitempty"` | ||||
| 	AdditionalContexts Mapping               `mapstructure:"additional_contexts" yaml:"additional_contexts,omitempty" json:"additional_contexts,omitempty"` | ||||
| 	Pull               bool                  `mapstructure:"pull" yaml:"pull,omitempty" json:"pull,omitempty"` | ||||
| 	ExtraHosts         HostsList             `mapstructure:"extra_hosts" yaml:"extra_hosts,omitempty" json:"extra_hosts,omitempty"` | ||||
| 	Isolation          string                `yaml:",omitempty" json:"isolation,omitempty"` | ||||
| 	Network            string                `yaml:",omitempty" json:"network,omitempty"` | ||||
| 	Target             string                `yaml:",omitempty" json:"target,omitempty"` | ||||
| 	Secrets            []ServiceSecretConfig `yaml:",omitempty" json:"secrets,omitempty"` | ||||
| 	Tags               StringList            `mapstructure:"tags" yaml:"tags,omitempty" json:"tags,omitempty"` | ||||
| 	Platforms          StringList            `mapstructure:"platforms" yaml:"platforms,omitempty" json:"platforms,omitempty"` | ||||
| 	Privileged         bool                  `yaml:",omitempty" json:"privileged,omitempty"` | ||||
|  | ||||
| 	Extensions map[string]interface{} `yaml:",inline" json:"-"` | ||||
| 	Extensions Extensions `mapstructure:"#extensions" yaml:",inline" json:"-"` | ||||
| } | ||||
|  | ||||
| // BlkioConfig define blkio config | ||||
| @@ -335,7 +326,7 @@ type BlkioConfig struct { | ||||
| 	DeviceWriteBps  []ThrottleDevice `mapstructure:"device_write_bps" yaml:",omitempty" json:"device_write_bps,omitempty"` | ||||
| 	DeviceWriteIOps []ThrottleDevice `mapstructure:"device_write_iops" yaml:",omitempty" json:"device_write_iops,omitempty"` | ||||
|  | ||||
| 	Extensions map[string]interface{} `yaml:",inline" json:"-"` | ||||
| 	Extensions Extensions `mapstructure:"#extensions" yaml:",inline" json:"-"` | ||||
| } | ||||
|  | ||||
| // WeightDevice is a structure that holds device:weight pair | ||||
| @@ -343,34 +334,34 @@ type WeightDevice struct { | ||||
| 	Path   string | ||||
| 	Weight uint16 | ||||
|  | ||||
| 	Extensions map[string]interface{} `yaml:",inline" json:"-"` | ||||
| 	Extensions Extensions `mapstructure:"#extensions" yaml:",inline" json:"-"` | ||||
| } | ||||
|  | ||||
| // ThrottleDevice is a structure that holds device:rate_per_second pair | ||||
| type ThrottleDevice struct { | ||||
| 	Path string | ||||
| 	Rate uint64 | ||||
| 	Rate UnitBytes | ||||
|  | ||||
| 	Extensions map[string]interface{} `yaml:",inline" json:"-"` | ||||
| 	Extensions Extensions `mapstructure:"#extensions" yaml:",inline" json:"-"` | ||||
| } | ||||
|  | ||||
| // ShellCommand is a string or list of string args. | ||||
| // | ||||
| // When marshaled to YAML, nil command fields will be omitted if `omitempty` | ||||
| // is specified as a struct tag. Explicitly empty commands (i.e. `[]` or `''`) | ||||
| // will serialize to an empty array (`[]`). | ||||
| // is specified as a struct tag. Explicitly empty commands (i.e. `[]` or | ||||
| // empty string will serialize to an empty array (`[]`). | ||||
| // | ||||
| // When marshaled to JSON, the `omitempty` struct must NOT be specified. | ||||
| // If the command field is nil, it will be serialized as `null`. | ||||
| // Explicitly empty commands (i.e. `[]` or `''`) will serialize to an empty | ||||
| // array (`[]`). | ||||
| // Explicitly empty commands (i.e. `[]` or empty string) will serialize to | ||||
| // an empty array (`[]`). | ||||
| // | ||||
| // The distinction between nil and explicitly empty is important to distinguish | ||||
| // between an unset value and a provided, but empty, value, which should be | ||||
| // preserved so that it can override any base value (e.g. container entrypoint). | ||||
| // | ||||
| // The different semantics between YAML and JSON are due to limitations with | ||||
| // JSON marshaling + `omitempty` in the Go stdlib, while gopkg.in/yaml.v2 gives | ||||
| // JSON marshaling + `omitempty` in the Go stdlib, while gopkg.in/yaml.v3 gives | ||||
| // us more flexibility via the yaml.IsZeroer interface. | ||||
| // | ||||
| // In the future, it might make sense to make fields of this type be | ||||
| @@ -394,7 +385,7 @@ func (s ShellCommand) IsZero() bool { | ||||
| // accurately if the `omitempty` struct tag is omitted/forgotten. | ||||
| // | ||||
| // A similar MarshalJSON() implementation is not needed because the Go stdlib | ||||
| // already serializes nil slices to `null`, whereas gopkg.in/yaml.v2 by default | ||||
| // already serializes nil slices to `null`, whereas gopkg.in/yaml.v3 by default | ||||
| // serializes nil slices to `[]`. | ||||
| func (s ShellCommand) MarshalYAML() (interface{}, error) { | ||||
| 	if s == nil { | ||||
| @@ -574,7 +565,7 @@ type LoggingConfig struct { | ||||
| 	Driver  string            `yaml:",omitempty" json:"driver,omitempty"` | ||||
| 	Options map[string]string `yaml:",omitempty" json:"options,omitempty"` | ||||
|  | ||||
| 	Extensions map[string]interface{} `yaml:",inline" json:"-"` | ||||
| 	Extensions Extensions `mapstructure:"#extensions" yaml:",inline" json:"-"` | ||||
| } | ||||
|  | ||||
| // DeployConfig the deployment configuration for a service | ||||
| @@ -589,7 +580,7 @@ type DeployConfig struct { | ||||
| 	Placement      Placement      `yaml:",omitempty" json:"placement,omitempty"` | ||||
| 	EndpointMode   string         `mapstructure:"endpoint_mode" yaml:"endpoint_mode,omitempty" json:"endpoint_mode,omitempty"` | ||||
|  | ||||
| 	Extensions map[string]interface{} `yaml:",inline" json:"-"` | ||||
| 	Extensions Extensions `mapstructure:"#extensions" yaml:",inline" json:"-"` | ||||
| } | ||||
|  | ||||
| // HealthCheckConfig the healthcheck configuration for a service | ||||
| @@ -601,7 +592,7 @@ type HealthCheckConfig struct { | ||||
| 	StartPeriod *Duration       `mapstructure:"start_period" yaml:"start_period,omitempty" json:"start_period,omitempty"` | ||||
| 	Disable     bool            `yaml:",omitempty" json:"disable,omitempty"` | ||||
|  | ||||
| 	Extensions map[string]interface{} `yaml:",inline" json:"-"` | ||||
| 	Extensions Extensions `mapstructure:"#extensions" yaml:",inline" json:"-"` | ||||
| } | ||||
|  | ||||
| // HealthCheckTest is the command run to test the health of a service | ||||
| @@ -616,7 +607,7 @@ type UpdateConfig struct { | ||||
| 	MaxFailureRatio float32  `mapstructure:"max_failure_ratio" yaml:"max_failure_ratio,omitempty" json:"max_failure_ratio,omitempty"` | ||||
| 	Order           string   `yaml:",omitempty" json:"order,omitempty"` | ||||
|  | ||||
| 	Extensions map[string]interface{} `yaml:",inline" json:"-"` | ||||
| 	Extensions Extensions `mapstructure:"#extensions" yaml:",inline" json:"-"` | ||||
| } | ||||
|  | ||||
| // Resources the resource limits and reservations | ||||
| @@ -624,7 +615,7 @@ type Resources struct { | ||||
| 	Limits       *Resource `yaml:",omitempty" json:"limits,omitempty"` | ||||
| 	Reservations *Resource `yaml:",omitempty" json:"reservations,omitempty"` | ||||
|  | ||||
| 	Extensions map[string]interface{} `yaml:",inline" json:"-"` | ||||
| 	Extensions Extensions `mapstructure:"#extensions" yaml:",inline" json:"-"` | ||||
| } | ||||
|  | ||||
| // Resource is a resource to be limited or reserved | ||||
| @@ -636,7 +627,7 @@ type Resource struct { | ||||
| 	Devices          []DeviceRequest   `mapstructure:"devices" yaml:"devices,omitempty" json:"devices,omitempty"` | ||||
| 	GenericResources []GenericResource `mapstructure:"generic_resources" yaml:"generic_resources,omitempty" json:"generic_resources,omitempty"` | ||||
|  | ||||
| 	Extensions map[string]interface{} `yaml:",inline" json:"-"` | ||||
| 	Extensions Extensions `mapstructure:"#extensions" yaml:",inline" json:"-"` | ||||
| } | ||||
|  | ||||
| type DeviceRequest struct { | ||||
| @@ -651,7 +642,7 @@ type DeviceRequest struct { | ||||
| type GenericResource struct { | ||||
| 	DiscreteResourceSpec *DiscreteGenericResource `mapstructure:"discrete_resource_spec" yaml:"discrete_resource_spec,omitempty" json:"discrete_resource_spec,omitempty"` | ||||
|  | ||||
| 	Extensions map[string]interface{} `yaml:",inline" json:"-"` | ||||
| 	Extensions Extensions `mapstructure:"#extensions" yaml:",inline" json:"-"` | ||||
| } | ||||
|  | ||||
| // DiscreteGenericResource represents a "user defined" resource which is defined | ||||
| @@ -662,7 +653,7 @@ type DiscreteGenericResource struct { | ||||
| 	Kind  string `json:"kind"` | ||||
| 	Value int64  `json:"value"` | ||||
|  | ||||
| 	Extensions map[string]interface{} `yaml:",inline" json:"-"` | ||||
| 	Extensions Extensions `mapstructure:"#extensions" yaml:",inline" json:"-"` | ||||
| } | ||||
|  | ||||
| // UnitBytes is the bytes type | ||||
| @@ -685,7 +676,7 @@ type RestartPolicy struct { | ||||
| 	MaxAttempts *uint64   `mapstructure:"max_attempts" yaml:"max_attempts,omitempty" json:"max_attempts,omitempty"` | ||||
| 	Window      *Duration `yaml:",omitempty" json:"window,omitempty"` | ||||
|  | ||||
| 	Extensions map[string]interface{} `yaml:",inline" json:"-"` | ||||
| 	Extensions Extensions `mapstructure:"#extensions" yaml:",inline" json:"-"` | ||||
| } | ||||
|  | ||||
| // Placement constraints for the service | ||||
| @@ -694,14 +685,14 @@ type Placement struct { | ||||
| 	Preferences []PlacementPreferences `yaml:",omitempty" json:"preferences,omitempty"` | ||||
| 	MaxReplicas uint64                 `mapstructure:"max_replicas_per_node" yaml:"max_replicas_per_node,omitempty" json:"max_replicas_per_node,omitempty"` | ||||
|  | ||||
| 	Extensions map[string]interface{} `yaml:",inline" json:"-"` | ||||
| 	Extensions Extensions `mapstructure:"#extensions" yaml:",inline" json:"-"` | ||||
| } | ||||
|  | ||||
| // PlacementPreferences is the preferences for a service placement | ||||
| type PlacementPreferences struct { | ||||
| 	Spread string `yaml:",omitempty" json:"spread,omitempty"` | ||||
|  | ||||
| 	Extensions map[string]interface{} `yaml:",inline" json:"-"` | ||||
| 	Extensions Extensions `mapstructure:"#extensions" yaml:",inline" json:"-"` | ||||
| } | ||||
|  | ||||
| // ServiceNetworkConfig is the network configuration for a service | ||||
| @@ -712,7 +703,7 @@ type ServiceNetworkConfig struct { | ||||
| 	Ipv6Address  string   `mapstructure:"ipv6_address" yaml:"ipv6_address,omitempty" json:"ipv6_address,omitempty"` | ||||
| 	LinkLocalIPs []string `mapstructure:"link_local_ips" yaml:"link_local_ips,omitempty" json:"link_local_ips,omitempty"` | ||||
|  | ||||
| 	Extensions map[string]interface{} `yaml:",inline" json:"-"` | ||||
| 	Extensions Extensions `mapstructure:"#extensions" yaml:",inline" json:"-"` | ||||
| } | ||||
|  | ||||
| // ServicePortConfig is the port configuration for a service | ||||
| @@ -723,7 +714,7 @@ type ServicePortConfig struct { | ||||
| 	Published string `yaml:",omitempty" json:"published,omitempty"` | ||||
| 	Protocol  string `yaml:",omitempty" json:"protocol,omitempty"` | ||||
|  | ||||
| 	Extensions map[string]interface{} `yaml:",inline" json:"-"` | ||||
| 	Extensions Extensions `mapstructure:"#extensions" yaml:",inline" json:"-"` | ||||
| } | ||||
|  | ||||
| // ParsePortConfig parse short syntax for service port configuration | ||||
| @@ -776,7 +767,7 @@ type ServiceVolumeConfig struct { | ||||
| 	Volume      *ServiceVolumeVolume `yaml:",omitempty" json:"volume,omitempty"` | ||||
| 	Tmpfs       *ServiceVolumeTmpfs  `yaml:",omitempty" json:"tmpfs,omitempty"` | ||||
|  | ||||
| 	Extensions map[string]interface{} `yaml:",inline" json:"-"` | ||||
| 	Extensions Extensions `mapstructure:"#extensions" yaml:",inline" json:"-"` | ||||
| } | ||||
|  | ||||
| // String render ServiceVolumeConfig as a volume string, one can parse back using loader.ParseVolume | ||||
| @@ -820,7 +811,7 @@ type ServiceVolumeBind struct { | ||||
| 	Propagation    string `yaml:",omitempty" json:"propagation,omitempty"` | ||||
| 	CreateHostPath bool   `mapstructure:"create_host_path" yaml:"create_host_path,omitempty" json:"create_host_path,omitempty"` | ||||
|  | ||||
| 	Extensions map[string]interface{} `yaml:",inline" json:"-"` | ||||
| 	Extensions Extensions `mapstructure:"#extensions" yaml:",inline" json:"-"` | ||||
| } | ||||
|  | ||||
| // SELinux represents the SELinux re-labeling options. | ||||
| @@ -851,7 +842,7 @@ const ( | ||||
| type ServiceVolumeVolume struct { | ||||
| 	NoCopy bool `mapstructure:"nocopy" yaml:"nocopy,omitempty" json:"nocopy,omitempty"` | ||||
|  | ||||
| 	Extensions map[string]interface{} `yaml:",inline" json:"-"` | ||||
| 	Extensions Extensions `mapstructure:"#extensions" yaml:",inline" json:"-"` | ||||
| } | ||||
|  | ||||
| // ServiceVolumeTmpfs are options for a service volume of type tmpfs | ||||
| @@ -860,7 +851,7 @@ type ServiceVolumeTmpfs struct { | ||||
|  | ||||
| 	Mode uint32 `yaml:",omitempty" json:"mode,omitempty"` | ||||
|  | ||||
| 	Extensions map[string]interface{} `yaml:",inline" json:"-"` | ||||
| 	Extensions Extensions `mapstructure:"#extensions" yaml:",inline" json:"-"` | ||||
| } | ||||
|  | ||||
| // FileReferenceConfig for a reference to a swarm file object | ||||
| @@ -871,7 +862,7 @@ type FileReferenceConfig struct { | ||||
| 	GID    string  `yaml:",omitempty" json:"gid,omitempty"` | ||||
| 	Mode   *uint32 `yaml:",omitempty" json:"mode,omitempty"` | ||||
|  | ||||
| 	Extensions map[string]interface{} `yaml:",inline" json:"-"` | ||||
| 	Extensions Extensions `mapstructure:"#extensions" yaml:",inline" json:"-"` | ||||
| } | ||||
|  | ||||
| // ServiceConfigObjConfig is the config obj configuration for a service | ||||
| @@ -886,7 +877,7 @@ type UlimitsConfig struct { | ||||
| 	Soft   int `yaml:",omitempty" json:"soft,omitempty"` | ||||
| 	Hard   int `yaml:",omitempty" json:"hard,omitempty"` | ||||
|  | ||||
| 	Extensions map[string]interface{} `yaml:",inline" json:"-"` | ||||
| 	Extensions Extensions `mapstructure:"#extensions" yaml:",inline" json:"-"` | ||||
| } | ||||
|  | ||||
| // MarshalYAML makes UlimitsConfig implement yaml.Marshaller | ||||
| @@ -894,7 +885,13 @@ func (u *UlimitsConfig) MarshalYAML() (interface{}, error) { | ||||
| 	if u.Single != 0 { | ||||
| 		return u.Single, nil | ||||
| 	} | ||||
| 	return u, nil | ||||
| 	return struct { | ||||
| 		Soft int | ||||
| 		Hard int | ||||
| 	}{ | ||||
| 		Soft: u.Soft, | ||||
| 		Hard: u.Hard, | ||||
| 	}, nil | ||||
| } | ||||
|  | ||||
| // MarshalJSON makes UlimitsConfig implement json.Marshaller | ||||
| @@ -908,23 +905,23 @@ func (u *UlimitsConfig) MarshalJSON() ([]byte, error) { | ||||
|  | ||||
| // NetworkConfig for a network | ||||
| type NetworkConfig struct { | ||||
| 	Name       string                 `yaml:",omitempty" json:"name,omitempty"` | ||||
| 	Driver     string                 `yaml:",omitempty" json:"driver,omitempty"` | ||||
| 	DriverOpts map[string]string      `mapstructure:"driver_opts" yaml:"driver_opts,omitempty" json:"driver_opts,omitempty"` | ||||
| 	Ipam       IPAMConfig             `yaml:",omitempty" json:"ipam,omitempty"` | ||||
| 	External   External               `yaml:",omitempty" json:"external,omitempty"` | ||||
| 	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:"-"` | ||||
| 	Name       string            `yaml:",omitempty" json:"name,omitempty"` | ||||
| 	Driver     string            `yaml:",omitempty" json:"driver,omitempty"` | ||||
| 	DriverOpts map[string]string `mapstructure:"driver_opts" yaml:"driver_opts,omitempty" json:"driver_opts,omitempty"` | ||||
| 	Ipam       IPAMConfig        `yaml:",omitempty" json:"ipam,omitempty"` | ||||
| 	External   External          `yaml:",omitempty" json:"external,omitempty"` | ||||
| 	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 Extensions        `mapstructure:"#extensions" yaml:",inline" json:"-"` | ||||
| } | ||||
|  | ||||
| // IPAMConfig for a network | ||||
| type IPAMConfig struct { | ||||
| 	Driver     string                 `yaml:",omitempty" json:"driver,omitempty"` | ||||
| 	Config     []*IPAMPool            `yaml:",omitempty" json:"config,omitempty"` | ||||
| 	Extensions map[string]interface{} `yaml:",inline" json:"-"` | ||||
| 	Driver     string      `yaml:",omitempty" json:"driver,omitempty"` | ||||
| 	Config     []*IPAMPool `yaml:",omitempty" json:"config,omitempty"` | ||||
| 	Extensions Extensions  `mapstructure:"#extensions" yaml:",inline" json:"-"` | ||||
| } | ||||
|  | ||||
| // IPAMPool for a network | ||||
| @@ -938,21 +935,21 @@ type IPAMPool struct { | ||||
|  | ||||
| // VolumeConfig for a volume | ||||
| type VolumeConfig struct { | ||||
| 	Name       string                 `yaml:",omitempty" json:"name,omitempty"` | ||||
| 	Driver     string                 `yaml:",omitempty" json:"driver,omitempty"` | ||||
| 	DriverOpts map[string]string      `mapstructure:"driver_opts" yaml:"driver_opts,omitempty" json:"driver_opts,omitempty"` | ||||
| 	External   External               `yaml:",omitempty" json:"external,omitempty"` | ||||
| 	Labels     Labels                 `yaml:",omitempty" json:"labels,omitempty"` | ||||
| 	Extensions map[string]interface{} `yaml:",inline" json:"-"` | ||||
| 	Name       string            `yaml:",omitempty" json:"name,omitempty"` | ||||
| 	Driver     string            `yaml:",omitempty" json:"driver,omitempty"` | ||||
| 	DriverOpts map[string]string `mapstructure:"driver_opts" yaml:"driver_opts,omitempty" json:"driver_opts,omitempty"` | ||||
| 	External   External          `yaml:",omitempty" json:"external,omitempty"` | ||||
| 	Labels     Labels            `yaml:",omitempty" json:"labels,omitempty"` | ||||
| 	Extensions Extensions        `mapstructure:"#extensions" yaml:",inline" json:"-"` | ||||
| } | ||||
|  | ||||
| // External identifies a Volume or Network as a reference to a resource that is | ||||
| // not managed, and should already exist. | ||||
| // External.name is deprecated and replaced by Volume.name | ||||
| type External struct { | ||||
| 	Name       string                 `yaml:",omitempty" json:"name,omitempty"` | ||||
| 	External   bool                   `yaml:",omitempty" json:"external,omitempty"` | ||||
| 	Extensions map[string]interface{} `yaml:",inline" json:"-"` | ||||
| 	Name       string     `yaml:",omitempty" json:"name,omitempty"` | ||||
| 	External   bool       `yaml:",omitempty" json:"external,omitempty"` | ||||
| 	Extensions Extensions `mapstructure:"#extensions" yaml:",inline" json:"-"` | ||||
| } | ||||
|  | ||||
| // MarshalYAML makes External implement yaml.Marshaller | ||||
| @@ -973,23 +970,23 @@ func (e External) MarshalJSON() ([]byte, error) { | ||||
|  | ||||
| // CredentialSpecConfig for credential spec on Windows | ||||
| type CredentialSpecConfig struct { | ||||
| 	Config     string                 `yaml:",omitempty" json:"config,omitempty"` // Config was added in API v1.40 | ||||
| 	File       string                 `yaml:",omitempty" json:"file,omitempty"` | ||||
| 	Registry   string                 `yaml:",omitempty" json:"registry,omitempty"` | ||||
| 	Extensions map[string]interface{} `yaml:",inline" json:"-"` | ||||
| 	Config     string     `yaml:",omitempty" json:"config,omitempty"` // Config was added in API v1.40 | ||||
| 	File       string     `yaml:",omitempty" json:"file,omitempty"` | ||||
| 	Registry   string     `yaml:",omitempty" json:"registry,omitempty"` | ||||
| 	Extensions Extensions `mapstructure:"#extensions" yaml:",inline" json:"-"` | ||||
| } | ||||
|  | ||||
| // FileObjectConfig is a config type for a file used by a service | ||||
| type FileObjectConfig struct { | ||||
| 	Name           string                 `yaml:",omitempty" json:"name,omitempty"` | ||||
| 	File           string                 `yaml:",omitempty" json:"file,omitempty"` | ||||
| 	Environment    string                 `yaml:",omitempty" json:"environment,omitempty"` | ||||
| 	External       External               `yaml:",omitempty" json:"external,omitempty"` | ||||
| 	Labels         Labels                 `yaml:",omitempty" json:"labels,omitempty"` | ||||
| 	Driver         string                 `yaml:",omitempty" json:"driver,omitempty"` | ||||
| 	DriverOpts     map[string]string      `mapstructure:"driver_opts" yaml:"driver_opts,omitempty" json:"driver_opts,omitempty"` | ||||
| 	TemplateDriver string                 `mapstructure:"template_driver" yaml:"template_driver,omitempty" json:"template_driver,omitempty"` | ||||
| 	Extensions     map[string]interface{} `yaml:",inline" json:"-"` | ||||
| 	Name           string            `yaml:",omitempty" json:"name,omitempty"` | ||||
| 	File           string            `yaml:",omitempty" json:"file,omitempty"` | ||||
| 	Environment    string            `yaml:",omitempty" json:"environment,omitempty"` | ||||
| 	External       External          `yaml:",omitempty" json:"external,omitempty"` | ||||
| 	Labels         Labels            `yaml:",omitempty" json:"labels,omitempty"` | ||||
| 	Driver         string            `yaml:",omitempty" json:"driver,omitempty"` | ||||
| 	DriverOpts     map[string]string `mapstructure:"driver_opts" yaml:"driver_opts,omitempty" json:"driver_opts,omitempty"` | ||||
| 	TemplateDriver string            `mapstructure:"template_driver" yaml:"template_driver,omitempty" json:"template_driver,omitempty"` | ||||
| 	Extensions     Extensions        `mapstructure:"#extensions" yaml:",inline" json:"-"` | ||||
| } | ||||
|  | ||||
| const ( | ||||
| @@ -1006,11 +1003,15 @@ const ( | ||||
| type DependsOnConfig map[string]ServiceDependency | ||||
|  | ||||
| type ServiceDependency struct { | ||||
| 	Condition  string                 `yaml:",omitempty" json:"condition,omitempty"` | ||||
| 	Extensions map[string]interface{} `yaml:",inline" json:"-"` | ||||
| 	Condition  string     `yaml:",omitempty" json:"condition,omitempty"` | ||||
| 	Restart    bool       `yaml:",omitempty" json:"restart,omitempty"` | ||||
| 	Extensions Extensions `mapstructure:"#extensions" yaml:",inline" json:"-"` | ||||
| } | ||||
|  | ||||
| type ExtendsConfig MappingWithEquals | ||||
| type ExtendsConfig struct { | ||||
| 	File    string `yaml:",omitempty" json:"file,omitempty"` | ||||
| 	Service string `yaml:",omitempty" json:"service,omitempty"` | ||||
| } | ||||
|  | ||||
| // SecretConfig for a secret | ||||
| type SecretConfig FileObjectConfig | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Nick Sieger
					Nick Sieger