From 201fe8525f1dc31415772b174d18869948256d79 Mon Sep 17 00:00:00 2001 From: Guillaume Lours Date: Thu, 10 Sep 2026 14:55:20 +0200 Subject: [PATCH] fix(transform): honor ignoreParseError in ssh and ulimits parsers ignoreParseError was threaded into every transform.Canonical parser's signature to satisfy the shared transformFunc type, but only ports and volume mounts actually used it; ssh and ulimits kept a no-op `_ bool`. A short-form value left unresolved on purpose (SkipInterpolation, e.g. docker compose publish's raw secret-scanning pass) crashed the loader outright instead of degrading gracefully like its siblings. Also fixes a copy-pasted "for external" error message in ulimits. Signed-off-by: Guillaume Lours --- transform/ssh.go | 5 ++- transform/ssh_test.go | 42 ++++++++++++++++++++++ transform/ulimits.go | 9 +++-- transform/ulimits_test.go | 76 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 129 insertions(+), 3 deletions(-) create mode 100644 transform/ulimits_test.go diff --git a/transform/ssh.go b/transform/ssh.go index 2663461e9..84a83684d 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, _ bool) (any, error) { +func transformSSH(data any, p tree.Path, ignoreParseError bool) (any, error) { switch v := data.(type) { case map[string]any: return v, nil @@ -37,6 +37,9 @@ func transformSSH(data any, p tree.Path, _ bool) (any, error) { id, path, ok := strings.Cut(s, "=") if !ok { if id != "default" { + if ignoreParseError { + return data, nil + } return nil, fmt.Errorf("invalid ssh key %q", s) } result[id] = nil diff --git a/transform/ssh_test.go b/transform/ssh_test.go index d66a19d9f..fae8d7a82 100644 --- a/transform/ssh_test.go +++ b/transform/ssh_test.go @@ -17,6 +17,7 @@ package transform import ( + "reflect" "testing" "github.com/compose-spec/compose-go/v2/tree" @@ -34,3 +35,44 @@ func TestSSHConfig(t *testing.T) { "foo": "bar", }) } + +func Test_transformSSH_ignoreParseError(t *testing.T) { + tests := []struct { + name string + yaml any + ignoreParseError bool + want any + wantErr string + }{ + { + name: "unresolved variable, error", + yaml: []any{ + "${SSH_AUTH_SOCK}", + }, + wantErr: `invalid ssh key "${SSH_AUTH_SOCK}"`, + }, + { + name: "unresolved variable, ignored", + yaml: []any{ + "${SSH_AUTH_SOCK}", + }, + ignoreParseError: true, + want: []any{ + "${SSH_AUTH_SOCK}", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := transformSSH(tt.yaml, tree.NewPath("test"), tt.ignoreParseError) + if tt.wantErr != "" { + assert.Error(t, err, tt.wantErr) + return + } + assert.NilError(t, err) + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("transformSSH() got = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/transform/ulimits.go b/transform/ulimits.go index 57cce4fb6..9a9962bd8 100644 --- a/transform/ulimits.go +++ b/transform/ulimits.go @@ -22,13 +22,18 @@ import ( "github.com/compose-spec/compose-go/v2/tree" ) -func transformUlimits(data any, p tree.Path, _ bool) (any, error) { +func transformUlimits(data any, p tree.Path, ignoreParseError bool) (any, error) { switch v := data.(type) { case map[string]any: return v, nil case int: return v, nil + case string: + if ignoreParseError { + return v, nil + } + return data, fmt.Errorf("%s: invalid type %T for ulimits", p, v) default: - return data, fmt.Errorf("%s: invalid type %T for external", p, v) + return data, fmt.Errorf("%s: invalid type %T for ulimits", p, v) } } diff --git a/transform/ulimits_test.go b/transform/ulimits_test.go new file mode 100644 index 000000000..1b99061d5 --- /dev/null +++ b/transform/ulimits_test.go @@ -0,0 +1,76 @@ +/* + 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_transformUlimits(t *testing.T) { + tests := []struct { + name string + yaml any + ignoreParseError bool + want any + wantErr string + }{ + { + name: "int", + yaml: 65535, + want: 65535, + }, + { + name: "long syntax", + yaml: map[string]any{ + "soft": 20000, + "hard": 40000, + }, + want: map[string]any{ + "soft": 20000, + "hard": 40000, + }, + }, + { + name: "unresolved variable, error", + yaml: "${NOFILE}", + wantErr: `test: invalid type string for ulimits`, + }, + { + name: "unresolved variable, ignored", + yaml: "${NOFILE}", + ignoreParseError: true, + want: "${NOFILE}", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := transformUlimits(tt.yaml, tree.NewPath("test"), tt.ignoreParseError) + if tt.wantErr != "" { + assert.Error(t, err, tt.wantErr) + return + } + assert.NilError(t, err) + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("transformUlimits() got = %v, want %v", got, tt.want) + } + }) + } +}