Skip to content

Commit c968929

Browse files
authored
Fix argocd_project v0 to v1 schema migration edge case (#60)
1 parent b156f19 commit c968929

File tree

2 files changed

+210
-99
lines changed

2 files changed

+210
-99
lines changed

argocd/schema_project.go

Lines changed: 43 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -351,51 +351,54 @@ func resourceArgoCDProjectV0() *schema.Resource {
351351
}
352352

353353
func resourceArgoCDProjectStateUpgradeV0(rawState map[string]interface{}, _ interface{}) (map[string]interface{}, error) {
354-
orphanedResources := rawState["spec"].([]map[string]interface{})[0]["orphaned_resources"]
354+
spec := rawState["spec"].([]map[string]interface{})
355+
if len(spec) > 0 {
356+
if orphanedResources, ok := spec[0]["orphaned_resources"]; ok {
357+
switch orphanedResources.(type) {
355358

356-
switch orphanedResources.(type) {
357-
358-
// <= v0.4.8
359-
case map[string]bool:
360-
warn := orphanedResources.(map[string]bool)["warn"]
361-
newOrphanedResources := schema.NewSet(
362-
schema.HashResource(&schema.Resource{
363-
Schema: map[string]*schema.Schema{
364-
"warn": {
365-
Type: schema.TypeBool,
366-
Optional: true,
367-
},
368-
"ignore": {
369-
Type: schema.TypeSet,
370-
Optional: true,
371-
Elem: &schema.Resource{
372-
Schema: map[string]*schema.Schema{
373-
"group": {
374-
Type: schema.TypeString,
375-
ValidateFunc: validateGroupName,
376-
Optional: true,
377-
},
378-
"kind": {
379-
Type: schema.TypeString,
380-
Optional: true,
381-
},
382-
"name": {
383-
Type: schema.TypeString,
384-
Optional: true,
359+
// <= v0.4.8
360+
case map[string]bool:
361+
warn := orphanedResources.(map[string]bool)["warn"]
362+
newOrphanedResources := schema.NewSet(
363+
schema.HashResource(&schema.Resource{
364+
Schema: map[string]*schema.Schema{
365+
"warn": {
366+
Type: schema.TypeBool,
367+
Optional: true,
368+
},
369+
"ignore": {
370+
Type: schema.TypeSet,
371+
Optional: true,
372+
Elem: &schema.Resource{
373+
Schema: map[string]*schema.Schema{
374+
"group": {
375+
Type: schema.TypeString,
376+
ValidateFunc: validateGroupName,
377+
Optional: true,
378+
},
379+
"kind": {
380+
Type: schema.TypeString,
381+
Optional: true,
382+
},
383+
"name": {
384+
Type: schema.TypeString,
385+
Optional: true,
386+
},
387+
},
385388
},
386389
},
387390
},
388-
},
389-
},
390-
}),
391-
[]interface{}{map[string]interface{}{"warn": warn}},
392-
)
393-
rawState["spec"].([]map[string]interface{})[0]["orphaned_resources"] = newOrphanedResources
391+
}),
392+
[]interface{}{map[string]interface{}{"warn": warn}},
393+
)
394+
rawState["spec"].([]map[string]interface{})[0]["orphaned_resources"] = newOrphanedResources
394395

395-
// >= v0.5.0 <= v1.1.0
396-
case *schema.Set:
397-
default:
398-
return nil, fmt.Errorf("error during state migration v0 to v1, unsupported type for 'orphaned_resources': %s", orphanedResources)
396+
// >= v0.5.0 <= v1.1.0
397+
case *schema.Set:
398+
default:
399+
return nil, fmt.Errorf("error during state migration v0 to v1, unsupported type for 'orphaned_resources': %s", orphanedResources)
400+
}
401+
}
399402
}
400403
return rawState, nil
401404
}

argocd/schema_project_test.go

Lines changed: 167 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -2,89 +2,197 @@ package argocd
22

33
import (
44
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
5+
"reflect"
56
"testing"
67
)
78

8-
func testResourceArgoCDProjectStateDataV0() map[string]interface{} {
9-
return map[string]interface{}{
10-
"spec": []map[string]interface{}{
11-
{
12-
"orphaned_resources": map[string]bool{"warn": true},
9+
func orphanedResourcesSchemaSetFuncV1() schema.SchemaSetFunc {
10+
return schema.HashResource(&schema.Resource{
11+
Schema: map[string]*schema.Schema{
12+
"warn": {
13+
Type: schema.TypeBool,
14+
Optional: true,
15+
},
16+
"ignore": {
17+
Type: schema.TypeSet,
18+
Optional: true,
19+
Elem: &schema.Resource{
20+
Schema: map[string]*schema.Schema{
21+
"group": {
22+
Type: schema.TypeString,
23+
ValidateFunc: validateGroupName,
24+
Optional: true,
25+
},
26+
"kind": {
27+
Type: schema.TypeString,
28+
Optional: true,
29+
},
30+
"name": {
31+
Type: schema.TypeString,
32+
Optional: true,
33+
},
34+
},
35+
},
1336
},
1437
},
15-
}
38+
})
1639
}
1740

18-
func testResourceArgoCDProjectStateDataV1() map[string]interface{} {
19-
newOrphanedResources := schema.NewSet(
20-
schema.HashResource(&schema.Resource{
21-
Schema: map[string]*schema.Schema{
22-
"warn": {
23-
Type: schema.TypeBool,
24-
Optional: true,
41+
func TestResourceArgoCDProjectStateUpgradeV0(t *testing.T) {
42+
type projectStateUpgradeTestCases []struct {
43+
name string
44+
expectedState map[string]interface{}
45+
sourceState map[string]interface{}
46+
}
47+
48+
cases := projectStateUpgradeTestCases{
49+
{
50+
name: "source_<_v0.5.0_with_warn",
51+
sourceState: map[string]interface{}{
52+
"spec": []map[string]interface{}{
53+
{
54+
"orphaned_resources": map[string]bool{"warn": true},
55+
},
2556
},
26-
"ignore": {
27-
Type: schema.TypeSet,
28-
Optional: true,
29-
Elem: &schema.Resource{
30-
Schema: map[string]*schema.Schema{
31-
"group": {
32-
Type: schema.TypeString,
33-
ValidateFunc: validateGroupName,
34-
Optional: true,
35-
},
36-
"kind": {
37-
Type: schema.TypeString,
38-
Optional: true,
39-
},
40-
"name": {
41-
Type: schema.TypeString,
42-
Optional: true,
43-
},
44-
},
57+
},
58+
expectedState: map[string]interface{}{
59+
"spec": []map[string]interface{}{
60+
{
61+
"orphaned_resources": schema.NewSet(
62+
orphanedResourcesSchemaSetFuncV1(),
63+
[]interface{}{map[string]interface{}{"warn": true}},
64+
),
65+
},
66+
},
67+
},
68+
},
69+
{
70+
name: "source_<_v0.5.0_without_orphaned_resources",
71+
sourceState: map[string]interface{}{
72+
"spec": []map[string]interface{}{
73+
{
74+
"source_repos": []string{"*"},
4575
},
4676
},
4777
},
48-
}),
49-
[]interface{}{map[string]interface{}{"warn": true}},
50-
)
51-
return map[string]interface{}{
52-
"spec": []map[string]interface{}{
53-
{
54-
"orphaned_resources": newOrphanedResources,
78+
expectedState: map[string]interface{}{
79+
"spec": []map[string]interface{}{
80+
{
81+
"source_repos": []string{"*"},
82+
},
83+
},
5584
},
5685
},
57-
}
58-
}
59-
60-
func TestResourceArgoCDProjectStateUpgradeV0(t *testing.T) {
61-
cases := []struct {
62-
name string
63-
expected map[string]interface{}
64-
sourceState map[string]interface{}
65-
}{
6686
{
67-
"source < v0.5.0",
68-
testResourceArgoCDProjectStateDataV1(),
69-
testResourceArgoCDProjectStateDataV0(),
87+
name: "source_<_v0.5.0_with_empty_orphaned_resources",
88+
sourceState: map[string]interface{}{
89+
"spec": []map[string]interface{}{
90+
{
91+
"orphaned_resources": map[string]bool{},
92+
},
93+
},
94+
},
95+
expectedState: map[string]interface{}{
96+
"spec": []map[string]interface{}{
97+
{
98+
"orphaned_resources": schema.NewSet(
99+
orphanedResourcesSchemaSetFuncV1(),
100+
[]interface{}{map[string]interface{}{"warn": false}},
101+
),
102+
},
103+
},
104+
},
105+
},
106+
{
107+
name: "source_<_v1.1.0_>=_0.4.8_with_warn",
108+
sourceState: map[string]interface{}{
109+
"spec": []map[string]interface{}{
110+
{
111+
"orphaned_resources": schema.NewSet(
112+
orphanedResourcesSchemaSetFuncV1(),
113+
[]interface{}{map[string]interface{}{"warn": true}},
114+
),
115+
"source_repos": []string{"*"},
116+
},
117+
},
118+
},
119+
expectedState: map[string]interface{}{
120+
"spec": []map[string]interface{}{
121+
{
122+
"orphaned_resources": schema.NewSet(
123+
orphanedResourcesSchemaSetFuncV1(),
124+
[]interface{}{map[string]interface{}{"warn": true}},
125+
),
126+
"source_repos": []string{"*"},
127+
},
128+
},
129+
},
70130
},
71131
{
72-
"source < v1.1.0, >= v0.5.0",
73-
testResourceArgoCDProjectStateDataV1(),
74-
testResourceArgoCDProjectStateDataV1(),
132+
name: "source_<_v1.1.1_without_orphaned_resources",
133+
sourceState: map[string]interface{}{
134+
"spec": []map[string]interface{}{
135+
{
136+
"source_repos": []string{"*"},
137+
},
138+
},
139+
},
140+
expectedState: map[string]interface{}{
141+
"spec": []map[string]interface{}{
142+
{
143+
"source_repos": []string{"*"},
144+
},
145+
},
146+
},
75147
},
76148
}
149+
77150
for _, tc := range cases {
78151
t.Run(tc.name, func(t *testing.T) {
79-
_actual, err := resourceArgoCDProjectStateUpgradeV0(tc.sourceState, nil)
152+
actualState, err := resourceArgoCDProjectStateUpgradeV0(tc.sourceState, nil)
80153
if err != nil {
81154
t.Fatalf("error migrating state: %s", err)
82155
}
83-
expected := tc.expected["spec"].([]map[string]interface{})[0]["orphaned_resources"].(*schema.Set)
84-
actual := _actual["spec"].([]map[string]interface{})[0]["orphaned_resources"].(*schema.Set)
85-
if !expected.HashEqual(actual) {
86-
t.Fatalf("\n\nexpected:\n\n%#v\n\ngot:\n\n%#v\n\n", expected, actual)
156+
if !reflect.DeepEqual(actualState, tc.expectedState) {
157+
if expectedSet, ok := tc.expectedState["spec"].([]map[string]interface{})[0]["orphaned_resources"]; ok {
158+
159+
actualSet := actualState["spec"].([]map[string]interface{})[0]["orphaned_resources"].(*schema.Set)
160+
161+
if !expectedSet.(*schema.Set).HashEqual(actualSet) {
162+
t.Fatalf("\n\nexpected:\n\n%#v\n\ngot:\n\n%#v\n\n", expectedSet, actualSet)
163+
}
164+
// Cannot DeepEqual a pointer reference
165+
for k, _ := range tc.expectedState["spec"].([]map[string]interface{})[0] {
166+
av := actualState["spec"].([]map[string]interface{})[0][k]
167+
ev := tc.expectedState["spec"].([]map[string]interface{})[0][k]
168+
if k != "orphaned_resources" && !reflect.DeepEqual(av, ev) {
169+
t.Fatalf("\n\n[maps] expected:\n\n%#v\n\ngot:\n\n%#v\n\n", tc.expectedState, actualState)
170+
}
171+
}
172+
for k, av := range actualState["spec"].([]map[string]interface{})[0] {
173+
ev := tc.expectedState["spec"].([]map[string]interface{})[0][k]
174+
if k != "orphaned_resources" && !reflect.DeepEqual(av, ev) {
175+
t.Fatalf("\n\n[maps] expected:\n\n%#v\n\ngot:\n\n%#v\n\n", tc.expectedState, actualState)
176+
}
177+
}
178+
} else {
179+
// Cannot DeepEqual a pointer reference
180+
for k, _ := range tc.expectedState["spec"].([]map[string]interface{})[0] {
181+
av := actualState["spec"].([]map[string]interface{})[0][k]
182+
ev := tc.expectedState["spec"].([]map[string]interface{})[0][k]
183+
if k != "orphaned_resources" && !reflect.DeepEqual(av, ev) {
184+
t.Fatalf("\n\n[maps without set] expected:\n\n%#v\n\ngot:\n\n%#v\n\n", tc.expectedState, actualState)
185+
}
186+
}
187+
for k, av := range actualState["spec"].([]map[string]interface{})[0] {
188+
ev := tc.expectedState["spec"].([]map[string]interface{})[0][k]
189+
if k != "orphaned_resources" && !reflect.DeepEqual(av, ev) {
190+
t.Fatalf("\n\n[maps] expected:\n\n%#v\n\ngot:\n\n%#v\n\n", tc.expectedState, actualState)
191+
}
192+
}
193+
}
87194
}
195+
88196
})
89197
}
90198
}

0 commit comments

Comments
 (0)