mirror of https://github.com/docker/buildx.git
Merge pull request #2425 from glours/bump-compose-go-2.1.0
bump compose-go to v2.1.1
This commit is contained in:
commit
1f28985d20
2
go.mod
2
go.mod
|
@ -6,7 +6,7 @@ require (
|
||||||
github.com/Masterminds/semver/v3 v3.2.1
|
github.com/Masterminds/semver/v3 v3.2.1
|
||||||
github.com/Microsoft/go-winio v0.6.1
|
github.com/Microsoft/go-winio v0.6.1
|
||||||
github.com/aws/aws-sdk-go-v2/config v1.26.6
|
github.com/aws/aws-sdk-go-v2/config v1.26.6
|
||||||
github.com/compose-spec/compose-go/v2 v2.0.2
|
github.com/compose-spec/compose-go/v2 v2.1.1
|
||||||
github.com/containerd/console v1.0.4
|
github.com/containerd/console v1.0.4
|
||||||
github.com/containerd/containerd v1.7.15
|
github.com/containerd/containerd v1.7.15
|
||||||
github.com/containerd/continuity v0.4.3
|
github.com/containerd/continuity v0.4.3
|
||||||
|
|
4
go.sum
4
go.sum
|
@ -84,8 +84,8 @@ github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+g
|
||||||
github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||||
github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE=
|
github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE=
|
||||||
github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4=
|
github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4=
|
||||||
github.com/compose-spec/compose-go/v2 v2.0.2 h1:zhXMV7VWI00Su0LdKt8/sxeXxcjLWhmGmpEyw+ZYznI=
|
github.com/compose-spec/compose-go/v2 v2.1.1 h1:tKuYJwAVgxIryRrsvWJSf1kNviVOQVVqwyHsV6YoIUc=
|
||||||
github.com/compose-spec/compose-go/v2 v2.0.2/go.mod h1:bEPizBkIojlQ20pi2vNluBa58tevvj0Y18oUSHPyfdc=
|
github.com/compose-spec/compose-go/v2 v2.1.1/go.mod h1:bEPizBkIojlQ20pi2vNluBa58tevvj0Y18oUSHPyfdc=
|
||||||
github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM=
|
github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM=
|
||||||
github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw=
|
github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw=
|
||||||
github.com/containerd/console v1.0.4 h1:F2g4+oChYvBTsASRTz8NP6iIAi97J3TtSAsLbIFn4ro=
|
github.com/containerd/console v1.0.4 h1:F2g4+oChYvBTsASRTz8NP6iIAi97J3TtSAsLbIFn4ro=
|
||||||
|
|
|
@ -283,6 +283,9 @@ func WithEnvFiles(file ...string) ProjectOptionsFn {
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if !s.IsDir() {
|
if !s.IsDir() {
|
||||||
o.EnvFiles = []string{defaultDotEnv}
|
o.EnvFiles = []string{defaultDotEnv}
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,8 +50,8 @@ func (g *graph[T]) checkCycle() error {
|
||||||
func searchCycle[T any](path []string, v *vertex[T]) error {
|
func searchCycle[T any](path []string, v *vertex[T]) error {
|
||||||
names := utils.MapKeys(v.children)
|
names := utils.MapKeys(v.children)
|
||||||
for _, name := range names {
|
for _, name := range names {
|
||||||
if i := slices.Index(path, name); i > 0 {
|
if i := slices.Index(path, name); i >= 0 {
|
||||||
return fmt.Errorf("dependency cycle detected: %s", strings.Join(path[i:], " -> "))
|
return fmt.Errorf("dependency cycle detected: %s -> %s", strings.Join(path[i:], " -> "), name)
|
||||||
}
|
}
|
||||||
ch := v.children[name]
|
ch := v.children[name]
|
||||||
err := searchCycle(append(path, name), ch)
|
err := searchCycle(append(path, name), ch)
|
||||||
|
|
|
@ -29,12 +29,15 @@ import (
|
||||||
"github.com/compose-spec/compose-go/v2/types"
|
"github.com/compose-spec/compose-go/v2/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// loadIncludeConfig parse the require config from raw yaml
|
// loadIncludeConfig parse the required config from raw yaml
|
||||||
func loadIncludeConfig(source any) ([]types.IncludeConfig, error) {
|
func loadIncludeConfig(source any) ([]types.IncludeConfig, error) {
|
||||||
if source == nil {
|
if source == nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
configs := source.([]any)
|
configs, ok := source.([]any)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("`include` must be a list, got %s", source)
|
||||||
|
}
|
||||||
for i, config := range configs {
|
for i, config := range configs {
|
||||||
if v, ok := config.(string); ok {
|
if v, ok := config.(string); ok {
|
||||||
configs[i] = map[string]any{
|
configs[i] = map[string]any{
|
||||||
|
@ -73,11 +76,19 @@ func ApplyInclude(ctx context.Context, configDetails types.ConfigDetails, model
|
||||||
p = path
|
p = path
|
||||||
|
|
||||||
if i == 0 { // This is the "main" file, used to define project-directory. Others are overrides
|
if i == 0 { // This is the "main" file, used to define project-directory. Others are overrides
|
||||||
relworkingdir = loader.Dir(path)
|
|
||||||
if r.ProjectDirectory == "" {
|
|
||||||
r.ProjectDirectory = filepath.Dir(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case r.ProjectDirectory == "":
|
||||||
|
relworkingdir = loader.Dir(path)
|
||||||
|
r.ProjectDirectory = filepath.Dir(path)
|
||||||
|
case !filepath.IsAbs(r.ProjectDirectory):
|
||||||
|
relworkingdir = loader.Dir(r.ProjectDirectory)
|
||||||
|
r.ProjectDirectory = filepath.Join(configDetails.WorkingDir, r.ProjectDirectory)
|
||||||
|
|
||||||
|
default:
|
||||||
|
relworkingdir = r.ProjectDirectory
|
||||||
|
|
||||||
|
}
|
||||||
for _, f := range included {
|
for _, f := range included {
|
||||||
if f == path {
|
if f == path {
|
||||||
included = append(included, path)
|
included = append(included, path)
|
||||||
|
|
|
@ -148,8 +148,11 @@ func (l localResourceLoader) Load(_ context.Context, p string) (string, error) {
|
||||||
return l.abs(p), nil
|
return l.abs(p), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l localResourceLoader) Dir(path string) string {
|
func (l localResourceLoader) Dir(originalPath string) string {
|
||||||
path = l.abs(filepath.Dir(path))
|
path := l.abs(originalPath)
|
||||||
|
if !l.isDir(path) {
|
||||||
|
path = l.abs(filepath.Dir(originalPath))
|
||||||
|
}
|
||||||
rel, err := filepath.Rel(l.WorkingDir, path)
|
rel, err := filepath.Rel(l.WorkingDir, path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return path
|
return path
|
||||||
|
@ -157,6 +160,14 @@ func (l localResourceLoader) Dir(path string) string {
|
||||||
return rel
|
return rel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (l localResourceLoader) isDir(path string) bool {
|
||||||
|
fileInfo, err := os.Stat(path)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return fileInfo.IsDir()
|
||||||
|
}
|
||||||
|
|
||||||
func (o *Options) clone() *Options {
|
func (o *Options) clone() *Options {
|
||||||
return &Options{
|
return &Options{
|
||||||
SkipValidation: o.SkipValidation,
|
SkipValidation: o.SkipValidation,
|
||||||
|
@ -317,19 +328,11 @@ func loadModelWithContext(ctx context.Context, configDetails *types.ConfigDetail
|
||||||
return nil, errors.New("No files specified")
|
return nil, errors.New("No files specified")
|
||||||
}
|
}
|
||||||
|
|
||||||
err := projectName(*configDetails, opts)
|
err := projectName(configDetails, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(milas): this should probably ALWAYS set (overriding any existing)
|
|
||||||
if _, ok := configDetails.Environment[consts.ComposeProjectName]; !ok && opts.projectName != "" {
|
|
||||||
if configDetails.Environment == nil {
|
|
||||||
configDetails.Environment = map[string]string{}
|
|
||||||
}
|
|
||||||
configDetails.Environment[consts.ComposeProjectName] = opts.projectName
|
|
||||||
}
|
|
||||||
|
|
||||||
return load(ctx, *configDetails, opts, nil)
|
return load(ctx, *configDetails, opts, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -452,7 +455,7 @@ func loadYamlModel(ctx context.Context, config types.ConfigDetails, opts *Option
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dict, err = transform.Canonical(dict)
|
dict, err = transform.Canonical(dict, opts.SkipInterpolation)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -584,10 +587,14 @@ func InvalidProjectNameErr(v string) error {
|
||||||
// projectName determines the canonical name to use for the project considering
|
// projectName determines the canonical name to use for the project considering
|
||||||
// the loader Options as well as `name` fields in Compose YAML fields (which
|
// the loader Options as well as `name` fields in Compose YAML fields (which
|
||||||
// also support interpolation).
|
// also support interpolation).
|
||||||
//
|
func projectName(details *types.ConfigDetails, opts *Options) error {
|
||||||
// TODO(milas): restructure loading so that we don't need to re-parse the YAML
|
defer func() {
|
||||||
// here, as it's both wasteful and makes this code error-prone.
|
if details.Environment == nil {
|
||||||
func projectName(details types.ConfigDetails, opts *Options) error {
|
details.Environment = map[string]string{}
|
||||||
|
}
|
||||||
|
details.Environment[consts.ComposeProjectName] = opts.projectName
|
||||||
|
}()
|
||||||
|
|
||||||
if opts.projectNameImperativelySet {
|
if opts.projectNameImperativelySet {
|
||||||
if NormalizeProjectName(opts.projectName) != opts.projectName {
|
if NormalizeProjectName(opts.projectName) != opts.projectName {
|
||||||
return InvalidProjectNameErr(opts.projectName)
|
return InvalidProjectNameErr(opts.projectName)
|
||||||
|
|
|
@ -26,18 +26,12 @@ import (
|
||||||
|
|
||||||
// Normalize compose project by moving deprecated attributes to their canonical position and injecting implicit defaults
|
// Normalize compose project by moving deprecated attributes to their canonical position and injecting implicit defaults
|
||||||
func Normalize(dict map[string]any, env types.Mapping) (map[string]any, error) {
|
func Normalize(dict map[string]any, env types.Mapping) (map[string]any, error) {
|
||||||
dict["networks"] = normalizeNetworks(dict)
|
normalizeNetworks(dict)
|
||||||
|
|
||||||
if d, ok := dict["services"]; ok {
|
if d, ok := dict["services"]; ok {
|
||||||
services := d.(map[string]any)
|
services := d.(map[string]any)
|
||||||
for name, s := range services {
|
for name, s := range services {
|
||||||
service := s.(map[string]any)
|
service := s.(map[string]any)
|
||||||
_, hasNetworks := service["networks"]
|
|
||||||
_, hasNetworkMode := service["network_mode"]
|
|
||||||
if !hasNetworks && !hasNetworkMode {
|
|
||||||
// Service without explicit network attachment are implicitly exposed on default network
|
|
||||||
service["networks"] = map[string]any{"default": nil}
|
|
||||||
}
|
|
||||||
|
|
||||||
if service["pull_policy"] == types.PullPolicyIfNotPresent {
|
if service["pull_policy"] == types.PullPolicyIfNotPresent {
|
||||||
service["pull_policy"] = types.PullPolicyMissing
|
service["pull_policy"] = types.PullPolicyMissing
|
||||||
|
@ -137,18 +131,51 @@ func Normalize(dict map[string]any, env types.Mapping) (map[string]any, error) {
|
||||||
return dict, nil
|
return dict, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func normalizeNetworks(dict map[string]any) map[string]any {
|
func normalizeNetworks(dict map[string]any) {
|
||||||
var networks map[string]any
|
var networks map[string]any
|
||||||
if n, ok := dict["networks"]; ok {
|
if n, ok := dict["networks"]; ok {
|
||||||
networks = n.(map[string]any)
|
networks = n.(map[string]any)
|
||||||
} else {
|
} else {
|
||||||
networks = map[string]any{}
|
networks = map[string]any{}
|
||||||
}
|
}
|
||||||
if _, ok := networks["default"]; !ok {
|
|
||||||
|
// implicit `default` network must be introduced only if actually used by some service
|
||||||
|
usesDefaultNetwork := false
|
||||||
|
|
||||||
|
if s, ok := dict["services"]; ok {
|
||||||
|
services := s.(map[string]any)
|
||||||
|
for name, se := range services {
|
||||||
|
service := se.(map[string]any)
|
||||||
|
if _, ok := service["network_mode"]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if n, ok := service["networks"]; !ok {
|
||||||
|
// If none explicitly declared, service is connected to default network
|
||||||
|
service["networks"] = map[string]any{"default": nil}
|
||||||
|
usesDefaultNetwork = true
|
||||||
|
} else {
|
||||||
|
net := n.(map[string]any)
|
||||||
|
if len(net) == 0 {
|
||||||
|
// networks section declared but empty (corner case)
|
||||||
|
service["networks"] = map[string]any{"default": nil}
|
||||||
|
usesDefaultNetwork = true
|
||||||
|
} else if _, ok := net["default"]; ok {
|
||||||
|
usesDefaultNetwork = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
services[name] = service
|
||||||
|
}
|
||||||
|
dict["services"] = services
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := networks["default"]; !ok && usesDefaultNetwork {
|
||||||
// If not declared explicitly, Compose model involves an implicit "default" network
|
// If not declared explicitly, Compose model involves an implicit "default" network
|
||||||
networks["default"] = nil
|
networks["default"] = nil
|
||||||
}
|
}
|
||||||
return networks
|
|
||||||
|
if len(networks) > 0 {
|
||||||
|
dict["networks"] = networks
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func resolve(a any, fn func(s string) (string, bool)) (any, bool) {
|
func resolve(a any, fn func(s string) (string, bool)) (any, bool) {
|
||||||
|
|
|
@ -19,6 +19,7 @@ package loader
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/compose-spec/compose-go/v2/tree"
|
"github.com/compose-spec/compose-go/v2/tree"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
|
@ -40,6 +41,15 @@ func (p *ResetProcessor) UnmarshalYAML(value *yaml.Node) error {
|
||||||
|
|
||||||
// resolveReset detects `!reset` tag being set on yaml nodes and record position in the yaml tree
|
// resolveReset detects `!reset` tag being set on yaml nodes and record position in the yaml tree
|
||||||
func (p *ResetProcessor) resolveReset(node *yaml.Node, path tree.Path) (*yaml.Node, error) {
|
func (p *ResetProcessor) resolveReset(node *yaml.Node, path tree.Path) (*yaml.Node, error) {
|
||||||
|
// If the path contains "<<", removing the "<<" element and merging the path
|
||||||
|
if strings.Contains(path.String(), ".<<") {
|
||||||
|
path = tree.NewPath(strings.Replace(path.String(), ".<<", "", 1))
|
||||||
|
}
|
||||||
|
// If the node is an alias, We need to process the alias field in order to consider the !override and !reset tags
|
||||||
|
if node.Kind == yaml.AliasNode {
|
||||||
|
return p.resolveReset(node.Alias, path)
|
||||||
|
}
|
||||||
|
|
||||||
if node.Tag == "!reset" {
|
if node.Tag == "!reset" {
|
||||||
p.paths = append(p.paths, path)
|
p.paths = append(p.paths, path)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
|
|
@ -28,7 +28,6 @@ import (
|
||||||
|
|
||||||
// checkConsistency validate a compose model is consistent
|
// checkConsistency validate a compose model is consistent
|
||||||
func checkConsistency(project *types.Project) error {
|
func checkConsistency(project *types.Project) error {
|
||||||
containerNames := map[string]string{}
|
|
||||||
for _, s := range project.Services {
|
for _, s := range project.Services {
|
||||||
if s.Build == nil && s.Image == "" {
|
if s.Build == nil && s.Image == "" {
|
||||||
return fmt.Errorf("service %q has neither an image nor a build context specified: %w", s.Name, errdefs.ErrInvalid)
|
return fmt.Errorf("service %q has neither an image nor a build context specified: %w", s.Name, errdefs.ErrInvalid)
|
||||||
|
@ -145,13 +144,6 @@ func checkConsistency(project *types.Project) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.ContainerName != "" {
|
|
||||||
if existing, ok := containerNames[s.ContainerName]; ok {
|
|
||||||
return fmt.Errorf(`"services.%s": container name "%s" is already in use by "services.%s": %w`, s.Name, s.ContainerName, existing, errdefs.ErrInvalid)
|
|
||||||
}
|
|
||||||
containerNames[s.ContainerName] = s.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.GetScale() > 1 && s.ContainerName != "" {
|
if s.GetScale() > 1 && s.ContainerName != "" {
|
||||||
attr := "scale"
|
attr := "scale"
|
||||||
if s.Scale == nil {
|
if s.Scale == nil {
|
||||||
|
|
|
@ -42,6 +42,7 @@ func init() {
|
||||||
unique["services.*.build.labels"] = keyValueIndexer
|
unique["services.*.build.labels"] = keyValueIndexer
|
||||||
unique["services.*.cap_add"] = keyValueIndexer
|
unique["services.*.cap_add"] = keyValueIndexer
|
||||||
unique["services.*.cap_drop"] = keyValueIndexer
|
unique["services.*.cap_drop"] = keyValueIndexer
|
||||||
|
unique["services.*.devices"] = volumeIndexer
|
||||||
unique["services.*.configs"] = mountIndexer("")
|
unique["services.*.configs"] = mountIndexer("")
|
||||||
unique["services.*.deploy.labels"] = keyValueIndexer
|
unique["services.*.deploy.labels"] = keyValueIndexer
|
||||||
unique["services.*.dns"] = keyValueIndexer
|
unique["services.*.dns"] = keyValueIndexer
|
||||||
|
@ -197,12 +198,15 @@ func portIndexer(y any, p tree.Path) (string, error) {
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func envFileIndexer(y any, _ tree.Path) (string, error) {
|
func envFileIndexer(y any, p tree.Path) (string, error) {
|
||||||
switch value := y.(type) {
|
switch value := y.(type) {
|
||||||
case string:
|
case string:
|
||||||
return value, nil
|
return value, nil
|
||||||
case map[string]any:
|
case map[string]any:
|
||||||
return value["path"].(string), nil
|
if pathValue, ok := value["path"]; ok {
|
||||||
|
return pathValue.(string), nil
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("environment path attribut %s is missing", p)
|
||||||
}
|
}
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -104,6 +104,7 @@
|
||||||
"context": {"type": "string"},
|
"context": {"type": "string"},
|
||||||
"dockerfile": {"type": "string"},
|
"dockerfile": {"type": "string"},
|
||||||
"dockerfile_inline": {"type": "string"},
|
"dockerfile_inline": {"type": "string"},
|
||||||
|
"entitlements": {"type": "array", "items": {"type": "string"}},
|
||||||
"args": {"$ref": "#/definitions/list_or_dict"},
|
"args": {"$ref": "#/definitions/list_or_dict"},
|
||||||
"ssh": {"$ref": "#/definitions/list_or_dict"},
|
"ssh": {"$ref": "#/definitions/list_or_dict"},
|
||||||
"labels": {"$ref": "#/definitions/list_or_dict"},
|
"labels": {"$ref": "#/definitions/list_or_dict"},
|
||||||
|
@ -295,6 +296,12 @@
|
||||||
"ipv6_address": {"type": "string"},
|
"ipv6_address": {"type": "string"},
|
||||||
"link_local_ips": {"$ref": "#/definitions/list_of_strings"},
|
"link_local_ips": {"$ref": "#/definitions/list_of_strings"},
|
||||||
"mac_address": {"type": "string"},
|
"mac_address": {"type": "string"},
|
||||||
|
"driver_opts": {
|
||||||
|
"type": "object",
|
||||||
|
"patternProperties": {
|
||||||
|
"^.+$": {"type": ["string", "number"]}
|
||||||
|
}
|
||||||
|
},
|
||||||
"priority": {"type": "number"}
|
"priority": {"type": "number"}
|
||||||
},
|
},
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
|
|
|
@ -271,102 +271,6 @@ func Substitute(template string, mapping Mapping) (string, error) {
|
||||||
return SubstituteWith(template, mapping, DefaultPattern)
|
return SubstituteWith(template, mapping, DefaultPattern)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExtractVariables returns a map of all the variables defined in the specified
|
|
||||||
// composefile (dict representation) and their default value if any.
|
|
||||||
func ExtractVariables(configDict map[string]interface{}, pattern *regexp.Regexp) map[string]Variable {
|
|
||||||
if pattern == nil {
|
|
||||||
pattern = DefaultPattern
|
|
||||||
}
|
|
||||||
return recurseExtract(configDict, pattern)
|
|
||||||
}
|
|
||||||
|
|
||||||
func recurseExtract(value interface{}, pattern *regexp.Regexp) map[string]Variable {
|
|
||||||
m := map[string]Variable{}
|
|
||||||
|
|
||||||
switch value := value.(type) {
|
|
||||||
case string:
|
|
||||||
if values, is := extractVariable(value, pattern); is {
|
|
||||||
for _, v := range values {
|
|
||||||
m[v.Name] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case map[string]interface{}:
|
|
||||||
for _, elem := range value {
|
|
||||||
submap := recurseExtract(elem, pattern)
|
|
||||||
for key, value := range submap {
|
|
||||||
m[key] = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case []interface{}:
|
|
||||||
for _, elem := range value {
|
|
||||||
if values, is := extractVariable(elem, pattern); is {
|
|
||||||
for _, v := range values {
|
|
||||||
m[v.Name] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
type Variable struct {
|
|
||||||
Name string
|
|
||||||
DefaultValue string
|
|
||||||
PresenceValue string
|
|
||||||
Required bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func extractVariable(value interface{}, pattern *regexp.Regexp) ([]Variable, bool) {
|
|
||||||
sValue, ok := value.(string)
|
|
||||||
if !ok {
|
|
||||||
return []Variable{}, false
|
|
||||||
}
|
|
||||||
matches := pattern.FindAllStringSubmatch(sValue, -1)
|
|
||||||
if len(matches) == 0 {
|
|
||||||
return []Variable{}, false
|
|
||||||
}
|
|
||||||
values := []Variable{}
|
|
||||||
for _, match := range matches {
|
|
||||||
groups := matchGroups(match, pattern)
|
|
||||||
if escaped := groups[groupEscaped]; escaped != "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
val := groups[groupNamed]
|
|
||||||
if val == "" {
|
|
||||||
val = groups[groupBraced]
|
|
||||||
}
|
|
||||||
name := val
|
|
||||||
var defaultValue string
|
|
||||||
var presenceValue string
|
|
||||||
var required bool
|
|
||||||
switch {
|
|
||||||
case strings.Contains(val, ":?"):
|
|
||||||
name, _ = partition(val, ":?")
|
|
||||||
required = true
|
|
||||||
case strings.Contains(val, "?"):
|
|
||||||
name, _ = partition(val, "?")
|
|
||||||
required = true
|
|
||||||
case strings.Contains(val, ":-"):
|
|
||||||
name, defaultValue = partition(val, ":-")
|
|
||||||
case strings.Contains(val, "-"):
|
|
||||||
name, defaultValue = partition(val, "-")
|
|
||||||
case strings.Contains(val, ":+"):
|
|
||||||
name, presenceValue = partition(val, ":+")
|
|
||||||
case strings.Contains(val, "+"):
|
|
||||||
name, presenceValue = partition(val, "+")
|
|
||||||
}
|
|
||||||
values = append(values, Variable{
|
|
||||||
Name: name,
|
|
||||||
DefaultValue: defaultValue,
|
|
||||||
PresenceValue: presenceValue,
|
|
||||||
Required: required,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return values, len(values) > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Soft default (fall back if unset or empty)
|
// Soft default (fall back if unset or empty)
|
||||||
func defaultWhenEmptyOrUnset(substitution string, mapping Mapping) (string, bool, error) {
|
func defaultWhenEmptyOrUnset(substitution string, mapping Mapping) (string, bool, error) {
|
||||||
return withDefaultWhenAbsence(substitution, mapping, true)
|
return withDefaultWhenAbsence(substitution, mapping, true)
|
||||||
|
|
155
vendor/github.com/compose-spec/compose-go/v2/template/variables.go
generated
vendored
Normal file
155
vendor/github.com/compose-spec/compose-go/v2/template/variables.go
generated
vendored
Normal file
|
@ -0,0 +1,155 @@
|
||||||
|
/*
|
||||||
|
Copyright 2020 The Compose Specification Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package template
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Variable struct {
|
||||||
|
Name string
|
||||||
|
DefaultValue string
|
||||||
|
PresenceValue string
|
||||||
|
Required bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtractVariables returns a map of all the variables defined in the specified
|
||||||
|
// compose file (dict representation) and their default value if any.
|
||||||
|
func ExtractVariables(configDict map[string]interface{}, pattern *regexp.Regexp) map[string]Variable {
|
||||||
|
if pattern == nil {
|
||||||
|
pattern = DefaultPattern
|
||||||
|
}
|
||||||
|
return recurseExtract(configDict, pattern)
|
||||||
|
}
|
||||||
|
|
||||||
|
func recurseExtract(value interface{}, pattern *regexp.Regexp) map[string]Variable {
|
||||||
|
m := map[string]Variable{}
|
||||||
|
|
||||||
|
switch value := value.(type) {
|
||||||
|
case string:
|
||||||
|
if values, is := extractVariable(value, pattern); is {
|
||||||
|
for _, v := range values {
|
||||||
|
m[v.Name] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case map[string]interface{}:
|
||||||
|
for _, elem := range value {
|
||||||
|
submap := recurseExtract(elem, pattern)
|
||||||
|
for key, value := range submap {
|
||||||
|
m[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case []interface{}:
|
||||||
|
for _, elem := range value {
|
||||||
|
if values, is := extractVariable(elem, pattern); is {
|
||||||
|
for _, v := range values {
|
||||||
|
m[v.Name] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractVariable(value interface{}, pattern *regexp.Regexp) ([]Variable, bool) {
|
||||||
|
sValue, ok := value.(string)
|
||||||
|
if !ok {
|
||||||
|
return []Variable{}, false
|
||||||
|
}
|
||||||
|
matches := pattern.FindAllStringSubmatch(sValue, -1)
|
||||||
|
if len(matches) == 0 {
|
||||||
|
return []Variable{}, false
|
||||||
|
}
|
||||||
|
values := []Variable{}
|
||||||
|
for _, match := range matches {
|
||||||
|
groups := matchGroups(match, pattern)
|
||||||
|
if escaped := groups[groupEscaped]; escaped != "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
val := groups[groupNamed]
|
||||||
|
if val == "" {
|
||||||
|
val = groups[groupBraced]
|
||||||
|
s := match[0]
|
||||||
|
i := getFirstBraceClosingIndex(s)
|
||||||
|
if i > 0 {
|
||||||
|
val = s[2:i]
|
||||||
|
if len(s) > i {
|
||||||
|
if v, b := extractVariable(s[i+1:], pattern); b {
|
||||||
|
values = append(values, v...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
name := val
|
||||||
|
var defaultValue string
|
||||||
|
var presenceValue string
|
||||||
|
var required bool
|
||||||
|
i := strings.IndexFunc(val, func(r rune) bool {
|
||||||
|
if r >= 'a' && r <= 'z' {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if r >= 'A' && r <= 'Z' {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if r == '_' {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
if i > 0 {
|
||||||
|
name = val[:i]
|
||||||
|
rest := val[i:]
|
||||||
|
switch {
|
||||||
|
case strings.HasPrefix(rest, ":?"):
|
||||||
|
required = true
|
||||||
|
case strings.HasPrefix(rest, "?"):
|
||||||
|
required = true
|
||||||
|
case strings.HasPrefix(rest, ":-"):
|
||||||
|
defaultValue = rest[2:]
|
||||||
|
case strings.HasPrefix(rest, "-"):
|
||||||
|
defaultValue = rest[1:]
|
||||||
|
case strings.HasPrefix(rest, ":+"):
|
||||||
|
presenceValue = rest[2:]
|
||||||
|
case strings.HasPrefix(rest, "+"):
|
||||||
|
presenceValue = rest[1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
values = append(values, Variable{
|
||||||
|
Name: name,
|
||||||
|
DefaultValue: defaultValue,
|
||||||
|
PresenceValue: presenceValue,
|
||||||
|
Required: required,
|
||||||
|
})
|
||||||
|
|
||||||
|
if defaultValue != "" {
|
||||||
|
if v, b := extractVariable(defaultValue, pattern); b {
|
||||||
|
values = append(values, v...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if presenceValue != "" {
|
||||||
|
if v, b := extractVariable(presenceValue, pattern); b {
|
||||||
|
values = append(values, v...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return values, len(values) > 0
|
||||||
|
}
|
|
@ -22,10 +22,10 @@ import (
|
||||||
"github.com/compose-spec/compose-go/v2/tree"
|
"github.com/compose-spec/compose-go/v2/tree"
|
||||||
)
|
)
|
||||||
|
|
||||||
func transformBuild(data any, p tree.Path) (any, error) {
|
func transformBuild(data any, p tree.Path, ignoreParseError bool) (any, error) {
|
||||||
switch v := data.(type) {
|
switch v := data.(type) {
|
||||||
case map[string]any:
|
case map[string]any:
|
||||||
return transformMapping(v, p)
|
return transformMapping(v, p, ignoreParseError)
|
||||||
case string:
|
case string:
|
||||||
return map[string]any{
|
return map[string]any{
|
||||||
"context": v,
|
"context": v,
|
||||||
|
@ -35,7 +35,7 @@ func transformBuild(data any, p tree.Path) (any, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func defaultBuildContext(data any, _ tree.Path) (any, error) {
|
func defaultBuildContext(data any, _ tree.Path, _ bool) (any, error) {
|
||||||
switch v := data.(type) {
|
switch v := data.(type) {
|
||||||
case map[string]any:
|
case map[string]any:
|
||||||
if _, ok := v["context"]; !ok {
|
if _, ok := v["context"]; !ok {
|
||||||
|
|
|
@ -20,7 +20,7 @@ import (
|
||||||
"github.com/compose-spec/compose-go/v2/tree"
|
"github.com/compose-spec/compose-go/v2/tree"
|
||||||
)
|
)
|
||||||
|
|
||||||
type transformFunc func(data any, p tree.Path) (any, error)
|
type transformFunc func(data any, p tree.Path, ignoreParseError bool) (any, error)
|
||||||
|
|
||||||
var transformers = map[tree.Path]transformFunc{}
|
var transformers = map[tree.Path]transformFunc{}
|
||||||
|
|
||||||
|
@ -48,18 +48,18 @@ func init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Canonical transforms a compose model into canonical syntax
|
// Canonical transforms a compose model into canonical syntax
|
||||||
func Canonical(yaml map[string]any) (map[string]any, error) {
|
func Canonical(yaml map[string]any, ignoreParseError bool) (map[string]any, error) {
|
||||||
canonical, err := transform(yaml, tree.NewPath())
|
canonical, err := transform(yaml, tree.NewPath(), ignoreParseError)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return canonical.(map[string]any), nil
|
return canonical.(map[string]any), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func transform(data any, p tree.Path) (any, error) {
|
func transform(data any, p tree.Path, ignoreParseError bool) (any, error) {
|
||||||
for pattern, transformer := range transformers {
|
for pattern, transformer := range transformers {
|
||||||
if p.Matches(pattern) {
|
if p.Matches(pattern) {
|
||||||
t, err := transformer(data, p)
|
t, err := transformer(data, p, ignoreParseError)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -68,13 +68,13 @@ func transform(data any, p tree.Path) (any, error) {
|
||||||
}
|
}
|
||||||
switch v := data.(type) {
|
switch v := data.(type) {
|
||||||
case map[string]any:
|
case map[string]any:
|
||||||
a, err := transformMapping(v, p)
|
a, err := transformMapping(v, p, ignoreParseError)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return a, err
|
return a, err
|
||||||
}
|
}
|
||||||
return v, nil
|
return v, nil
|
||||||
case []any:
|
case []any:
|
||||||
a, err := transformSequence(v, p)
|
a, err := transformSequence(v, p, ignoreParseError)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return a, err
|
return a, err
|
||||||
}
|
}
|
||||||
|
@ -84,9 +84,9 @@ func transform(data any, p tree.Path) (any, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func transformSequence(v []any, p tree.Path) ([]any, error) {
|
func transformSequence(v []any, p tree.Path, ignoreParseError bool) ([]any, error) {
|
||||||
for i, e := range v {
|
for i, e := range v {
|
||||||
t, err := transform(e, p.Next("[]"))
|
t, err := transform(e, p.Next("[]"), ignoreParseError)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -95,9 +95,9 @@ func transformSequence(v []any, p tree.Path) ([]any, error) {
|
||||||
return v, nil
|
return v, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func transformMapping(v map[string]any, p tree.Path) (map[string]any, error) {
|
func transformMapping(v map[string]any, p tree.Path, ignoreParseError bool) (map[string]any, error) {
|
||||||
for k, e := range v {
|
for k, e := range v {
|
||||||
t, err := transform(e, p.Next(k))
|
t, err := transform(e, p.Next(k), ignoreParseError)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,7 @@ func SetDefaultValues(yaml map[string]any) (map[string]any, error) {
|
||||||
func setDefaults(data any, p tree.Path) (any, error) {
|
func setDefaults(data any, p tree.Path) (any, error) {
|
||||||
for pattern, transformer := range defaultValues {
|
for pattern, transformer := range defaultValues {
|
||||||
if p.Matches(pattern) {
|
if p.Matches(pattern) {
|
||||||
t, err := transformer(data, p)
|
t, err := transformer(data, p, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ import (
|
||||||
"github.com/compose-spec/compose-go/v2/tree"
|
"github.com/compose-spec/compose-go/v2/tree"
|
||||||
)
|
)
|
||||||
|
|
||||||
func transformDependsOn(data any, p tree.Path) (any, error) {
|
func transformDependsOn(data any, p tree.Path, _ bool) (any, error) {
|
||||||
switch v := data.(type) {
|
switch v := data.(type) {
|
||||||
case map[string]any:
|
case map[string]any:
|
||||||
for i, e := range v {
|
for i, e := range v {
|
||||||
|
|
|
@ -22,7 +22,7 @@ import (
|
||||||
"github.com/compose-spec/compose-go/v2/tree"
|
"github.com/compose-spec/compose-go/v2/tree"
|
||||||
)
|
)
|
||||||
|
|
||||||
func transformEnvFile(data any, p tree.Path) (any, error) {
|
func transformEnvFile(data any, p tree.Path, _ bool) (any, error) {
|
||||||
switch v := data.(type) {
|
switch v := data.(type) {
|
||||||
case string:
|
case string:
|
||||||
return []any{
|
return []any{
|
||||||
|
|
|
@ -22,10 +22,10 @@ import (
|
||||||
"github.com/compose-spec/compose-go/v2/tree"
|
"github.com/compose-spec/compose-go/v2/tree"
|
||||||
)
|
)
|
||||||
|
|
||||||
func transformExtends(data any, p tree.Path) (any, error) {
|
func transformExtends(data any, p tree.Path, ignoreParseError bool) (any, error) {
|
||||||
switch v := data.(type) {
|
switch v := data.(type) {
|
||||||
case map[string]any:
|
case map[string]any:
|
||||||
return transformMapping(v, p)
|
return transformMapping(v, p, ignoreParseError)
|
||||||
case string:
|
case string:
|
||||||
return map[string]any{
|
return map[string]any{
|
||||||
"service": v,
|
"service": v,
|
||||||
|
|
|
@ -23,11 +23,11 @@ import (
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
func transformMaybeExternal(data any, p tree.Path) (any, error) {
|
func transformMaybeExternal(data any, p tree.Path, ignoreParseError bool) (any, error) {
|
||||||
if data == nil {
|
if data == nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
resource, err := transformMapping(data.(map[string]any), p)
|
resource, err := transformMapping(data.(map[string]any), p, ignoreParseError)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ import (
|
||||||
"github.com/compose-spec/compose-go/v2/tree"
|
"github.com/compose-spec/compose-go/v2/tree"
|
||||||
)
|
)
|
||||||
|
|
||||||
func transformInclude(data any, p tree.Path) (any, error) {
|
func transformInclude(data any, p tree.Path, _ bool) (any, error) {
|
||||||
switch v := data.(type) {
|
switch v := data.(type) {
|
||||||
case map[string]any:
|
case map[string]any:
|
||||||
return v, nil
|
return v, nil
|
||||||
|
|
|
@ -23,7 +23,7 @@ import (
|
||||||
"github.com/compose-spec/compose-go/v2/tree"
|
"github.com/compose-spec/compose-go/v2/tree"
|
||||||
)
|
)
|
||||||
|
|
||||||
func transformKeyValue(data any, p tree.Path) (any, error) {
|
func transformKeyValue(data any, p tree.Path, ignoreParseError bool) (any, error) {
|
||||||
switch v := data.(type) {
|
switch v := data.(type) {
|
||||||
case map[string]any:
|
case map[string]any:
|
||||||
return v, nil
|
return v, nil
|
||||||
|
@ -32,6 +32,9 @@ func transformKeyValue(data any, p tree.Path) (any, error) {
|
||||||
for _, e := range v {
|
for _, e := range v {
|
||||||
before, after, found := strings.Cut(e.(string), "=")
|
before, after, found := strings.Cut(e.(string), "=")
|
||||||
if !found {
|
if !found {
|
||||||
|
if ignoreParseError {
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
return nil, fmt.Errorf("%s: invalid value %s, expected key=value", p, e)
|
return nil, fmt.Errorf("%s: invalid value %s, expected key=value", p, e)
|
||||||
}
|
}
|
||||||
mapping[before] = after
|
mapping[before] = after
|
||||||
|
|
|
@ -24,7 +24,7 @@ import (
|
||||||
"github.com/mitchellh/mapstructure"
|
"github.com/mitchellh/mapstructure"
|
||||||
)
|
)
|
||||||
|
|
||||||
func transformPorts(data any, p tree.Path) (any, error) {
|
func transformPorts(data any, p tree.Path, ignoreParseError bool) (any, error) {
|
||||||
switch entries := data.(type) {
|
switch entries := data.(type) {
|
||||||
case []any:
|
case []any:
|
||||||
// We process the list instead of individual items here.
|
// We process the list instead of individual items here.
|
||||||
|
@ -48,7 +48,10 @@ func transformPorts(data any, p tree.Path) (any, error) {
|
||||||
case string:
|
case string:
|
||||||
parsed, err := types.ParsePortConfig(value)
|
parsed, err := types.ParsePortConfig(value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return data, nil
|
if ignoreParseError {
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -22,7 +22,7 @@ import (
|
||||||
"github.com/compose-spec/compose-go/v2/tree"
|
"github.com/compose-spec/compose-go/v2/tree"
|
||||||
)
|
)
|
||||||
|
|
||||||
func transformFileMount(data any, p tree.Path) (any, error) {
|
func transformFileMount(data any, p tree.Path, _ bool) (any, error) {
|
||||||
switch v := data.(type) {
|
switch v := data.(type) {
|
||||||
case map[string]any:
|
case map[string]any:
|
||||||
return data, nil
|
return data, nil
|
||||||
|
@ -35,7 +35,7 @@ func transformFileMount(data any, p tree.Path) (any, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func defaultSecretMount(data any, p tree.Path) (any, error) {
|
func defaultSecretMount(data any, p tree.Path, _ bool) (any, error) {
|
||||||
switch v := data.(type) {
|
switch v := data.(type) {
|
||||||
case map[string]any:
|
case map[string]any:
|
||||||
source := v["source"]
|
source := v["source"]
|
||||||
|
|
|
@ -20,16 +20,16 @@ import (
|
||||||
"github.com/compose-spec/compose-go/v2/tree"
|
"github.com/compose-spec/compose-go/v2/tree"
|
||||||
)
|
)
|
||||||
|
|
||||||
func transformService(data any, p tree.Path) (any, error) {
|
func transformService(data any, p tree.Path, ignoreParseError bool) (any, error) {
|
||||||
switch value := data.(type) {
|
switch value := data.(type) {
|
||||||
case map[string]any:
|
case map[string]any:
|
||||||
return transformMapping(value, p)
|
return transformMapping(value, p, ignoreParseError)
|
||||||
default:
|
default:
|
||||||
return value, nil
|
return value, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func transformServiceNetworks(data any, _ tree.Path) (any, error) {
|
func transformServiceNetworks(data any, _ tree.Path, _ bool) (any, error) {
|
||||||
if slice, ok := data.([]any); ok {
|
if slice, ok := data.([]any); ok {
|
||||||
networks := make(map[string]any, len(slice))
|
networks := make(map[string]any, len(slice))
|
||||||
for _, net := range slice {
|
for _, net := range slice {
|
||||||
|
|
|
@ -23,7 +23,7 @@ import (
|
||||||
"github.com/compose-spec/compose-go/v2/tree"
|
"github.com/compose-spec/compose-go/v2/tree"
|
||||||
)
|
)
|
||||||
|
|
||||||
func transformSSH(data any, p tree.Path) (any, error) {
|
func transformSSH(data any, p tree.Path, _ bool) (any, error) {
|
||||||
switch v := data.(type) {
|
switch v := data.(type) {
|
||||||
case map[string]any:
|
case map[string]any:
|
||||||
return v, nil
|
return v, nil
|
||||||
|
|
|
@ -22,7 +22,7 @@ import (
|
||||||
"github.com/compose-spec/compose-go/v2/tree"
|
"github.com/compose-spec/compose-go/v2/tree"
|
||||||
)
|
)
|
||||||
|
|
||||||
func transformUlimits(data any, p tree.Path) (any, error) {
|
func transformUlimits(data any, p tree.Path, _ bool) (any, error) {
|
||||||
switch v := data.(type) {
|
switch v := data.(type) {
|
||||||
case map[string]any:
|
case map[string]any:
|
||||||
return v, nil
|
return v, nil
|
||||||
|
|
|
@ -24,13 +24,16 @@ import (
|
||||||
"github.com/compose-spec/compose-go/v2/tree"
|
"github.com/compose-spec/compose-go/v2/tree"
|
||||||
)
|
)
|
||||||
|
|
||||||
func transformVolumeMount(data any, p tree.Path) (any, error) {
|
func transformVolumeMount(data any, p tree.Path, ignoreParseError bool) (any, error) {
|
||||||
switch v := data.(type) {
|
switch v := data.(type) {
|
||||||
case map[string]any:
|
case map[string]any:
|
||||||
return v, nil
|
return v, nil
|
||||||
case string:
|
case string:
|
||||||
volume, err := format.ParseVolume(v) // TODO(ndeloof) ParseVolume should not rely on types and return map[string]
|
volume, err := format.ParseVolume(v) // TODO(ndeloof) ParseVolume should not rely on types and return map[string]
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if ignoreParseError {
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
volume.Target = cleanTarget(volume.Target)
|
volume.Target = cleanTarget(volume.Target)
|
||||||
|
|
|
@ -659,12 +659,12 @@ func (p *Project) WithServicesTransform(fn func(name string, s ServiceConfig) (S
|
||||||
name string
|
name string
|
||||||
service ServiceConfig
|
service ServiceConfig
|
||||||
}
|
}
|
||||||
resultCh := make(chan result)
|
expect := len(p.Services)
|
||||||
|
resultCh := make(chan result, expect)
|
||||||
newProject := p.deepCopy()
|
newProject := p.deepCopy()
|
||||||
|
|
||||||
eg, ctx := errgroup.WithContext(context.Background())
|
eg, ctx := errgroup.WithContext(context.Background())
|
||||||
eg.Go(func() error {
|
eg.Go(func() error {
|
||||||
expect := len(newProject.Services)
|
|
||||||
s := Services{}
|
s := Services{}
|
||||||
for expect > 0 {
|
for expect > 0 {
|
||||||
select {
|
select {
|
||||||
|
@ -696,3 +696,17 @@ func (p *Project) WithServicesTransform(fn func(name string, s ServiceConfig) (S
|
||||||
}
|
}
|
||||||
return newProject, eg.Wait()
|
return newProject, eg.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CheckContainerNameUnicity validate project doesn't have services declaring the same container_name
|
||||||
|
func (p *Project) CheckContainerNameUnicity() error {
|
||||||
|
names := utils.Set[string]{}
|
||||||
|
for name, s := range p.Services {
|
||||||
|
if s.ContainerName != "" {
|
||||||
|
if existing, ok := names[s.ContainerName]; ok {
|
||||||
|
return fmt.Errorf(`services.%s: container name %q is already in use by service %s"`, name, s.ContainerName, existing)
|
||||||
|
}
|
||||||
|
names.Add(s.ContainerName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -28,7 +28,11 @@ func (l *StringList) DecodeMapstructure(value interface{}) error {
|
||||||
case []interface{}:
|
case []interface{}:
|
||||||
list := make([]string, len(v))
|
list := make([]string, len(v))
|
||||||
for i, e := range v {
|
for i, e := range v {
|
||||||
list[i] = e.(string)
|
val, ok := e.(string)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("invalid type %T for string list", value)
|
||||||
|
}
|
||||||
|
list[i] = val
|
||||||
}
|
}
|
||||||
*l = list
|
*l = list
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -266,6 +266,7 @@ type BuildConfig struct {
|
||||||
Context string `yaml:"context,omitempty" json:"context,omitempty"`
|
Context string `yaml:"context,omitempty" json:"context,omitempty"`
|
||||||
Dockerfile string `yaml:"dockerfile,omitempty" json:"dockerfile,omitempty"`
|
Dockerfile string `yaml:"dockerfile,omitempty" json:"dockerfile,omitempty"`
|
||||||
DockerfileInline string `yaml:"dockerfile_inline,omitempty" json:"dockerfile_inline,omitempty"`
|
DockerfileInline string `yaml:"dockerfile_inline,omitempty" json:"dockerfile_inline,omitempty"`
|
||||||
|
Entitlements []string `yaml:"entitlements,omitempty" json:"entitlements,omitempty"`
|
||||||
Args MappingWithEquals `yaml:"args,omitempty" json:"args,omitempty"`
|
Args MappingWithEquals `yaml:"args,omitempty" json:"args,omitempty"`
|
||||||
SSH SSHConfig `yaml:"ssh,omitempty" json:"ssh,omitempty"`
|
SSH SSHConfig `yaml:"ssh,omitempty" json:"ssh,omitempty"`
|
||||||
Labels Labels `yaml:"labels,omitempty" json:"labels,omitempty"`
|
Labels Labels `yaml:"labels,omitempty" json:"labels,omitempty"`
|
||||||
|
@ -452,6 +453,7 @@ type ServiceNetworkConfig struct {
|
||||||
Ipv6Address string `yaml:"ipv6_address,omitempty" json:"ipv6_address,omitempty"`
|
Ipv6Address string `yaml:"ipv6_address,omitempty" json:"ipv6_address,omitempty"`
|
||||||
LinkLocalIPs []string `yaml:"link_local_ips,omitempty" json:"link_local_ips,omitempty"`
|
LinkLocalIPs []string `yaml:"link_local_ips,omitempty" json:"link_local_ips,omitempty"`
|
||||||
MacAddress string `yaml:"mac_address,omitempty" json:"mac_address,omitempty"`
|
MacAddress string `yaml:"mac_address,omitempty" json:"mac_address,omitempty"`
|
||||||
|
DriverOpts Options `yaml:"driver_opts,omitempty" json:"driver_opts,omitempty"`
|
||||||
|
|
||||||
Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"`
|
Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -131,7 +131,7 @@ github.com/cenkalti/backoff/v4
|
||||||
# github.com/cespare/xxhash/v2 v2.2.0
|
# github.com/cespare/xxhash/v2 v2.2.0
|
||||||
## explicit; go 1.11
|
## explicit; go 1.11
|
||||||
github.com/cespare/xxhash/v2
|
github.com/cespare/xxhash/v2
|
||||||
# github.com/compose-spec/compose-go/v2 v2.0.2
|
# github.com/compose-spec/compose-go/v2 v2.1.1
|
||||||
## explicit; go 1.21
|
## explicit; go 1.21
|
||||||
github.com/compose-spec/compose-go/v2/cli
|
github.com/compose-spec/compose-go/v2/cli
|
||||||
github.com/compose-spec/compose-go/v2/consts
|
github.com/compose-spec/compose-go/v2/consts
|
||||||
|
|
Loading…
Reference in New Issue