Skip to content

feat: add VDB group update and tag management with acceptance tests #126

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions docs/resources/vdb_group.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@ Creating a vdb group and assigning vdb with vdb_id = my_vdb_id
resource "delphix_vdb_group" "vdb_group_name" {
name = "my vdb group"
vdb_ids = ["my_vdb_id"]
tags {
key = "environment"
value = "production"
}
tags {
key = "project"
value = "terraform"
}
}
```

Expand All @@ -21,6 +29,10 @@ resource "delphix_vdb_group" "vdb_group_name" {

* `vdb_ids` - The list of VDB IDs in this VDBGroup.

* `tags` - The tags to be created for the VDB group. This is a map of 2 parameters:
* `key` - The key of the tag.
* `value` - The value of the tag.

## Attribute Reference

This resource exports same attributes as the arguments.
8 changes: 8 additions & 0 deletions examples/vdb_group/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,12 @@ resource "delphix_vdb" "vdb_provision_loop" {
resource "delphix_vdb_group" "this" {
name = "random"
vdb_ids = sort(flatten([for vdb in delphix_vdb.example : vdb.id]))
tags {
key = "environment"
value = "production"
}
tags {
key = "project"
value = "terraform"
}
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ go 1.22.0
toolchain go1.22.6

require (
github.com/delphix/dct-sdk-go/v25 v25.1.2
github.com/delphix/dct-sdk-go/v25 v25.2.0
github.com/hashicorp/terraform-plugin-sdk/v2 v2.33.0
)

Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/delphix/dct-sdk-go/v25 v25.1.2 h1:wiJui4cZB4xK9Znu9JdWb5N3rKqbnz1oXWaqLXjGHnE=
github.com/delphix/dct-sdk-go/v25 v25.1.2/go.mod h1:Y//bIbAZP6SZhLLZAQMxEfeRXvsvKQwu/kSR8a5hfqc=
github.com/delphix/dct-sdk-go/v25 v25.2.0 h1:djFGvJwDHE99vBFa5ZlixcV49niz7nRsuwfue8l/AQA=
github.com/delphix/dct-sdk-go/v25 v25.2.0/go.mod h1:fCw+bOFPHiNcqUGvRpOEq4PINgcmw6KptstJy4v66Uo=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
Expand Down
169 changes: 166 additions & 3 deletions internal/provider/resource_vdb_group.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package provider

import (
"context"
"time"

"github.com/hashicorp/terraform-plugin-log/tflog"

Expand All @@ -18,6 +19,9 @@ func resourceVdbGroup() *schema.Resource {
ReadContext: resourceVdbGroupRead,
UpdateContext: resourceVdbGroupUpdate,
DeleteContext: resourceVdbGroupDelete,
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},

Schema: map[string]*schema.Schema{
"id": {
Expand All @@ -35,18 +39,39 @@ func resourceVdbGroup() *schema.Resource {
Type: schema.TypeString,
},
},
"tags": {
Type: schema.TypeList,
Optional: true,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We will need computed:true here, as without that, it will detect UIPs in case of auto-tagging.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Computed: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"key": {
Type: schema.TypeString,
Required: true,
},
"value": {
Type: schema.TypeString,
Required: true,
},
},
},
},
},
}
}

func resourceVdbGroupCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {

var diags diag.Diagnostics

client := meta.(*apiClient).client

vdbGroupCreateReq := *dctapi.NewCreateVDBGroupRequest(d.Get("name").(string))
vdbGroupCreateReq.SetVdbIds(toStringArray(d.Get("vdb_ids")))

if v, has_v := d.GetOk("tags"); has_v {
vdbGroupCreateReq.SetTags(toTagArray(v))
}

apiRes, httpRes, err := client.VDBGroupsAPI.CreateVdbGroup(ctx).CreateVDBGroupRequest(vdbGroupCreateReq).Execute()

if diags := apiErrorResponseHelper(ctx, apiRes, httpRes, err); diags != nil {
Expand All @@ -64,7 +89,6 @@ func resourceVdbGroupCreate(ctx context.Context, d *schema.ResourceData, meta in
}

func resourceVdbGroupRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {

client := meta.(*apiClient).client

var diags diag.Diagnostics
Expand All @@ -80,12 +104,151 @@ func resourceVdbGroupRead(ctx context.Context, d *schema.ResourceData, meta inte

d.Set("name", apiRes.GetName())
d.Set("vdb_ids", apiRes.GetVdbIds())
d.Set("tags", flattenTags(apiRes.GetTags()))
return diags
}

func resourceVdbGroupUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*apiClient).client
vdbGroupId := d.Id()

// Existing update logic
updateVdbGroupReq := *dctapi.NewUpdateVDBGroupParameters()
if d.HasChange("name") {
updateVdbGroupReq.SetName(d.Get("name").(string))
}
if d.HasChange("vdb_ids") {
updateVdbGroupReq.SetVdbIds(toStringArray(d.Get("vdb_ids")))
}

_, httpRes, err := client.VDBGroupsAPI.UpdateVdbGroupById(ctx, vdbGroupId).UpdateVDBGroupParameters(updateVdbGroupReq).Execute()
if diags := apiErrorResponseHelper(ctx, nil, httpRes, err); diags != nil {
return diags
}

return diag.Errorf("not implemented")
// Polling logic for name/vdb_ids update
maxAttempts := 10
for attempt := 1; attempt <= maxAttempts; attempt++ {
status, err := PollVdbGroupStatus(ctx, client, vdbGroupId)
if err != nil {
return diag.FromErr(err)
}
if status == "RUNNING" {
break
}
if status == "FAILED" || status == "CANCELED" {
return diag.Errorf("VDB group update failed with status: %s", status)
}
time.Sleep(time.Duration(STATUS_POLL_SLEEP_TIME) * time.Second)
}

// Tag update logic
if d.HasChange("tags") {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This update logic looks too complex and I am not sure if we really want a lookup logic around. As of now there are no tag update api. we explicitly need to delete the old one and create a new one.
The logic here ultimately does the same api call and there are no data retrieval involved, i dont see lookup being advantageous. Rather we can simply remove the old tags and create the new ones if there are changes.
I may be overlooking something so would really want other's opinions on it.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since tags are used for managing access, there could be potential race conditions when tags that should not be deleted get deleted as part of the update if we take the approach of deleting and recreating all tags?

oldTags, newTags := d.GetChange("tags")
oldTagList := oldTags.([]interface{})
newTagList := newTags.([]interface{})

// Create a map of old tags for easy lookup
oldTagMap := make(map[string]map[string]bool)
for _, tag := range oldTagList {
tagMap := tag.(map[string]interface{})
key := tagMap["key"].(string)
value := tagMap["value"].(string)
if _, exists := oldTagMap[key]; !exists {
oldTagMap[key] = make(map[string]bool)
}
oldTagMap[key][value] = true
}

// Create a map of new tags for easy lookup
newTagMap := make(map[string]map[string]bool)
for _, tag := range newTagList {
tagMap := tag.(map[string]interface{})
key := tagMap["key"].(string)
value := tagMap["value"].(string)
if _, exists := newTagMap[key]; !exists {
newTagMap[key] = make(map[string]bool)
}
newTagMap[key][value] = true
}

// If newTagList is empty, delete all existing tags
if len(newTagList) == 0 {
for key := range oldTagMap {
deleteTag := *dctapi.NewDeleteTag()
deleteTag.SetKey(key)
httpRes, err := client.VDBGroupsAPI.DeleteVdbGroupTags(ctx, vdbGroupId).DeleteTag(deleteTag).Execute()
if diags := apiErrorResponseHelper(ctx, nil, httpRes, err); diags != nil {
return diags
}
}
} else {
// Delete removed tags
for key, oldValues := range oldTagMap {
newValues, exists := newTagMap[key]
if !exists {
// Key doesn't exist in new tags, delete all values for this key
deleteTag := *dctapi.NewDeleteTag()
deleteTag.SetKey(key)
httpRes, err := client.VDBGroupsAPI.DeleteVdbGroupTags(ctx, vdbGroupId).DeleteTag(deleteTag).Execute()
if diags := apiErrorResponseHelper(ctx, nil, httpRes, err); diags != nil {
return diags
}
} else {
// Key exists, delete only values that are not in new tags
for oldValue := range oldValues {
if !newValues[oldValue] {
deleteTag := *dctapi.NewDeleteTag()
deleteTag.SetKey(key)
deleteTag.SetValue(oldValue)
httpRes, err := client.VDBGroupsAPI.DeleteVdbGroupTags(ctx, vdbGroupId).DeleteTag(deleteTag).Execute()
if diags := apiErrorResponseHelper(ctx, nil, httpRes, err); diags != nil {
return diags
}
}
}
}
}

// Create new tags
var tags []dctapi.Tag
for key, newValues := range newTagMap {
oldValues, exists := oldTagMap[key]
if !exists {
// Key doesn't exist in old tags, create all values
for value := range newValues {
tag := *dctapi.NewTag(key, value)
tags = append(tags, tag)
}
} else {
// Key exists, create only new values
for value := range newValues {
if !oldValues[value] {
tag := *dctapi.NewTag(key, value)
tags = append(tags, tag)
}
}
}
}
if len(tags) > 0 {
tagsRequest := *dctapi.NewTagsRequest(tags)
_, httpRes, err := client.VDBGroupsAPI.CreateVdbGroupsTags(ctx, vdbGroupId).TagsRequest(tagsRequest).Execute()
if diags := apiErrorResponseHelper(ctx, nil, httpRes, err); diags != nil {
return diags
}
}
}
}

return resourceVdbGroupRead(ctx, d, meta)
}

func PollVdbGroupStatus(ctx context.Context, client *dctapi.APIClient, vdbGroupId string) (string, error) {
res, _, err := client.VDBGroupsAPI.GetVdbGroup(ctx, vdbGroupId).Execute()
if err != nil {
return "", err
}
return res.GetStatus(), nil
}

func resourceVdbGroupDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
Expand Down
Loading