Skip to content

Commit 77e3cd2

Browse files
authored
Merge pull request #2206 from loft-sh/fix-op-patching-adds
fix: op patching to not add elements if key already exists
2 parents 4543273 + 70af82f commit 77e3cd2

3 files changed

Lines changed: 109 additions & 24 deletions

File tree

e2e/tests/config/config.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,51 @@ var _ = DevSpaceDescribe("config", func() {
244244
framework.ExpectEqual(latestConfig.Deployments["test2"].Name, "test2")
245245
})
246246

247+
ginkgo.It("should not be able to add in patch if key already exists", func() {
248+
tempDir, err := framework.CopyToTempDir("tests/config/testdata/patch-add-dont-overwrite-existing-key")
249+
framework.ExpectNoError(err)
250+
defer framework.CleanupTempDir(initialDir, tempDir)
251+
252+
configBuffer := &bytes.Buffer{}
253+
printCmd := &cmd.PrintCmd{
254+
GlobalFlags: &flags.GlobalFlags{
255+
ConfigPath: "devspace.yaml",
256+
Profiles: []string{"deploy"},
257+
},
258+
Out: configBuffer,
259+
SkipInfo: true,
260+
}
261+
262+
err = printCmd.Run(f)
263+
framework.ExpectError(err)
264+
})
265+
266+
ginkgo.It("should be able to add in patch if key does not already exists", func() {
267+
tempDir, err := framework.CopyToTempDir("tests/config/testdata/patch-add-dont-overwrite-existing-key")
268+
framework.ExpectNoError(err)
269+
defer framework.CleanupTempDir(initialDir, tempDir)
270+
271+
configBuffer := &bytes.Buffer{}
272+
printCmd := &cmd.PrintCmd{
273+
GlobalFlags: &flags.GlobalFlags{
274+
ConfigPath: "devspace.yaml",
275+
Profiles: []string{"patch-ok"},
276+
},
277+
Out: configBuffer,
278+
SkipInfo: true,
279+
}
280+
281+
err = printCmd.Run(f)
282+
framework.ExpectNoError(err)
283+
284+
latestConfig := &latest.Config{}
285+
err = yaml.Unmarshal(configBuffer.Bytes(), latestConfig)
286+
framework.ExpectNoError(err)
287+
288+
// validate config
289+
framework.ExpectEqual(string(latestConfig.Images["importme"].RebuildStrategy), "ignoreContextChanges")
290+
})
291+
247292
ginkgo.It("should load profile cached and uncached", func() {
248293
tempDir, err := framework.CopyToTempDir("tests/config/testdata/profile")
249294
framework.ExpectNoError(err)
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
version: v1beta11
2+
images:
3+
importme:
4+
image: golang:1.17
5+
context: ../..
6+
profiles:
7+
- name: deploy
8+
patches:
9+
- op: add
10+
path: images.importme.context
11+
value: ../..
12+
- name: patch-ok
13+
patches:
14+
- op: add
15+
path: images.importme.rebuildStrategy
16+
value: ignoreContextChanges

pkg/devspace/config/loader/patch/operation.go

Lines changed: 48 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package patch
22

33
import (
44
"fmt"
5-
65
"github.com/vmware-labs/yaml-jsonpath/pkg/yamlpath"
76
yaml "gopkg.in/yaml.v3"
87
)
@@ -35,38 +34,63 @@ func (op *Operation) Perform(doc *yaml.Node) error {
3534
return err
3635
}
3736

38-
if len(matches) == 0 {
39-
if op.Op == opAdd {
40-
matches, err = getParents(doc, op.Path)
41-
if err != nil {
42-
return fmt.Errorf("could not add using path: %s", op.Path)
37+
if len(matches) == 0 && op.Op != opAdd {
38+
return fmt.Errorf("%s operation does not apply: doc is missing path: %s", op.Op, op.Path)
39+
}
40+
41+
// function that will actually perform the patch operation
42+
var opFunc func(parent *yaml.Node, match *yaml.Node)
43+
44+
switch op.Op {
45+
case opAdd:
46+
opFunc = op.add
47+
48+
if len(matches) > 0 {
49+
if matches[0].Kind == yaml.MappingNode || matches[0].Kind == yaml.SequenceNode {
50+
break
4351
}
52+
}
53+
54+
originalMatches := matches
4455

45-
parentPath := op.Path.getParentPath()
46-
propertName := op.Path.getChildName()
47-
if op.Value != nil {
48-
propertyValue := op.Value.Content[0]
49-
op.Value = createMappingNode(propertName, propertyValue)
56+
matches, err = getParents(doc, op.Path)
57+
if err != nil {
58+
return fmt.Errorf("could not add using path: %s", op.Path)
59+
}
60+
61+
if len(matches) > 0 && len(originalMatches) > 0 {
62+
if matches[0].Kind == yaml.SequenceNode {
63+
matches = originalMatches
64+
break
5065
}
51-
op.Path = OpPath(parentPath)
52-
} else {
53-
return fmt.Errorf("%s operation does not apply: doc is missing path: %s", op.Op, op.Path)
66+
67+
// we are trying to overwrite an existing key in a map, don't do that!
68+
return fmt.Errorf(
69+
"attempting add operation for non array/object path '%s' which already exists",
70+
op.Path,
71+
)
5472
}
73+
74+
parentPath := op.Path.getParentPath()
75+
propertyName := op.Path.getChildName()
76+
if op.Value != nil {
77+
propertyValue := op.Value.Content[0]
78+
op.Value = createMappingNode(propertyName, propertyValue)
79+
}
80+
op.Path = OpPath(parentPath)
81+
82+
case opRemove:
83+
opFunc = op.remove
84+
case opReplace:
85+
opFunc = op.replace
86+
default:
87+
return fmt.Errorf("unexpected op: %s", op.Op)
5588
}
5689

5790
for _, match := range matches {
5891
parent := find(doc, containsChild(match))
5992

60-
switch op.Op {
61-
case opAdd:
62-
op.add(parent, match)
63-
case opRemove:
64-
op.remove(parent, match)
65-
case opReplace:
66-
op.replace(parent, match)
67-
default:
68-
return fmt.Errorf("unexpected op: %s", op.Op)
69-
}
93+
opFunc(parent, match)
7094
}
7195

7296
return nil

0 commit comments

Comments
 (0)