From e3e16ad08823f7ae9494ac5d296d9a488ec45dd3 Mon Sep 17 00:00:00 2001 From: Guillaume Lours <705411+glours@users.noreply.github.com> Date: Tue, 23 Apr 2024 10:28:28 +0200 Subject: [PATCH 1/2] bumpo compose-go to v2.1.0 Signed-off-by: Guillaume Lours <705411+glours@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 +- .../compose-spec/compose-go/v2/cli/options.go | 3 ++ .../compose-spec/compose-go/v2/graph/cycle.go | 4 +- .../compose-go/v2/loader/include.go | 23 ++++++--- .../compose-go/v2/loader/loader.go | 17 +++++-- .../compose-go/v2/loader/normalize.go | 47 +++++++++++++++---- .../compose-go/v2/loader/reset.go | 10 ++++ .../compose-go/v2/loader/validate.go | 8 ---- .../compose-go/v2/override/uncity.go | 1 + .../compose-go/v2/schema/compose-spec.json | 1 + .../compose-go/v2/transform/build.go | 6 +-- .../compose-go/v2/transform/canonical.go | 22 ++++----- .../compose-go/v2/transform/defaults.go | 2 +- .../compose-go/v2/transform/dependson.go | 2 +- .../compose-go/v2/transform/envfile.go | 2 +- .../compose-go/v2/transform/extends.go | 4 +- .../compose-go/v2/transform/external.go | 4 +- .../compose-go/v2/transform/include.go | 2 +- .../compose-go/v2/transform/mapping.go | 5 +- .../compose-go/v2/transform/ports.go | 7 ++- .../compose-go/v2/transform/secrets.go | 4 +- .../compose-go/v2/transform/services.go | 6 +-- .../compose-go/v2/transform/ssh.go | 2 +- .../compose-go/v2/transform/ulimits.go | 2 +- .../compose-go/v2/transform/volume.go | 5 +- .../compose-go/v2/types/project.go | 14 ++++++ .../compose-spec/compose-go/v2/types/types.go | 1 + vendor/modules.txt | 2 +- 29 files changed, 146 insertions(+), 66 deletions(-) diff --git a/go.mod b/go.mod index 135877fa..a418ebdc 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/Masterminds/semver/v3 v3.2.1 github.com/Microsoft/go-winio v0.6.1 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.0 github.com/containerd/console v1.0.4 github.com/containerd/containerd v1.7.15 github.com/containerd/continuity v0.4.3 diff --git a/go.sum b/go.sum index 3da905bd..27844b31 100644 --- a/go.sum +++ b/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/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/compose-spec/compose-go/v2 v2.0.2 h1:zhXMV7VWI00Su0LdKt8/sxeXxcjLWhmGmpEyw+ZYznI= -github.com/compose-spec/compose-go/v2 v2.0.2/go.mod h1:bEPizBkIojlQ20pi2vNluBa58tevvj0Y18oUSHPyfdc= +github.com/compose-spec/compose-go/v2 v2.1.0 h1:qdW2qISQlCQG8v1O2TChcdxgAWTUGgUX/CPSO+ES9+E= +github.com/compose-spec/compose-go/v2 v2.1.0/go.mod h1:bEPizBkIojlQ20pi2vNluBa58tevvj0Y18oUSHPyfdc= 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/console v1.0.4 h1:F2g4+oChYvBTsASRTz8NP6iIAi97J3TtSAsLbIFn4ro= diff --git a/vendor/github.com/compose-spec/compose-go/v2/cli/options.go b/vendor/github.com/compose-spec/compose-go/v2/cli/options.go index 2c5ee1a3..9fd346cb 100644 --- a/vendor/github.com/compose-spec/compose-go/v2/cli/options.go +++ b/vendor/github.com/compose-spec/compose-go/v2/cli/options.go @@ -283,6 +283,9 @@ func WithEnvFiles(file ...string) ProjectOptionsFn { if os.IsNotExist(err) { return nil } + if err != nil { + return err + } if !s.IsDir() { o.EnvFiles = []string{defaultDotEnv} } diff --git a/vendor/github.com/compose-spec/compose-go/v2/graph/cycle.go b/vendor/github.com/compose-spec/compose-go/v2/graph/cycle.go index d96785cf..60c37cef 100644 --- a/vendor/github.com/compose-spec/compose-go/v2/graph/cycle.go +++ b/vendor/github.com/compose-spec/compose-go/v2/graph/cycle.go @@ -50,8 +50,8 @@ func (g *graph[T]) checkCycle() error { func searchCycle[T any](path []string, v *vertex[T]) error { names := utils.MapKeys(v.children) for _, name := range names { - if i := slices.Index(path, name); i > 0 { - return fmt.Errorf("dependency cycle detected: %s", strings.Join(path[i:], " -> ")) + if i := slices.Index(path, name); i >= 0 { + return fmt.Errorf("dependency cycle detected: %s -> %s", strings.Join(path[i:], " -> "), name) } ch := v.children[name] err := searchCycle(append(path, name), ch) diff --git a/vendor/github.com/compose-spec/compose-go/v2/loader/include.go b/vendor/github.com/compose-spec/compose-go/v2/loader/include.go index e7631415..d40b24ec 100644 --- a/vendor/github.com/compose-spec/compose-go/v2/loader/include.go +++ b/vendor/github.com/compose-spec/compose-go/v2/loader/include.go @@ -29,12 +29,15 @@ import ( "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) { if source == 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 { if v, ok := config.(string); ok { configs[i] = map[string]any{ @@ -73,11 +76,19 @@ func ApplyInclude(ctx context.Context, configDetails types.ConfigDetails, model p = path 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 { if f == path { included = append(included, path) diff --git a/vendor/github.com/compose-spec/compose-go/v2/loader/loader.go b/vendor/github.com/compose-spec/compose-go/v2/loader/loader.go index 1ed3977a..78e2242a 100644 --- a/vendor/github.com/compose-spec/compose-go/v2/loader/loader.go +++ b/vendor/github.com/compose-spec/compose-go/v2/loader/loader.go @@ -148,8 +148,11 @@ func (l localResourceLoader) Load(_ context.Context, p string) (string, error) { return l.abs(p), nil } -func (l localResourceLoader) Dir(path string) string { - path = l.abs(filepath.Dir(path)) +func (l localResourceLoader) Dir(originalPath string) string { + path := l.abs(originalPath) + if !l.isDir(path) { + path = l.abs(filepath.Dir(originalPath)) + } rel, err := filepath.Rel(l.WorkingDir, path) if err != nil { return path @@ -157,6 +160,14 @@ func (l localResourceLoader) Dir(path string) string { 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 { return &Options{ SkipValidation: o.SkipValidation, @@ -452,7 +463,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 { return nil, err } diff --git a/vendor/github.com/compose-spec/compose-go/v2/loader/normalize.go b/vendor/github.com/compose-spec/compose-go/v2/loader/normalize.go index 551b4be6..5f7c8e85 100644 --- a/vendor/github.com/compose-spec/compose-go/v2/loader/normalize.go +++ b/vendor/github.com/compose-spec/compose-go/v2/loader/normalize.go @@ -26,18 +26,12 @@ import ( // 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) { - dict["networks"] = normalizeNetworks(dict) + normalizeNetworks(dict) if d, ok := dict["services"]; ok { services := d.(map[string]any) for name, s := range services { 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 { 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 } -func normalizeNetworks(dict map[string]any) map[string]any { +func normalizeNetworks(dict map[string]any) { var networks map[string]any if n, ok := dict["networks"]; ok { networks = n.(map[string]any) } else { 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 networks["default"] = nil } - return networks + + if len(networks) > 0 { + dict["networks"] = networks + } } func resolve(a any, fn func(s string) (string, bool)) (any, bool) { diff --git a/vendor/github.com/compose-spec/compose-go/v2/loader/reset.go b/vendor/github.com/compose-spec/compose-go/v2/loader/reset.go index 3c615eff..2b7f04c3 100644 --- a/vendor/github.com/compose-spec/compose-go/v2/loader/reset.go +++ b/vendor/github.com/compose-spec/compose-go/v2/loader/reset.go @@ -19,6 +19,7 @@ package loader import ( "fmt" "strconv" + "strings" "github.com/compose-spec/compose-go/v2/tree" "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 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" { p.paths = append(p.paths, path) return nil, nil diff --git a/vendor/github.com/compose-spec/compose-go/v2/loader/validate.go b/vendor/github.com/compose-spec/compose-go/v2/loader/validate.go index 973e5213..cfaf139f 100644 --- a/vendor/github.com/compose-spec/compose-go/v2/loader/validate.go +++ b/vendor/github.com/compose-spec/compose-go/v2/loader/validate.go @@ -28,7 +28,6 @@ import ( // checkConsistency validate a compose model is consistent func checkConsistency(project *types.Project) error { - containerNames := map[string]string{} for _, s := range project.Services { 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) @@ -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 != "" { attr := "scale" if s.Scale == nil { diff --git a/vendor/github.com/compose-spec/compose-go/v2/override/uncity.go b/vendor/github.com/compose-spec/compose-go/v2/override/uncity.go index 9cbc45f4..057b2f28 100644 --- a/vendor/github.com/compose-spec/compose-go/v2/override/uncity.go +++ b/vendor/github.com/compose-spec/compose-go/v2/override/uncity.go @@ -42,6 +42,7 @@ func init() { unique["services.*.build.labels"] = keyValueIndexer unique["services.*.cap_add"] = keyValueIndexer unique["services.*.cap_drop"] = keyValueIndexer + unique["services.*.devices"] = volumeIndexer unique["services.*.configs"] = mountIndexer("") unique["services.*.deploy.labels"] = keyValueIndexer unique["services.*.dns"] = keyValueIndexer diff --git a/vendor/github.com/compose-spec/compose-go/v2/schema/compose-spec.json b/vendor/github.com/compose-spec/compose-go/v2/schema/compose-spec.json index 3a4ee727..cbe51db7 100644 --- a/vendor/github.com/compose-spec/compose-go/v2/schema/compose-spec.json +++ b/vendor/github.com/compose-spec/compose-go/v2/schema/compose-spec.json @@ -104,6 +104,7 @@ "context": {"type": "string"}, "dockerfile": {"type": "string"}, "dockerfile_inline": {"type": "string"}, + "entitlements": {"type": "array", "items": {"type": "string"}}, "args": {"$ref": "#/definitions/list_or_dict"}, "ssh": {"$ref": "#/definitions/list_or_dict"}, "labels": {"$ref": "#/definitions/list_or_dict"}, diff --git a/vendor/github.com/compose-spec/compose-go/v2/transform/build.go b/vendor/github.com/compose-spec/compose-go/v2/transform/build.go index 88ac05bf..90a996cc 100644 --- a/vendor/github.com/compose-spec/compose-go/v2/transform/build.go +++ b/vendor/github.com/compose-spec/compose-go/v2/transform/build.go @@ -22,10 +22,10 @@ import ( "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) { case map[string]any: - return transformMapping(v, p) + return transformMapping(v, p, ignoreParseError) case string: return map[string]any{ "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) { case map[string]any: if _, ok := v["context"]; !ok { diff --git a/vendor/github.com/compose-spec/compose-go/v2/transform/canonical.go b/vendor/github.com/compose-spec/compose-go/v2/transform/canonical.go index 4b6603fc..1240c77c 100644 --- a/vendor/github.com/compose-spec/compose-go/v2/transform/canonical.go +++ b/vendor/github.com/compose-spec/compose-go/v2/transform/canonical.go @@ -20,7 +20,7 @@ import ( "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{} @@ -48,18 +48,18 @@ func init() { } // Canonical transforms a compose model into canonical syntax -func Canonical(yaml map[string]any) (map[string]any, error) { - canonical, err := transform(yaml, tree.NewPath()) +func Canonical(yaml map[string]any, ignoreParseError bool) (map[string]any, error) { + canonical, err := transform(yaml, tree.NewPath(), ignoreParseError) if err != nil { return nil, err } 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 { if p.Matches(pattern) { - t, err := transformer(data, p) + t, err := transformer(data, p, ignoreParseError) if err != nil { return nil, err } @@ -68,13 +68,13 @@ func transform(data any, p tree.Path) (any, error) { } switch v := data.(type) { case map[string]any: - a, err := transformMapping(v, p) + a, err := transformMapping(v, p, ignoreParseError) if err != nil { return a, err } return v, nil case []any: - a, err := transformSequence(v, p) + a, err := transformSequence(v, p, ignoreParseError) if err != nil { 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 { - t, err := transform(e, p.Next("[]")) + t, err := transform(e, p.Next("[]"), ignoreParseError) if err != nil { return nil, err } @@ -95,9 +95,9 @@ func transformSequence(v []any, p tree.Path) ([]any, error) { 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 { - t, err := transform(e, p.Next(k)) + t, err := transform(e, p.Next(k), ignoreParseError) if err != nil { return nil, err } diff --git a/vendor/github.com/compose-spec/compose-go/v2/transform/defaults.go b/vendor/github.com/compose-spec/compose-go/v2/transform/defaults.go index 49293b72..276b1370 100644 --- a/vendor/github.com/compose-spec/compose-go/v2/transform/defaults.go +++ b/vendor/github.com/compose-spec/compose-go/v2/transform/defaults.go @@ -39,7 +39,7 @@ func SetDefaultValues(yaml map[string]any) (map[string]any, error) { func setDefaults(data any, p tree.Path) (any, error) { for pattern, transformer := range defaultValues { if p.Matches(pattern) { - t, err := transformer(data, p) + t, err := transformer(data, p, false) if err != nil { return nil, err } diff --git a/vendor/github.com/compose-spec/compose-go/v2/transform/dependson.go b/vendor/github.com/compose-spec/compose-go/v2/transform/dependson.go index 8c6e1ed3..0a72ffa4 100644 --- a/vendor/github.com/compose-spec/compose-go/v2/transform/dependson.go +++ b/vendor/github.com/compose-spec/compose-go/v2/transform/dependson.go @@ -22,7 +22,7 @@ import ( "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) { case map[string]any: for i, e := range v { diff --git a/vendor/github.com/compose-spec/compose-go/v2/transform/envfile.go b/vendor/github.com/compose-spec/compose-go/v2/transform/envfile.go index 842303b1..e5100530 100644 --- a/vendor/github.com/compose-spec/compose-go/v2/transform/envfile.go +++ b/vendor/github.com/compose-spec/compose-go/v2/transform/envfile.go @@ -22,7 +22,7 @@ import ( "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) { case string: return []any{ diff --git a/vendor/github.com/compose-spec/compose-go/v2/transform/extends.go b/vendor/github.com/compose-spec/compose-go/v2/transform/extends.go index 9f77176e..e0f9be2d 100644 --- a/vendor/github.com/compose-spec/compose-go/v2/transform/extends.go +++ b/vendor/github.com/compose-spec/compose-go/v2/transform/extends.go @@ -22,10 +22,10 @@ import ( "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) { case map[string]any: - return transformMapping(v, p) + return transformMapping(v, p, ignoreParseError) case string: return map[string]any{ "service": v, diff --git a/vendor/github.com/compose-spec/compose-go/v2/transform/external.go b/vendor/github.com/compose-spec/compose-go/v2/transform/external.go index 9e24eb83..be718f03 100644 --- a/vendor/github.com/compose-spec/compose-go/v2/transform/external.go +++ b/vendor/github.com/compose-spec/compose-go/v2/transform/external.go @@ -23,11 +23,11 @@ import ( "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 { return nil, nil } - resource, err := transformMapping(data.(map[string]any), p) + resource, err := transformMapping(data.(map[string]any), p, ignoreParseError) if err != nil { return nil, err } diff --git a/vendor/github.com/compose-spec/compose-go/v2/transform/include.go b/vendor/github.com/compose-spec/compose-go/v2/transform/include.go index 2488483a..8a80439e 100644 --- a/vendor/github.com/compose-spec/compose-go/v2/transform/include.go +++ b/vendor/github.com/compose-spec/compose-go/v2/transform/include.go @@ -22,7 +22,7 @@ import ( "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) { case map[string]any: return v, nil diff --git a/vendor/github.com/compose-spec/compose-go/v2/transform/mapping.go b/vendor/github.com/compose-spec/compose-go/v2/transform/mapping.go index b247f84f..007aa9ed 100644 --- a/vendor/github.com/compose-spec/compose-go/v2/transform/mapping.go +++ b/vendor/github.com/compose-spec/compose-go/v2/transform/mapping.go @@ -23,7 +23,7 @@ import ( "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) { case map[string]any: return v, nil @@ -32,6 +32,9 @@ func transformKeyValue(data any, p tree.Path) (any, error) { for _, e := range v { before, after, found := strings.Cut(e.(string), "=") if !found { + if ignoreParseError { + return data, nil + } return nil, fmt.Errorf("%s: invalid value %s, expected key=value", p, e) } mapping[before] = after diff --git a/vendor/github.com/compose-spec/compose-go/v2/transform/ports.go b/vendor/github.com/compose-spec/compose-go/v2/transform/ports.go index 75a6fb65..de11b942 100644 --- a/vendor/github.com/compose-spec/compose-go/v2/transform/ports.go +++ b/vendor/github.com/compose-spec/compose-go/v2/transform/ports.go @@ -24,7 +24,7 @@ import ( "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) { case []any: // We process the list instead of individual items here. @@ -48,7 +48,10 @@ func transformPorts(data any, p tree.Path) (any, error) { case string: parsed, err := types.ParsePortConfig(value) if err != nil { - return data, nil + if ignoreParseError { + return data, nil + } + return nil, err } if err != nil { return nil, err diff --git a/vendor/github.com/compose-spec/compose-go/v2/transform/secrets.go b/vendor/github.com/compose-spec/compose-go/v2/transform/secrets.go index 1e84d18d..c2db1352 100644 --- a/vendor/github.com/compose-spec/compose-go/v2/transform/secrets.go +++ b/vendor/github.com/compose-spec/compose-go/v2/transform/secrets.go @@ -22,7 +22,7 @@ import ( "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) { case map[string]any: 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) { case map[string]any: source := v["source"] diff --git a/vendor/github.com/compose-spec/compose-go/v2/transform/services.go b/vendor/github.com/compose-spec/compose-go/v2/transform/services.go index c1411452..960c3e7e 100644 --- a/vendor/github.com/compose-spec/compose-go/v2/transform/services.go +++ b/vendor/github.com/compose-spec/compose-go/v2/transform/services.go @@ -20,16 +20,16 @@ import ( "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) { case map[string]any: - return transformMapping(value, p) + return transformMapping(value, p, ignoreParseError) default: 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 { networks := make(map[string]any, len(slice)) for _, net := range slice { diff --git a/vendor/github.com/compose-spec/compose-go/v2/transform/ssh.go b/vendor/github.com/compose-spec/compose-go/v2/transform/ssh.go index 48e2a6cf..2663461e 100644 --- a/vendor/github.com/compose-spec/compose-go/v2/transform/ssh.go +++ b/vendor/github.com/compose-spec/compose-go/v2/transform/ssh.go @@ -23,7 +23,7 @@ import ( "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) { case map[string]any: return v, nil diff --git a/vendor/github.com/compose-spec/compose-go/v2/transform/ulimits.go b/vendor/github.com/compose-spec/compose-go/v2/transform/ulimits.go index de7784c3..57cce4fb 100644 --- a/vendor/github.com/compose-spec/compose-go/v2/transform/ulimits.go +++ b/vendor/github.com/compose-spec/compose-go/v2/transform/ulimits.go @@ -22,7 +22,7 @@ import ( "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) { case map[string]any: return v, nil diff --git a/vendor/github.com/compose-spec/compose-go/v2/transform/volume.go b/vendor/github.com/compose-spec/compose-go/v2/transform/volume.go index e6dd8795..b08e8b1a 100644 --- a/vendor/github.com/compose-spec/compose-go/v2/transform/volume.go +++ b/vendor/github.com/compose-spec/compose-go/v2/transform/volume.go @@ -24,13 +24,16 @@ import ( "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) { case map[string]any: return v, nil case string: volume, err := format.ParseVolume(v) // TODO(ndeloof) ParseVolume should not rely on types and return map[string] if err != nil { + if ignoreParseError { + return v, nil + } return nil, err } volume.Target = cleanTarget(volume.Target) diff --git a/vendor/github.com/compose-spec/compose-go/v2/types/project.go b/vendor/github.com/compose-spec/compose-go/v2/types/project.go index 780dd91c..b86a563f 100644 --- a/vendor/github.com/compose-spec/compose-go/v2/types/project.go +++ b/vendor/github.com/compose-spec/compose-go/v2/types/project.go @@ -696,3 +696,17 @@ func (p *Project) WithServicesTransform(fn func(name string, s ServiceConfig) (S } 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 +} diff --git a/vendor/github.com/compose-spec/compose-go/v2/types/types.go b/vendor/github.com/compose-spec/compose-go/v2/types/types.go index 2ee7f4c4..466454c4 100644 --- a/vendor/github.com/compose-spec/compose-go/v2/types/types.go +++ b/vendor/github.com/compose-spec/compose-go/v2/types/types.go @@ -266,6 +266,7 @@ type BuildConfig struct { Context string `yaml:"context,omitempty" json:"context,omitempty"` Dockerfile string `yaml:"dockerfile,omitempty" json:"dockerfile,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"` SSH SSHConfig `yaml:"ssh,omitempty" json:"ssh,omitempty"` Labels Labels `yaml:"labels,omitempty" json:"labels,omitempty"` diff --git a/vendor/modules.txt b/vendor/modules.txt index a38eb746..325d937d 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -131,7 +131,7 @@ github.com/cenkalti/backoff/v4 # github.com/cespare/xxhash/v2 v2.2.0 ## explicit; go 1.11 github.com/cespare/xxhash/v2 -# github.com/compose-spec/compose-go/v2 v2.0.2 +# github.com/compose-spec/compose-go/v2 v2.1.0 ## explicit; go 1.21 github.com/compose-spec/compose-go/v2/cli github.com/compose-spec/compose-go/v2/consts From 33a55280033c7ccecee5d7e0c67d8d90c13e7646 Mon Sep 17 00:00:00 2001 From: CrazyMax <1951866+crazy-max@users.noreply.github.com> Date: Thu, 30 May 2024 15:37:13 +0200 Subject: [PATCH 2/2] bump compose-go to v2.1.1 Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 +- .../compose-go/v2/loader/loader.go | 22 +-- .../compose-go/v2/override/uncity.go | 7 +- .../compose-go/v2/schema/compose-spec.json | 6 + .../compose-go/v2/template/template.go | 96 ----------- .../compose-go/v2/template/variables.go | 155 ++++++++++++++++++ .../compose-go/v2/types/project.go | 4 +- .../compose-go/v2/types/stringOrList.go | 6 +- .../compose-spec/compose-go/v2/types/types.go | 1 + vendor/modules.txt | 2 +- 11 files changed, 187 insertions(+), 118 deletions(-) create mode 100644 vendor/github.com/compose-spec/compose-go/v2/template/variables.go diff --git a/go.mod b/go.mod index a418ebdc..38e73227 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/Masterminds/semver/v3 v3.2.1 github.com/Microsoft/go-winio v0.6.1 github.com/aws/aws-sdk-go-v2/config v1.26.6 - github.com/compose-spec/compose-go/v2 v2.1.0 + github.com/compose-spec/compose-go/v2 v2.1.1 github.com/containerd/console v1.0.4 github.com/containerd/containerd v1.7.15 github.com/containerd/continuity v0.4.3 diff --git a/go.sum b/go.sum index 27844b31..ac7a9827 100644 --- a/go.sum +++ b/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/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/compose-spec/compose-go/v2 v2.1.0 h1:qdW2qISQlCQG8v1O2TChcdxgAWTUGgUX/CPSO+ES9+E= -github.com/compose-spec/compose-go/v2 v2.1.0/go.mod h1:bEPizBkIojlQ20pi2vNluBa58tevvj0Y18oUSHPyfdc= +github.com/compose-spec/compose-go/v2 v2.1.1 h1:tKuYJwAVgxIryRrsvWJSf1kNviVOQVVqwyHsV6YoIUc= +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/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw= github.com/containerd/console v1.0.4 h1:F2g4+oChYvBTsASRTz8NP6iIAi97J3TtSAsLbIFn4ro= diff --git a/vendor/github.com/compose-spec/compose-go/v2/loader/loader.go b/vendor/github.com/compose-spec/compose-go/v2/loader/loader.go index 78e2242a..cd8f281a 100644 --- a/vendor/github.com/compose-spec/compose-go/v2/loader/loader.go +++ b/vendor/github.com/compose-spec/compose-go/v2/loader/loader.go @@ -328,19 +328,11 @@ func loadModelWithContext(ctx context.Context, configDetails *types.ConfigDetail return nil, errors.New("No files specified") } - err := projectName(*configDetails, opts) + err := projectName(configDetails, opts) if err != nil { 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) } @@ -595,10 +587,14 @@ func InvalidProjectNameErr(v string) error { // 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) error { +func projectName(details *types.ConfigDetails, opts *Options) error { + defer func() { + if details.Environment == nil { + details.Environment = map[string]string{} + } + details.Environment[consts.ComposeProjectName] = opts.projectName + }() + if opts.projectNameImperativelySet { if NormalizeProjectName(opts.projectName) != opts.projectName { return InvalidProjectNameErr(opts.projectName) diff --git a/vendor/github.com/compose-spec/compose-go/v2/override/uncity.go b/vendor/github.com/compose-spec/compose-go/v2/override/uncity.go index 057b2f28..7819015e 100644 --- a/vendor/github.com/compose-spec/compose-go/v2/override/uncity.go +++ b/vendor/github.com/compose-spec/compose-go/v2/override/uncity.go @@ -198,12 +198,15 @@ func portIndexer(y any, p tree.Path) (string, error) { return "", nil } -func envFileIndexer(y any, _ tree.Path) (string, error) { +func envFileIndexer(y any, p tree.Path) (string, error) { switch value := y.(type) { case string: return value, nil 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 } diff --git a/vendor/github.com/compose-spec/compose-go/v2/schema/compose-spec.json b/vendor/github.com/compose-spec/compose-go/v2/schema/compose-spec.json index cbe51db7..33a79e9b 100644 --- a/vendor/github.com/compose-spec/compose-go/v2/schema/compose-spec.json +++ b/vendor/github.com/compose-spec/compose-go/v2/schema/compose-spec.json @@ -296,6 +296,12 @@ "ipv6_address": {"type": "string"}, "link_local_ips": {"$ref": "#/definitions/list_of_strings"}, "mac_address": {"type": "string"}, + "driver_opts": { + "type": "object", + "patternProperties": { + "^.+$": {"type": ["string", "number"]} + } + }, "priority": {"type": "number"} }, "additionalProperties": false, diff --git a/vendor/github.com/compose-spec/compose-go/v2/template/template.go b/vendor/github.com/compose-spec/compose-go/v2/template/template.go index 0d9c17ef..d9483cbd 100644 --- a/vendor/github.com/compose-spec/compose-go/v2/template/template.go +++ b/vendor/github.com/compose-spec/compose-go/v2/template/template.go @@ -271,102 +271,6 @@ func Substitute(template string, mapping Mapping) (string, error) { 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) func defaultWhenEmptyOrUnset(substitution string, mapping Mapping) (string, bool, error) { return withDefaultWhenAbsence(substitution, mapping, true) diff --git a/vendor/github.com/compose-spec/compose-go/v2/template/variables.go b/vendor/github.com/compose-spec/compose-go/v2/template/variables.go new file mode 100644 index 00000000..fb32f9c3 --- /dev/null +++ b/vendor/github.com/compose-spec/compose-go/v2/template/variables.go @@ -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 +} diff --git a/vendor/github.com/compose-spec/compose-go/v2/types/project.go b/vendor/github.com/compose-spec/compose-go/v2/types/project.go index b86a563f..6f049e98 100644 --- a/vendor/github.com/compose-spec/compose-go/v2/types/project.go +++ b/vendor/github.com/compose-spec/compose-go/v2/types/project.go @@ -659,12 +659,12 @@ func (p *Project) WithServicesTransform(fn func(name string, s ServiceConfig) (S name string service ServiceConfig } - resultCh := make(chan result) + expect := len(p.Services) + resultCh := make(chan result, expect) newProject := p.deepCopy() eg, ctx := errgroup.WithContext(context.Background()) eg.Go(func() error { - expect := len(newProject.Services) s := Services{} for expect > 0 { select { diff --git a/vendor/github.com/compose-spec/compose-go/v2/types/stringOrList.go b/vendor/github.com/compose-spec/compose-go/v2/types/stringOrList.go index 059043fb..a6720df0 100644 --- a/vendor/github.com/compose-spec/compose-go/v2/types/stringOrList.go +++ b/vendor/github.com/compose-spec/compose-go/v2/types/stringOrList.go @@ -28,7 +28,11 @@ func (l *StringList) DecodeMapstructure(value interface{}) error { case []interface{}: list := make([]string, len(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 default: diff --git a/vendor/github.com/compose-spec/compose-go/v2/types/types.go b/vendor/github.com/compose-spec/compose-go/v2/types/types.go index 466454c4..00b1ac13 100644 --- a/vendor/github.com/compose-spec/compose-go/v2/types/types.go +++ b/vendor/github.com/compose-spec/compose-go/v2/types/types.go @@ -453,6 +453,7 @@ type ServiceNetworkConfig struct { Ipv6Address string `yaml:"ipv6_address,omitempty" json:"ipv6_address,omitempty"` LinkLocalIPs []string `yaml:"link_local_ips,omitempty" json:"link_local_ips,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:"-"` } diff --git a/vendor/modules.txt b/vendor/modules.txt index 325d937d..8d6551d4 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -131,7 +131,7 @@ github.com/cenkalti/backoff/v4 # github.com/cespare/xxhash/v2 v2.2.0 ## explicit; go 1.11 github.com/cespare/xxhash/v2 -# github.com/compose-spec/compose-go/v2 v2.1.0 +# github.com/compose-spec/compose-go/v2 v2.1.1 ## explicit; go 1.21 github.com/compose-spec/compose-go/v2/cli github.com/compose-spec/compose-go/v2/consts