Repository: compose-spec/compose-go
Pull request #552 (2024-01-26 13:09:28)

!override can be used to replace value
Introduce `!override` so one can replace any entry in compose.override.yaml, while `!reset` would remove but not allow to re-define
i.e:
- `foo: !reset null` removes `foo` attribute from merged yaml (value is ignored)
- `foo: !override hello` overrides any previous `foo` attribute value with new value `"hello"`

**TODO**: document this on the compose spec


fixes https://github.com/docker/compose/issues/11408

laurazard: This makes it read a bit weird, no? If `!reset` only removes and does not allow to redefine, having to write:
```yaml
foo: !reset null
```
is a bit weird, I would expect to just write
```yaml
foo: !reset
```

Can we assume `null` when there is no value for `!reset`? (ignore this if that's already something we can do)
ndeloof: @laurazard I we one can, but `!reset` is just a yaml tag, yaml syntax requires a value. Can also use:
```yaml
foo: !reset {}
```

laurazard: RIght, I had a vague idea from when I was looking into the tags there was some reason why it was required. Regardless, that's unrelated to this change, `!override` sounds good to me.



Patch:
diff --git a/loader/null.go b/loader/reset.go
similarity index 97%
rename from loader/null.go
rename to loader/reset.go
index d3e99f4..3c615ef 100644
--- a/loader/null.go
+++ b/loader/reset.go
@@ -44,6 +44,10 @@ func (p *ResetProcessor) resolveReset(node *yaml.Node, path tree.Path) (*yaml.No
 		p.paths = append(p.paths, path)
 		return nil, nil
 	}
+	if node.Tag == "!override" {
+		p.paths = append(p.paths, path)
+		return node, nil
+	}
 	switch node.Kind {
 	case yaml.SequenceNode:
 		var nodes []*yaml.Node
diff --git a/override/merge.go b/override/merge.go
index 9120b3f..2196d79 100644
--- a/override/merge.go
+++ b/override/merge.go
@@ -62,6 +62,9 @@ func mergeYaml(e any, o any, p tree.Path) (any, error) {
 			return merged, nil
 		}
 	}
+	if o == nil {
+		return e, nil
+	}
 	switch value := e.(type) {
 	case map[string]any:
 		other, ok := o.(map[string]any)


Test patch:
diff --git a/loader/reset_test.go b/loader/reset_test.go
new file mode 100644
index 0000000..71e7dc3
--- /dev/null
+++ b/loader/reset_test.go
@@ -0,0 +1,83 @@
+/*
+   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 loader
+
+import (
+	"testing"
+
+	"github.com/compose-spec/compose-go/v2/types"
+	"gotest.tools/v3/assert"
+)
+
+func TestResetRemove(t *testing.T) {
+	p, err := Load(types.ConfigDetails{
+		ConfigFiles: []types.ConfigFile{
+			{
+				Filename: "(inline)",
+				Content: []byte(`
+name: test-reset
+networks:
+  test:
+    name: test
+    external: true
+`),
+			},
+			{
+				Filename: "(override)",
+				Content: []byte(`
+networks:
+  test: !reset {}
+`),
+			},
+		},
+	}, func(options *Options) {
+		options.SkipNormalization = true
+		options.SkipConsistencyCheck = true
+	})
+	assert.NilError(t, err)
+	_, ok := p.Networks["test"]
+	assert.Check(t, !ok)
+}
+
+func TestOverrideReplace(t *testing.T) {
+	p, err := Load(types.ConfigDetails{
+		ConfigFiles: []types.ConfigFile{
+			{
+				Filename: "(inline)",
+				Content: []byte(`
+name: test-override
+networks:
+  test:
+    name: test
+    external: true
+`),
+			},
+			{
+				Filename: "(override)",
+				Content: []byte(`
+networks:
+  test: !override {}
+`),
+			},
+		},
+	}, func(options *Options) {
+		options.SkipNormalization = true
+		options.SkipConsistencyCheck = true
+	})
+	assert.NilError(t, err)
+	assert.Check(t, p.Networks["test"].External == false)
+}