diff --git a/diff/diff_test.go b/diff/diff_test.go index b3965e80..0a9e3ea6 100644 --- a/diff/diff_test.go +++ b/diff/diff_test.go @@ -602,6 +602,23 @@ spec: containers: - name: app image: demo:v1 + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: node-type + operator: In + values: + - standard + priorities: + - 1 + booleanArrayField: + - true + multiTypeField: + - 3 + - true + - "string" ` newManifest := ` apiVersion: apps/v1 @@ -616,6 +633,23 @@ spec: containers: - name: app image: demo:v2 + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: node-type + operator: In + values: + - dedicated + priorities: + - 2 + booleanArrayField: + - false + multiTypeField: + - false + - "new-string" + - 2 ` oldIndex := manifest.Parse([]byte(oldManifest), "prod", true) newIndex := manifest.Parse([]byte(newManifest), "prod", true) @@ -633,7 +667,7 @@ spec: require.Equal(t, "Deployment", entry.Kind) require.Equal(t, "prod", entry.Namespace) require.Equal(t, "web", entry.Name) - require.Len(t, entry.Changes, 2) + require.Len(t, entry.Changes, 8) replicasChange, ok := findChange(entry.Changes, "spec", "replicas") require.True(t, ok) require.InDelta(t, float64(2), replicasChange.OldValue, 0.001) @@ -643,6 +677,36 @@ spec: require.True(t, ok) require.Equal(t, "demo:v1", imageChange.OldValue) require.Equal(t, "demo:v2", imageChange.NewValue) + + affinityChange, ok := findChange(entry.Changes, "spec.template.spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchExpressions[0].values", "0") + require.True(t, ok) + require.Equal(t, "standard", affinityChange.OldValue) + require.Equal(t, "dedicated", affinityChange.NewValue) + + priorityChange, ok := findChange(entry.Changes, "spec.template.spec.priorities", "0") + require.True(t, ok) + require.InDelta(t, float64(1), priorityChange.OldValue, 0.001) + require.InDelta(t, float64(2), priorityChange.NewValue, 0.001) + + booleanArrayFieldChange, ok := findChange(entry.Changes, "spec.template.spec.booleanArrayField", "0") + require.True(t, ok) + require.Equal(t, true, booleanArrayFieldChange.OldValue) + require.Equal(t, false, booleanArrayFieldChange.NewValue) + + multiTypeFieldChange0, ok := findChange(entry.Changes, "spec.template.spec.multiTypeField", "0") + require.True(t, ok) + require.InDelta(t, float64(3), multiTypeFieldChange0.OldValue, 0.001) + require.Equal(t, false, multiTypeFieldChange0.NewValue) + + multiTypeFieldChange1, ok := findChange(entry.Changes, "spec.template.spec.multiTypeField", "1") + require.True(t, ok) + require.Equal(t, true, multiTypeFieldChange1.OldValue) + require.Equal(t, "new-string", multiTypeFieldChange1.NewValue) + + multiTypeFieldChange2, ok := findChange(entry.Changes, "spec.template.spec.multiTypeField", "2") + require.True(t, ok) + require.Equal(t, "string", multiTypeFieldChange2.OldValue) + require.InDelta(t, float64(2), multiTypeFieldChange2.NewValue, 0.001) } func TestStructuredOutputAddAndRemove(t *testing.T) { @@ -654,7 +718,20 @@ kind: Job metadata: name: migrate namespace: ops -spec: {} +spec: + restartPolicy: "Never" + containers: + - name: app + image: demo:v1 + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: node-type + operator: In + values: + - standard ` newIndex := manifest.Parse([]byte(newManifest), "ops", true) @@ -665,6 +742,7 @@ spec: {} var entries []StructuredEntry require.NoError(t, json.Unmarshal(buf.Bytes(), &entries)) require.Len(t, entries, 1) + require.Equal(t, "ADD", entries[0].ChangeType) require.True(t, entries[0].ResourceStatus.NewExists) require.False(t, entries[0].ResourceStatus.OldExists) diff --git a/diff/structured.go b/diff/structured.go index 6b0f1238..55d7f9f1 100644 --- a/diff/structured.go +++ b/diff/structured.go @@ -271,6 +271,18 @@ func diffArrayNodes(changes *[]FieldChange, tokens []string, oldNode, newNode in if reflect.DeepEqual(oldVal, newVal) { continue } + // createNodePatch only supports JSON objects; handle scalar values directly + if isScalarValue(oldVal) && isScalarValue(newVal) { + path, field := splitTokens(next) + *changes = append(*changes, FieldChange{ + Path: path, + Field: field, + Change: "replace", + OldValue: oldVal, + NewValue: newVal, + }) + continue + } subPatch, err := createNodePatch(oldVal, newVal) if err != nil { return err @@ -311,6 +323,18 @@ func diffArrayNodes(changes *[]FieldChange, tokens []string, oldNode, newNode in return nil } +func isScalarValue(value interface{}) bool { + switch value.(type) { + case string, bool, + float32, float64, + int, int8, int16, int32, int64, + uint, uint8, uint16, uint32, uint64: + return true + default: + return false + } +} + func createNodePatch(oldNode, newNode interface{}) (interface{}, error) { oldJSON, err := json.Marshal(oldNode) if err != nil {