bumpo compose-go to v2.1.0

Signed-off-by: Guillaume Lours <705411+glours@users.noreply.github.com>
This commit is contained in:
Guillaume Lours 2024-04-23 10:28:28 +02:00
parent 699fa43f7f
commit e3e16ad088
No known key found for this signature in database
29 changed files with 146 additions and 66 deletions

2
go.mod
View File

@ -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

4
go.sum
View File

@ -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=

View File

@ -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}
}

View File

@ -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)

View File

@ -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)

View File

@ -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
}

View File

@ -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) {

View File

@ -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

View File

@ -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 {

View File

@ -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

View File

@ -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"},

View File

@ -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 {

View File

@ -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
}

View File

@ -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
}

View File

@ -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 {

View File

@ -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{

View File

@ -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,

View File

@ -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
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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"]

View File

@ -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 {

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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
}

View File

@ -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"`

2
vendor/modules.txt vendored
View File

@ -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