Skip to content

Commit

Permalink
argocd_project: introduce schema versioning and migration (#58)
Browse files Browse the repository at this point in the history
  • Loading branch information
oboukili authored Jan 29, 2021
1 parent 603dc2c commit b156f19
Show file tree
Hide file tree
Showing 3 changed files with 314 additions and 3 deletions.
10 changes: 9 additions & 1 deletion argocd/resource_argocd_project.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,15 @@ func resourceArgoCDProject() *schema.Resource {
},
Schema: map[string]*schema.Schema{
"metadata": metadataSchema("appprojects.argoproj.io"),
"spec": projectSpecSchema(),
"spec": projectSpecSchemaV1(),
},
SchemaVersion: 1,
StateUpgraders: []schema.StateUpgrader{
{
Type: resourceArgoCDProjectV0().CoreConfigSchema().ImpliedType(),
Upgrade: resourceArgoCDProjectStateUpgradeV0,
Version: 0,
},
},
}
}
Expand Down
217 changes: 215 additions & 2 deletions argocd/schema_project.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,162 @@
package argocd

import "github.com/hashicorp/terraform-plugin-sdk/helper/schema"
import (
"fmt"
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
)

func projectSpecSchema() *schema.Schema {
func projectSpecSchemaV0() *schema.Schema {
return &schema.Schema{
Type: schema.TypeList,
MinItems: 1,
MaxItems: 1,
Description: "ArgoCD App project resource specs. Required attributes: destination, source_repos.",
Required: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"cluster_resource_whitelist": {
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"group": {
Type: schema.TypeString,
ValidateFunc: validateGroupName,
Optional: true,
},
"kind": {
Type: schema.TypeString,
Optional: true,
},
},
},
},
"description": {
Type: schema.TypeString,
Optional: true,
},
"destination": {
Type: schema.TypeSet,
Required: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"server": {
Type: schema.TypeString,
Optional: true,
},
"namespace": {
Type: schema.TypeString,
Required: true,
},
"name": {
Type: schema.TypeString,
Optional: true,
Description: "Name of the destination cluster which can be used instead of server.",
},
},
},
},
"namespace_resource_blacklist": {
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"group": {
Type: schema.TypeString,
Optional: true,
},
"kind": {
Type: schema.TypeString,
Optional: true,
},
},
},
},
"orphaned_resources": {
Type: schema.TypeMap,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeBool},
},
"role": {
Type: schema.TypeList,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"description": {
Type: schema.TypeString,
Optional: true,
},
"groups": {
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"name": {
Type: schema.TypeString,
ValidateFunc: validateRoleName,
Required: true,
},
"policies": {
Type: schema.TypeList,
Required: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
},
},
},
"source_repos": {
Type: schema.TypeList,
Required: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"sync_window": {
Type: schema.TypeList,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"applications": {
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"clusters": {
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"duration": {
Type: schema.TypeString,
ValidateFunc: validateSyncWindowDuration,
Optional: true,
},
"kind": {
Type: schema.TypeString,
ValidateFunc: validateSyncWindowKind,
Optional: true,
},
"manual_sync": {
Type: schema.TypeBool,
Optional: true,
},
"namespaces": {
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"schedule": {
Type: schema.TypeString,
ValidateFunc: validateSyncWindowSchedule,
Optional: true,
},
},
},
},
},
},
}
}

func projectSpecSchemaV1() *schema.Schema {
return &schema.Schema{
Type: schema.TypeList,
MinItems: 1,
Expand Down Expand Up @@ -186,3 +340,62 @@ func projectSpecSchema() *schema.Schema {
},
}
}

func resourceArgoCDProjectV0() *schema.Resource {
return &schema.Resource{
Schema: map[string]*schema.Schema{
"metadata": metadataSchema("appprojects.argoproj.io"),
"spec": projectSpecSchemaV0(),
},
}
}

func resourceArgoCDProjectStateUpgradeV0(rawState map[string]interface{}, _ interface{}) (map[string]interface{}, error) {
orphanedResources := rawState["spec"].([]map[string]interface{})[0]["orphaned_resources"]

switch orphanedResources.(type) {

// <= v0.4.8
case map[string]bool:
warn := orphanedResources.(map[string]bool)["warn"]
newOrphanedResources := schema.NewSet(
schema.HashResource(&schema.Resource{
Schema: map[string]*schema.Schema{
"warn": {
Type: schema.TypeBool,
Optional: true,
},
"ignore": {
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"group": {
Type: schema.TypeString,
ValidateFunc: validateGroupName,
Optional: true,
},
"kind": {
Type: schema.TypeString,
Optional: true,
},
"name": {
Type: schema.TypeString,
Optional: true,
},
},
},
},
},
}),
[]interface{}{map[string]interface{}{"warn": warn}},
)
rawState["spec"].([]map[string]interface{})[0]["orphaned_resources"] = newOrphanedResources

// >= v0.5.0 <= v1.1.0
case *schema.Set:
default:
return nil, fmt.Errorf("error during state migration v0 to v1, unsupported type for 'orphaned_resources': %s", orphanedResources)
}
return rawState, nil
}
90 changes: 90 additions & 0 deletions argocd/schema_project_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package argocd

import (
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
"testing"
)

func testResourceArgoCDProjectStateDataV0() map[string]interface{} {
return map[string]interface{}{
"spec": []map[string]interface{}{
{
"orphaned_resources": map[string]bool{"warn": true},
},
},
}
}

func testResourceArgoCDProjectStateDataV1() map[string]interface{} {
newOrphanedResources := schema.NewSet(
schema.HashResource(&schema.Resource{
Schema: map[string]*schema.Schema{
"warn": {
Type: schema.TypeBool,
Optional: true,
},
"ignore": {
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"group": {
Type: schema.TypeString,
ValidateFunc: validateGroupName,
Optional: true,
},
"kind": {
Type: schema.TypeString,
Optional: true,
},
"name": {
Type: schema.TypeString,
Optional: true,
},
},
},
},
},
}),
[]interface{}{map[string]interface{}{"warn": true}},
)
return map[string]interface{}{
"spec": []map[string]interface{}{
{
"orphaned_resources": newOrphanedResources,
},
},
}
}

func TestResourceArgoCDProjectStateUpgradeV0(t *testing.T) {
cases := []struct {
name string
expected map[string]interface{}
sourceState map[string]interface{}
}{
{
"source < v0.5.0",
testResourceArgoCDProjectStateDataV1(),
testResourceArgoCDProjectStateDataV0(),
},
{
"source < v1.1.0, >= v0.5.0",
testResourceArgoCDProjectStateDataV1(),
testResourceArgoCDProjectStateDataV1(),
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
_actual, err := resourceArgoCDProjectStateUpgradeV0(tc.sourceState, nil)
if err != nil {
t.Fatalf("error migrating state: %s", err)
}
expected := tc.expected["spec"].([]map[string]interface{})[0]["orphaned_resources"].(*schema.Set)
actual := _actual["spec"].([]map[string]interface{})[0]["orphaned_resources"].(*schema.Set)
if !expected.HashEqual(actual) {
t.Fatalf("\n\nexpected:\n\n%#v\n\ngot:\n\n%#v\n\n", expected, actual)
}
})
}
}

0 comments on commit b156f19

Please sign in to comment.