diff --git a/loader/loader.go b/loader/loader.go index 4bcb0f89..78e2242a 100644 --- a/loader/loader.go +++ b/loader/loader.go @@ -463,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/transform/build.go b/transform/build.go index 88ac05bf..90a996cc 100644 --- a/transform/build.go +++ b/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/transform/build_test.go b/transform/build_test.go index 11b25668..bbaa9ab7 100644 --- a/transform/build_test.go +++ b/transform/build_test.go @@ -49,7 +49,7 @@ func Test_transformBuild(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := transformBuild(tt.yaml, tree.NewPath("services.foo.build")) + got, err := transformBuild(tt.yaml, tree.NewPath("services.foo.build"), false) assert.NilError(t, err) if !reflect.DeepEqual(got, tt.want) { t.Errorf("transformBuild() got = %v, want %v", got, tt.want) diff --git a/transform/canonical.go b/transform/canonical.go index 4b6603fc..1240c77c 100644 --- a/transform/canonical.go +++ b/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/transform/defaults.go b/transform/defaults.go index 49293b72..276b1370 100644 --- a/transform/defaults.go +++ b/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/transform/dependson.go b/transform/dependson.go index 8c6e1ed3..0a72ffa4 100644 --- a/transform/dependson.go +++ b/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/transform/envfile.go b/transform/envfile.go index 842303b1..e5100530 100644 --- a/transform/envfile.go +++ b/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/transform/envfile_test.go b/transform/envfile_test.go index 71cbc0e0..ea142443 100644 --- a/transform/envfile_test.go +++ b/transform/envfile_test.go @@ -25,7 +25,7 @@ import ( ) func TestSingle(t *testing.T) { - env, err := transformEnvFile(".env", tree.NewPath("service.test.env_file")) + env, err := transformEnvFile(".env", tree.NewPath("service.test.env_file"), false) assert.NilError(t, err) assert.DeepEqual(t, env, []any{ map[string]any{ @@ -42,7 +42,7 @@ func TestSequence(t *testing.T) { - other.env `), &in) assert.NilError(t, err) - env, err := transformEnvFile(in, tree.NewPath("service.test.env_file")) + env, err := transformEnvFile(in, tree.NewPath("service.test.env_file"), false) assert.NilError(t, err) assert.DeepEqual(t, env, []any{ map[string]any{ @@ -64,7 +64,7 @@ func TestOptional(t *testing.T) { required: false `), &in) assert.NilError(t, err) - env, err := transformEnvFile(in, tree.NewPath("service.test.env_file")) + env, err := transformEnvFile(in, tree.NewPath("service.test.env_file"), false) assert.NilError(t, err) assert.DeepEqual(t, env, []any{ map[string]any{ diff --git a/transform/extends.go b/transform/extends.go index 9f77176e..e0f9be2d 100644 --- a/transform/extends.go +++ b/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/transform/external.go b/transform/external.go index 9e24eb83..be718f03 100644 --- a/transform/external.go +++ b/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/transform/external_test.go b/transform/external_test.go index 56fa33a7..96eae430 100644 --- a/transform/external_test.go +++ b/transform/external_test.go @@ -26,7 +26,7 @@ import ( func TestNotExternal(t *testing.T) { ssh, err := transformMaybeExternal(map[string]any{ "driver": "foo", - }, tree.NewPath("resources.test")) + }, tree.NewPath("resources.test"), false) assert.NilError(t, err) assert.DeepEqual(t, ssh, map[string]any{ "driver": "foo", @@ -37,7 +37,7 @@ func TestExternalNamed(t *testing.T) { ssh, err := transformMaybeExternal(map[string]any{ "external": true, "name": "foo", - }, tree.NewPath("resources.test")) + }, tree.NewPath("resources.test"), false) assert.NilError(t, err) assert.DeepEqual(t, ssh, map[string]any{ "external": true, @@ -48,7 +48,7 @@ func TestExternalNamed(t *testing.T) { func TestExternalUnnamed(t *testing.T) { ssh, err := transformMaybeExternal(map[string]any{ "external": true, - }, tree.NewPath("resources.test")) + }, tree.NewPath("resources.test"), false) assert.NilError(t, err) assert.DeepEqual(t, ssh, map[string]any{ "external": true, @@ -60,7 +60,7 @@ func TestExternalLegacy(t *testing.T) { "external": map[string]any{ "name": "foo", }, - }, tree.NewPath("resources.test")) + }, tree.NewPath("resources.test"), false) assert.NilError(t, err) assert.DeepEqual(t, ssh, map[string]any{ "external": true, @@ -74,7 +74,7 @@ func TestExternalLegacyNamed(t *testing.T) { "name": "foo", }, "name": "foo", - }, tree.NewPath("resources.test")) + }, tree.NewPath("resources.test"), false) assert.NilError(t, err) assert.DeepEqual(t, ssh, map[string]any{ "external": true, diff --git a/transform/include.go b/transform/include.go index 2488483a..8a80439e 100644 --- a/transform/include.go +++ b/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/transform/mapping.go b/transform/mapping.go index b247f84f..007aa9ed 100644 --- a/transform/mapping.go +++ b/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/transform/ports.go b/transform/ports.go index 75a6fb65..de11b942 100644 --- a/transform/ports.go +++ b/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/transform/ports_test.go b/transform/ports_test.go new file mode 100644 index 00000000..2722bfa5 --- /dev/null +++ b/transform/ports_test.go @@ -0,0 +1,89 @@ +/* + 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 transform + +import ( + "reflect" + "testing" + + "github.com/compose-spec/compose-go/v2/tree" + "gotest.tools/v3/assert" +) + +func Test_transformPorts(t *testing.T) { + tests := []struct { + name string + yaml any + ignoreParseError bool + want any + wantErr string + }{ + { + name: "[]string", + yaml: []any{ + "127.0.0.1:8080:80", + "127.0.0.1:8081:81", + }, + want: []any{ + map[string]any{ + "host_ip": "127.0.0.1", + "mode": "ingress", + "protocol": "tcp", + "published": "8080", + "target": uint32(80), + }, + map[string]any{ + "host_ip": "127.0.0.1", + "mode": "ingress", + "protocol": "tcp", + "published": "8081", + "target": uint32(81), + }, + }, + }, + { + name: "invalid IP", + yaml: []any{ + "127.0.1:8080:80", + }, + wantErr: "Invalid ip address: 127.0.1", + }, + { + name: "ignore invalid IP", + yaml: []any{ + "${HOST_IP}:${HOST_PORT}:80", + }, + ignoreParseError: true, + want: []any{ + "${HOST_IP}:${HOST_PORT}:80", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := transformPorts(tt.yaml, tree.NewPath("services.foo.ports"), tt.ignoreParseError) + if tt.wantErr != "" { + assert.Error(t, err, tt.wantErr) + return + } + assert.NilError(t, err) + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("transformPorts() got = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/transform/secrets.go b/transform/secrets.go index 1e84d18d..c2db1352 100644 --- a/transform/secrets.go +++ b/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/transform/services.go b/transform/services.go index c1411452..960c3e7e 100644 --- a/transform/services.go +++ b/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/transform/ssh.go b/transform/ssh.go index 48e2a6cf..2663461e 100644 --- a/transform/ssh.go +++ b/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/transform/ssh_test.go b/transform/ssh_test.go index 7f4caaac..d66a19d9 100644 --- a/transform/ssh_test.go +++ b/transform/ssh_test.go @@ -27,7 +27,7 @@ func TestSSHConfig(t *testing.T) { ssh, err := transformSSH([]any{ "default", "foo=bar", - }, tree.NewPath("test")) + }, tree.NewPath("test"), false) assert.NilError(t, err) assert.DeepEqual(t, ssh, map[string]any{ "default": nil, diff --git a/transform/ulimits.go b/transform/ulimits.go index de7784c3..57cce4fb 100644 --- a/transform/ulimits.go +++ b/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/transform/volume.go b/transform/volume.go index e6dd8795..b08e8b1a 100644 --- a/transform/volume.go +++ b/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)