Skip to content

Commit 855edb9

Browse files
committed
remove dependency on mapstructure
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
1 parent c4e7bc8 commit 855edb9

35 files changed

Lines changed: 229 additions & 1966 deletions

cli/options.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -517,8 +517,9 @@ func (o *ProjectOptions) prepare(ctx context.Context) (*types.ConfigDetails, err
517517
return configDetails, nil
518518
}
519519

520-
// ProjectFromOptions load a compose project based on command line options
521-
// Deprecated: use ProjectOptions.LoadProject or ProjectOptions.LoadModel
520+
// ProjectFromOptions load a compose project based on command line options.
521+
//
522+
// Deprecated: use ProjectOptions.LoadProject or ProjectOptions.LoadModel.
522523
func ProjectFromOptions(ctx context.Context, options *ProjectOptions) (*types.Project, error) {
523524
return options.LoadProject(ctx)
524525
}

go.mod

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ require (
66
github.com/distribution/reference v0.5.0
77
github.com/docker/go-connections v0.4.0
88
github.com/docker/go-units v0.5.0
9-
github.com/go-viper/mapstructure/v2 v2.4.0
109
github.com/google/go-cmp v0.5.9
1110
github.com/mattn/go-shellwords v1.0.12
1211
github.com/opencontainers/go-digest v1.0.0

go.sum

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@ github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKoh
99
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
1010
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
1111
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
12-
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
13-
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
1412
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
1513
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
1614
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=

interpolation/node_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ services:
5858
web:
5959
image: ${IMAGE:-default}
6060
`
61-
lookup := func(key string) (string, bool) {
61+
lookup := func(_ string) (string, bool) {
6262
return "", false
6363
}
6464

@@ -83,7 +83,7 @@ services:
8383
ports:
8484
- "8080"
8585
`
86-
lookup := func(key string) (string, bool) {
86+
lookup := func(_ string) (string, bool) {
8787
return "", false
8888
}
8989

@@ -198,7 +198,7 @@ services:
198198
web:
199199
image: ${IMAGE:?}
200200
`
201-
lookup := func(key string) (string, bool) {
201+
lookup := func(_ string) (string, bool) {
202202
return "", false
203203
}
204204

loader/compose_model.go

Lines changed: 43 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"fmt"
2424
"io"
2525
"os"
26+
"path"
2627
"path/filepath"
2728
"reflect"
2829
"regexp"
@@ -31,7 +32,6 @@ import (
3132
"strings"
3233

3334
"github.com/compose-spec/compose-go/v2/dotenv"
34-
"github.com/sirupsen/logrus"
3535
"github.com/compose-spec/compose-go/v2/format"
3636
interp "github.com/compose-spec/compose-go/v2/interpolation"
3737
"github.com/compose-spec/compose-go/v2/override"
@@ -40,6 +40,7 @@ import (
4040
"github.com/compose-spec/compose-go/v2/tree"
4141
"github.com/compose-spec/compose-go/v2/types"
4242
"github.com/compose-spec/compose-go/v2/validation"
43+
"github.com/sirupsen/logrus"
4344
"go.yaml.in/yaml/v4"
4445
)
4546

@@ -85,15 +86,13 @@ type ComposeModel struct {
8586
func init() {
8687
// Wire up the volume-parsing hook so that types.ServiceVolumeConfig.UnmarshalYAML
8788
// can parse short syntax without importing the format package directly.
88-
types.ParseVolumeFunc = func(s string) (types.ServiceVolumeConfig, error) {
89-
return format.ParseVolume(s)
90-
}
89+
types.ParseVolumeFunc = format.ParseVolume
9190
}
9291

9392
// LoadLazyModel parses compose files into raw yaml.Node layers without
9493
// performing interpolation or normalization. The resulting ComposeModel
9594
// can later be materialized into a types.Project by calling Resolve().
96-
func LoadLazyModel(ctx context.Context, configDetails types.ConfigDetails, options ...func(*Options)) (*ComposeModel, error) {
95+
func LoadLazyModel(_ context.Context, configDetails types.ConfigDetails, options ...func(*Options)) (*ComposeModel, error) {
9796
opts := ToOptions(&configDetails, options)
9897

9998
if len(configDetails.ConfigFiles) < 1 {
@@ -105,9 +104,9 @@ func LoadLazyModel(ctx context.Context, configDetails types.ConfigDetails, optio
105104
}
106105

107106
model := &ComposeModel{
108-
configDetails: configDetails,
109-
opts: opts,
110-
nodeContexts: make(map[*yaml.Node]*NodeContext),
107+
configDetails: configDetails,
108+
opts: opts,
109+
nodeContexts: make(map[*yaml.Node]*NodeContext),
111110
serviceWorkDirs: make(map[string]string),
112111
}
113112

@@ -242,7 +241,7 @@ func checkDuplicateKeys(node *yaml.Node) error {
242241
// This is the point where all deferred processing happens:
243242
// extends resolution, includes loading, merging, interpolation,
244243
// type casting, and decoding into Go structs.
245-
func (m *ComposeModel) Resolve() (*types.Project, error) {
244+
func (m *ComposeModel) Resolve() (*types.Project, error) { //nolint:gocyclo
246245
// 1. Process extends on raw nodes (before interpolation, same as existing pipeline)
247246
if !m.opts.SkipExtends {
248247
for _, layer := range m.layers {
@@ -372,9 +371,7 @@ func (m *ComposeModel) Resolve() (*types.Project, error) {
372371

373372
// 7. Normalization (default network, resource names, build defaults, etc.)
374373
if !m.opts.SkipNormalization {
375-
if err := normalizeProject(project, m.opts); err != nil {
376-
return nil, err
377-
}
374+
normalizeProject(project)
378375
}
379376

380377
// 7a. Always resolve environment references in secrets/configs
@@ -429,7 +426,7 @@ func (m *ComposeModel) Resolve() (*types.Project, error) {
429426
// interpolateTree walks the yaml.Node tree and interpolates scalar values
430427
// using per-node context from nodeContexts. Nodes not found in the map
431428
// inherit context from the nearest registered ancestor during the walk.
432-
func (m *ComposeModel) interpolateTree(node *yaml.Node, path tree.Path, inherited *NodeContext) error {
429+
func (m *ComposeModel) interpolateTree(node *yaml.Node, p tree.Path, inherited *NodeContext) error {
433430
if node == nil {
434431
return nil
435432
}
@@ -443,7 +440,7 @@ func (m *ComposeModel) interpolateTree(node *yaml.Node, path tree.Path, inherite
443440
switch node.Kind {
444441
case yaml.DocumentNode:
445442
for _, child := range node.Content {
446-
if err := m.interpolateTree(child, path, ctx); err != nil {
443+
if err := m.interpolateTree(child, p, ctx); err != nil {
447444
return err
448445
}
449446
}
@@ -463,28 +460,28 @@ func (m *ComposeModel) interpolateTree(node *yaml.Node, path tree.Path, inherite
463460
pairCtx = c
464461
}
465462

466-
next := path.Next(keyNode.Value)
463+
next := p.Next(keyNode.Value)
467464
if err := m.interpolateTree(valNode, next, pairCtx); err != nil {
468465
return err
469466
}
470467
}
471468

472469
case yaml.SequenceNode:
473470
for _, child := range node.Content {
474-
if err := m.interpolateTree(child, path.Next(tree.PathMatchList), ctx); err != nil {
471+
if err := m.interpolateTree(child, p.Next(tree.PathMatchList), ctx); err != nil {
475472
return err
476473
}
477474
}
478475

479476
case yaml.ScalarNode:
480-
return m.interpolateScalar(node, path, ctx)
477+
return m.interpolateScalar(node, p, ctx)
481478
}
482479

483480
return nil
484481
}
485482

486483
// interpolateScalar substitutes variables and applies type casting on a single scalar node.
487-
func (m *ComposeModel) interpolateScalar(node *yaml.Node, path tree.Path, ctx *NodeContext) error {
484+
func (m *ComposeModel) interpolateScalar(node *yaml.Node, p tree.Path, ctx *NodeContext) error {
488485
if node.Tag != "!!str" && node.Tag != "" && !strings.Contains(node.Value, "$") {
489486
return nil
490487
}
@@ -505,7 +502,7 @@ func (m *ComposeModel) interpolateScalar(node *yaml.Node, path tree.Path, ctx *N
505502
// Type casting based on tree path
506503
var caster interp.Cast
507504
for pattern, c := range interpolateTypeCastMapping {
508-
if path.Matches(pattern) {
505+
if p.Matches(pattern) {
509506
caster = c
510507
break
511508
}
@@ -615,7 +612,7 @@ func (m *ComposeModel) resolveServiceExtends(layer *Layer, services *yaml.Node,
615612
// cycle detection using file:service identifiers
616613
chainID := layer.Context.Source + ":" + name
617614
if slices.Contains(chain, chainID) {
618-
return fmt.Errorf("Circular reference with extends")
615+
return fmt.Errorf("circular reference with extends")
619616
}
620617
chain = append(chain, chainID)
621618

@@ -812,7 +809,7 @@ func (m *ComposeModel) applyIncludeNodes() error {
812809
return nil
813810
}
814811

815-
func (m *ComposeModel) loadIncludeEntry(parent *Layer, entry *yaml.Node) ([]*Layer, error) {
812+
func (m *ComposeModel) loadIncludeEntry(parent *Layer, entry *yaml.Node) ([]*Layer, error) { //nolint:gocyclo
816813
var paths []string
817814
var projectDir string
818815
var envFiles []string
@@ -859,15 +856,16 @@ func (m *ComposeModel) loadIncludeEntry(parent *Layer, entry *yaml.Node) ([]*Lay
859856
for i, p := range paths {
860857
resolved := false
861858
for _, loader := range m.opts.RemoteResourceLoaders() {
862-
if loader.Accept(p) {
863-
absPath, loadErr := loader.Load(context.TODO(), p)
864-
if loadErr != nil {
865-
return nil, types.WrapNodeError(entry, fmt.Errorf("loading include %s: %w", p, loadErr))
866-
}
867-
paths[i] = absPath
868-
resolved = true
869-
break
859+
if !loader.Accept(p) {
860+
continue
870861
}
862+
absPath, loadErr := loader.Load(context.TODO(), p)
863+
if loadErr != nil {
864+
return nil, types.WrapNodeError(entry, fmt.Errorf("loading include %s: %w", p, loadErr))
865+
}
866+
paths[i] = absPath
867+
resolved = true
868+
break
871869
}
872870
if !resolved && !filepath.IsAbs(p) {
873871
paths[i] = filepath.Join(parent.Context.WorkingDir, p)
@@ -1022,13 +1020,13 @@ func addBuildContextDefault(svc *yaml.Node) {
10221020
// resolveServiceNodePaths adjusts relative paths in a service yaml.Node to be
10231021
// expressed relative to workDir. This is used for extends from external files
10241022
// to make paths relative to the main project directory.
1025-
func resolveServiceNodePaths(svc *yaml.Node, workDir string) {
1023+
func resolveServiceNodePaths(svc *yaml.Node, workDir string) { //nolint:gocyclo
10261024
if svc == nil || svc.Kind != yaml.MappingNode || workDir == "." {
10271025
return
10281026
}
10291027

10301028
absNodePath := func(p string) string {
1031-
if filepath.IsAbs(p) || p == "" {
1029+
if filepath.IsAbs(p) || path.IsAbs(p) || p == "" {
10321030
return p
10331031
}
10341032
return filepath.Join(workDir, p)
@@ -1037,12 +1035,13 @@ func resolveServiceNodePaths(svc *yaml.Node, workDir string) {
10371035
// build.context
10381036
_, build := override.FindKey(svc, "build")
10391037
if build != nil {
1040-
if build.Kind == yaml.ScalarNode {
1038+
switch build.Kind {
1039+
case yaml.ScalarNode:
10411040
// short syntax: build: ./path
10421041
if !strings.Contains(build.Value, "://") {
10431042
build.Value = absNodePath(build.Value)
10441043
}
1045-
} else if build.Kind == yaml.MappingNode {
1044+
case yaml.MappingNode:
10461045
_, ctx := override.FindKey(build, "context")
10471046
if ctx != nil && ctx.Kind == yaml.ScalarNode && !strings.Contains(ctx.Value, "://") {
10481047
ctx.Value = absNodePath(ctx.Value)
@@ -1079,18 +1078,19 @@ func resolveServiceNodePaths(svc *yaml.Node, workDir string) {
10791078
_, volumes := override.FindKey(svc, "volumes")
10801079
if volumes != nil && volumes.Kind == yaml.SequenceNode {
10811080
for i, item := range volumes.Content {
1082-
if item.Kind == yaml.MappingNode {
1081+
switch item.Kind {
1082+
case yaml.MappingNode:
10831083
_, vtype := override.FindKey(item, "type")
10841084
if vtype != nil && vtype.Value == "bind" {
10851085
_, src := override.FindKey(item, "source")
10861086
if src != nil && src.Kind == yaml.ScalarNode {
10871087
src.Value = absNodePath(src.Value)
10881088
}
10891089
}
1090-
} else if item.Kind == yaml.ScalarNode {
1090+
case yaml.ScalarNode:
10911091
// Short syntax: parse, resolve source, convert to long syntax mapping
10921092
vol, err := format.ParseVolume(item.Value)
1093-
if err == nil && vol.Type == types.VolumeTypeBind && vol.Source != "" && !filepath.IsAbs(vol.Source) {
1093+
if err == nil && vol.Type == types.VolumeTypeBind && vol.Source != "" && !filepath.IsAbs(vol.Source) && !path.IsAbs(vol.Source) {
10941094
vol.Source = absNodePath(vol.Source)
10951095
// Convert to long syntax mapping node to preserve bind type
10961096
trueNode := &yaml.Node{Kind: yaml.ScalarNode, Tag: "!!bool", Value: "true"}
@@ -1252,7 +1252,7 @@ func resolveLayerNodePaths(node *yaml.Node, workDir string) {
12521252
}
12531253

12541254
absPath := func(p string) string {
1255-
if filepath.IsAbs(p) || p == "" {
1255+
if filepath.IsAbs(p) || path.IsAbs(p) || p == "" {
12561256
return p
12571257
}
12581258
return filepath.Join(workDir, p)
@@ -1299,16 +1299,20 @@ func resolveLayerNodePaths(node *yaml.Node, workDir string) {
12991299
}
13001300

13011301
// processProjectExtensions converts raw extension values to registered Go types.
1302-
// This mirrors the old processExtensions that operated on map[string]any.
13031302
func processProjectExtensions(project *types.Project, known map[string]any) error {
13041303
convertExtensions := func(ext types.Extensions) error {
13051304
for name, val := range ext {
13061305
typ, ok := known[name]
13071306
if !ok {
13081307
continue
13091308
}
1309+
// Marshal the raw value to yaml, then unmarshal into the target type
1310+
b, err := yaml.Marshal(val)
1311+
if err != nil {
1312+
return fmt.Errorf("converting extension %s: %w", name, err)
1313+
}
13101314
target := reflect.New(reflect.TypeOf(typ)).Interface()
1311-
if err := Transform(val, target); err != nil {
1315+
if err := yaml.Unmarshal(b, target); err != nil {
13121316
return fmt.Errorf("converting extension %s: %w", name, err)
13131317
}
13141318
ext[name] = reflect.ValueOf(target).Elem().Interface()

0 commit comments

Comments
 (0)