Skip to content

Commit b156f19

Browse files
authored
argocd_project: introduce schema versioning and migration (argoproj-labs#58)
1 parent 603dc2c commit b156f19

File tree

3 files changed

+314
-3
lines changed

3 files changed

+314
-3
lines changed

argocd/resource_argocd_project.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,15 @@ func resourceArgoCDProject() *schema.Resource {
2323
},
2424
Schema: map[string]*schema.Schema{
2525
"metadata": metadataSchema("appprojects.argoproj.io"),
26-
"spec": projectSpecSchema(),
26+
"spec": projectSpecSchemaV1(),
27+
},
28+
SchemaVersion: 1,
29+
StateUpgraders: []schema.StateUpgrader{
30+
{
31+
Type: resourceArgoCDProjectV0().CoreConfigSchema().ImpliedType(),
32+
Upgrade: resourceArgoCDProjectStateUpgradeV0,
33+
Version: 0,
34+
},
2735
},
2836
}
2937
}

argocd/schema_project.go

Lines changed: 215 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,162 @@
11
package argocd
22

3-
import "github.com/hashicorp/terraform-plugin-sdk/helper/schema"
3+
import (
4+
"fmt"
5+
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
6+
)
47

5-
func projectSpecSchema() *schema.Schema {
8+
func projectSpecSchemaV0() *schema.Schema {
9+
return &schema.Schema{
10+
Type: schema.TypeList,
11+
MinItems: 1,
12+
MaxItems: 1,
13+
Description: "ArgoCD App project resource specs. Required attributes: destination, source_repos.",
14+
Required: true,
15+
Elem: &schema.Resource{
16+
Schema: map[string]*schema.Schema{
17+
"cluster_resource_whitelist": {
18+
Type: schema.TypeSet,
19+
Optional: true,
20+
Elem: &schema.Resource{
21+
Schema: map[string]*schema.Schema{
22+
"group": {
23+
Type: schema.TypeString,
24+
ValidateFunc: validateGroupName,
25+
Optional: true,
26+
},
27+
"kind": {
28+
Type: schema.TypeString,
29+
Optional: true,
30+
},
31+
},
32+
},
33+
},
34+
"description": {
35+
Type: schema.TypeString,
36+
Optional: true,
37+
},
38+
"destination": {
39+
Type: schema.TypeSet,
40+
Required: true,
41+
Elem: &schema.Resource{
42+
Schema: map[string]*schema.Schema{
43+
"server": {
44+
Type: schema.TypeString,
45+
Optional: true,
46+
},
47+
"namespace": {
48+
Type: schema.TypeString,
49+
Required: true,
50+
},
51+
"name": {
52+
Type: schema.TypeString,
53+
Optional: true,
54+
Description: "Name of the destination cluster which can be used instead of server.",
55+
},
56+
},
57+
},
58+
},
59+
"namespace_resource_blacklist": {
60+
Type: schema.TypeSet,
61+
Optional: true,
62+
Elem: &schema.Resource{
63+
Schema: map[string]*schema.Schema{
64+
"group": {
65+
Type: schema.TypeString,
66+
Optional: true,
67+
},
68+
"kind": {
69+
Type: schema.TypeString,
70+
Optional: true,
71+
},
72+
},
73+
},
74+
},
75+
"orphaned_resources": {
76+
Type: schema.TypeMap,
77+
Optional: true,
78+
Elem: &schema.Schema{Type: schema.TypeBool},
79+
},
80+
"role": {
81+
Type: schema.TypeList,
82+
Optional: true,
83+
Elem: &schema.Resource{
84+
Schema: map[string]*schema.Schema{
85+
"description": {
86+
Type: schema.TypeString,
87+
Optional: true,
88+
},
89+
"groups": {
90+
Type: schema.TypeList,
91+
Optional: true,
92+
Elem: &schema.Schema{Type: schema.TypeString},
93+
},
94+
"name": {
95+
Type: schema.TypeString,
96+
ValidateFunc: validateRoleName,
97+
Required: true,
98+
},
99+
"policies": {
100+
Type: schema.TypeList,
101+
Required: true,
102+
Elem: &schema.Schema{Type: schema.TypeString},
103+
},
104+
},
105+
},
106+
},
107+
"source_repos": {
108+
Type: schema.TypeList,
109+
Required: true,
110+
Elem: &schema.Schema{Type: schema.TypeString},
111+
},
112+
"sync_window": {
113+
Type: schema.TypeList,
114+
Optional: true,
115+
Elem: &schema.Resource{
116+
Schema: map[string]*schema.Schema{
117+
"applications": {
118+
Type: schema.TypeList,
119+
Optional: true,
120+
Elem: &schema.Schema{Type: schema.TypeString},
121+
},
122+
"clusters": {
123+
Type: schema.TypeList,
124+
Optional: true,
125+
Elem: &schema.Schema{Type: schema.TypeString},
126+
},
127+
"duration": {
128+
Type: schema.TypeString,
129+
ValidateFunc: validateSyncWindowDuration,
130+
Optional: true,
131+
},
132+
"kind": {
133+
Type: schema.TypeString,
134+
ValidateFunc: validateSyncWindowKind,
135+
Optional: true,
136+
},
137+
"manual_sync": {
138+
Type: schema.TypeBool,
139+
Optional: true,
140+
},
141+
"namespaces": {
142+
Type: schema.TypeList,
143+
Optional: true,
144+
Elem: &schema.Schema{Type: schema.TypeString},
145+
},
146+
"schedule": {
147+
Type: schema.TypeString,
148+
ValidateFunc: validateSyncWindowSchedule,
149+
Optional: true,
150+
},
151+
},
152+
},
153+
},
154+
},
155+
},
156+
}
157+
}
158+
159+
func projectSpecSchemaV1() *schema.Schema {
6160
return &schema.Schema{
7161
Type: schema.TypeList,
8162
MinItems: 1,
@@ -186,3 +340,62 @@ func projectSpecSchema() *schema.Schema {
186340
},
187341
}
188342
}
343+
344+
func resourceArgoCDProjectV0() *schema.Resource {
345+
return &schema.Resource{
346+
Schema: map[string]*schema.Schema{
347+
"metadata": metadataSchema("appprojects.argoproj.io"),
348+
"spec": projectSpecSchemaV0(),
349+
},
350+
}
351+
}
352+
353+
func resourceArgoCDProjectStateUpgradeV0(rawState map[string]interface{}, _ interface{}) (map[string]interface{}, error) {
354+
orphanedResources := rawState["spec"].([]map[string]interface{})[0]["orphaned_resources"]
355+
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,
385+
},
386+
},
387+
},
388+
},
389+
},
390+
}),
391+
[]interface{}{map[string]interface{}{"warn": warn}},
392+
)
393+
rawState["spec"].([]map[string]interface{})[0]["orphaned_resources"] = newOrphanedResources
394+
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)
399+
}
400+
return rawState, nil
401+
}

argocd/schema_project_test.go

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package argocd
2+
3+
import (
4+
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
5+
"testing"
6+
)
7+
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},
13+
},
14+
},
15+
}
16+
}
17+
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,
25+
},
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+
},
45+
},
46+
},
47+
},
48+
}),
49+
[]interface{}{map[string]interface{}{"warn": true}},
50+
)
51+
return map[string]interface{}{
52+
"spec": []map[string]interface{}{
53+
{
54+
"orphaned_resources": newOrphanedResources,
55+
},
56+
},
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+
}{
66+
{
67+
"source < v0.5.0",
68+
testResourceArgoCDProjectStateDataV1(),
69+
testResourceArgoCDProjectStateDataV0(),
70+
},
71+
{
72+
"source < v1.1.0, >= v0.5.0",
73+
testResourceArgoCDProjectStateDataV1(),
74+
testResourceArgoCDProjectStateDataV1(),
75+
},
76+
}
77+
for _, tc := range cases {
78+
t.Run(tc.name, func(t *testing.T) {
79+
_actual, err := resourceArgoCDProjectStateUpgradeV0(tc.sourceState, nil)
80+
if err != nil {
81+
t.Fatalf("error migrating state: %s", err)
82+
}
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)
87+
}
88+
})
89+
}
90+
}

0 commit comments

Comments
 (0)