mirror of
https://gitea.com/Lydanne/buildx.git
synced 2025-07-09 21:17:09 +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