Skip to content
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

Add vcd_rde_behavior_invocation data source to invoke behaviors #1117

Merged
merged 29 commits into from
Oct 11, 2023
Merged
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
4 changes: 4 additions & 0 deletions .changes/v3.11.0/1117-bug-fixes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
* Fixes a bug that made impossible to delete `vcd_rde_type_behavior_acl` resources when the Access Level is the last one
in the Behavior [GH-1117]
* Fixes the resource `vcd_rde_type_behavior_acl` to avoid race conditions when creating, updating or deleting more than one
Access Level [GH-1117]
1 change: 1 addition & 0 deletions .changes/v3.11.0/1117-experimental-features.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* **New Data Source:** `vcd_rde_behavior_invocation` to invoke a Behavior of a given RDE [GH-1117]
1 change: 1 addition & 0 deletions .changes/v3.11.0/1117-improvements.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* Field `rde_type_id` from resource `vcd_rde` does not force a deletion when updated, to allow easier RDE Type version upgrades [GH-1117]
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ require (
github.com/hashicorp/go-version v1.6.0
github.com/hashicorp/terraform-plugin-sdk/v2 v2.27.0
github.com/kr/pretty v0.2.1
github.com/vmware/go-vcloud-director/v2 v2.22.0-alpha.4
github.com/vmware/go-vcloud-director/v2 v2.22.0-alpha.5
)

require (
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,8 @@ github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9
github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc=
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
github.com/vmware/go-vcloud-director/v2 v2.22.0-alpha.4 h1:1mpwifzya4HdxC3E9tb5K7OkTFMo7NmN3fyuTK7ooAU=
github.com/vmware/go-vcloud-director/v2 v2.22.0-alpha.4/go.mod h1:QPxGFgrUcSyzy9IlpwDE4UNT3tsOy2047tJOPEJ4nlw=
github.com/vmware/go-vcloud-director/v2 v2.22.0-alpha.5 h1:/W3WHBsb1AHYFEijokIrB4TY5ZvNgjsfzmdcVXdyQ3E=
github.com/vmware/go-vcloud-director/v2 v2.22.0-alpha.5/go.mod h1:QPxGFgrUcSyzy9IlpwDE4UNT3tsOy2047tJOPEJ4nlw=
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
github.com/zclconf/go-cty v1.13.2 h1:4GvrUxe/QUDYuJKAav4EYqdM47/kZa672LwmXFmEKT0=
github.com/zclconf/go-cty v1.13.2/go.mod h1:YKQzy/7pZ7iq2jNFzy5go57xdxdWoLLpaEp4u238AE0=
Expand Down
74 changes: 74 additions & 0 deletions vcd/datasource_vcd_rde_behavior_invocation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package vcd

import (
"context"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/vmware/go-vcloud-director/v2/types/v56"
)

// This data source is unusual, as it represents an imperative call to a function (aka invoking a behavior)
// in the declarative world of Terraform. Despite being a data source, whose goal is to perform read-only operations, invocations
// can mutate RDE contents. The nature of this one is similar to the built-in "http" provider, which has the "http_http" data source
// that can perform "PUT"/"POST"/"DELETE" operations.
func datasourceVcdRdeBehaviorInvocation() *schema.Resource {
return &schema.Resource{
ReadContext: datasourceVcdRdeBehaviorInvocationRead,
Schema: map[string]*schema.Schema{
"rde_id": {
Type: schema.TypeString,
Required: true,
Description: "The ID of the RDE for which the Behavior will be invoked",
},
"behavior_id": {
Type: schema.TypeString,
Required: true,
Description: "The ID of either a RDE Interface Behavior or RDE Type Behavior to be invoked",
},
"invoke_on_refresh": {
Type: schema.TypeBool,
Optional: true,
Default: true,
Description: "If 'true', invokes the Behavior on every refresh",
},
"arguments": {
Type: schema.TypeMap,
Optional: true,
Description: "The arguments to be passed to the invoked Behavior",
},
"metadata": {
Type: schema.TypeMap,
Optional: true,
Description: "Metadata to be passed to the invoked Behavior",
},
"result": {
Type: schema.TypeString,
Computed: true,
Description: "The raw result of the Behavior invocation",
},
},
}
}

func datasourceVcdRdeBehaviorInvocationRead(_ context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
vcdClient := meta.(*VCDClient)
rdeId := d.Get("rde_id").(string)
behaviorId := d.Get("behavior_id").(string)

if d.Get("invoke_on_refresh").(bool) {
rde, err := vcdClient.GetRdeById(rdeId)
if err != nil {
return diag.Errorf("[RDE Behavior Invocation] could not retrieve the RDE with ID '%s': %s", rdeId, err)
}
result, err := rde.InvokeBehavior(behaviorId, types.BehaviorInvocation{
Arguments: d.Get("arguments").(map[string]interface{}),
Metadata: d.Get("metadata").(map[string]interface{}),
})
if err != nil {
return diag.Errorf("[RDE Behavior Invocation] could not invoke the Behavior of the RDE with ID '%s': %s", rdeId, err)
}
dSet(d, "result", result)
}
d.SetId(rdeId + "|" + behaviorId) // Invocations are not real entities, so we make an artificial ID.
lvirbalas marked this conversation as resolved.
Show resolved Hide resolved
return nil
}
128 changes: 128 additions & 0 deletions vcd/datasource_vcd_rde_behavior_invocation_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
//go:build rde || ALL || functional

package vcd

import (
"fmt"
"regexp"
"testing"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
)

func TestAccVcdRdeBehaviorInvocation(t *testing.T) {
preTestChecks(t)
skipIfNotSysAdmin(t)

var params = StringMap{
"Nss": "nss",
"Version": "1.0.0",
"Vendor": "vendor",
"Name": t.Name(),
"Description": t.Name(),
"SchemaPath": getCurrentDir() + "/../test-resources/rde_type.json",
"EntityPath": getCurrentDir() + "/../test-resources/rde_instance.json",
"ExecutionId": "MyActivity",
"ExecutionType": "noop",
"AccessLevels": "\"urn:vcloud:accessLevel:FullControl\"",
}
testParamsNotEmpty(t, params)

configText := templateFill(testAccVcdRdeBehaviorInvocation, params)
debugPrintf("#[DEBUG] CONFIGURATION: %s\n", configText)

if vcdShortTest {
t.Skip(acceptanceTestsSkipped)
return
}

resource.Test(t, resource.TestCase{
ProviderFactories: testAccProviders,
CheckDestroy: testAccCheckRdeInterfaceDestroy("vcd_rde_interface.interface"), // If the interface is destroyed, everything is
Steps: []resource.TestStep{
{
Config: configText,
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("vcd_rde.rde", "name", t.Name()),
// No-op operations return the original entity data, hence the RDE Type should appear:
resource.TestMatchResourceAttr("data.vcd_rde_behavior_invocation.invoke", "result",
regexp.MustCompile(fmt.Sprintf("\"urn:vcloud:type:%s:%s:%s\"", params["Vendor"], params["Nss"], params["Version"]))),
resource.TestMatchResourceAttr("data.vcd_rde_behavior_invocation.invoke2", "result",
regexp.MustCompile(fmt.Sprintf("\"urn:vcloud:type:%s:%s:%s\"", params["Vendor"], params["Nss"], params["Version"]))),
resource.TestCheckNoResourceAttr("data.vcd_rde_behavior_invocation.invoke3", "result"),
),
},
},
})
postTestChecks(t)
}

const testAccVcdRdeBehaviorInvocation = `
resource "vcd_rde_interface" "interface" {
nss = "{{.Nss}}"
version = "{{.Version}}"
vendor = "{{.Vendor}}"
name = "{{.Name}}"
}

resource "vcd_rde_interface_behavior" "behavior" {
rde_interface_id = vcd_rde_interface.interface.id
name = "{{.Name}}"
description = "{{.Description}}"
execution = {
"id" : "{{.ExecutionId}}1"
"type" : "{{.ExecutionType}}"
}
}

resource "vcd_rde_type" "type" {
nss = "{{.Nss}}"
version = "{{.Version}}"
vendor = "{{.Vendor}}"
name = "{{.Name}}"
description = "{{.Description}}"
interface_ids = [vcd_rde_interface.interface.id]
schema = file("{{.SchemaPath}}")

# Behaviors can't be created after the RDE Interface is used by a RDE Type
# so we need to depend on the Behaviors to wait for them to be created first.
depends_on = [vcd_rde_interface_behavior.behavior]
}

resource "vcd_rde" "rde" {
org = "System" # We use System org to avoid using right bundles
rde_type_id = vcd_rde_type.type.id
name = "{{.Name}}"
resolve = true
input_entity = file("{{.EntityPath}}")
}

# Required Access Levels to invoke Behaviors
resource "vcd_rde_type_behavior_acl" "interface_acl" {
rde_type_id = vcd_rde_type.type.id
behavior_id = vcd_rde_interface_behavior.behavior.id
access_level_ids = [{{.AccessLevels}}]
}

data "vcd_rde_behavior_invocation" "invoke" {
rde_id = vcd_rde.rde.id
behavior_id = vcd_rde_interface_behavior.behavior.id
}

data "vcd_rde_behavior_invocation" "invoke2" {
rde_id = vcd_rde.rde.id
behavior_id = vcd_rde_interface_behavior.behavior.id
arguments = {
"arg1" : "not_used"
}
metadata = {
"meta1" : "not_used"
}
}

data "vcd_rde_behavior_invocation" "invoke3" {
rde_id = vcd_rde.rde.id
behavior_id = vcd_rde_interface_behavior.behavior.id
invoke_on_refresh = false
}
`
lvirbalas marked this conversation as resolved.
Show resolved Hide resolved
1 change: 1 addition & 0 deletions vcd/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ var globalDataSourceMap = map[string]*schema.Resource{
"vcd_rde_interface_behavior": datasourceVcdRdeInterfaceBehavior(), // 3.10
"vcd_rde_type_behavior": datasourceVcdRdeTypeBehavior(), // 3.10
"vcd_rde_type_behavior_acl": datasourceVcdRdeTypeBehaviorAccessLevel(), // 3.10
"vcd_rde_behavior_invocation": datasourceVcdRdeBehaviorInvocation(), // 3.11
}

var globalResourceMap = map[string]*schema.Resource{
Expand Down
7 changes: 5 additions & 2 deletions vcd/resource_vcd_rde.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ func resourceVcdRde() *schema.Resource {
"rde_type_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Didainius marked this conversation as resolved.
Show resolved Hide resolved
Description: "The Runtime Defined Entity Type ID",
},
"external_id": {
Expand Down Expand Up @@ -121,7 +120,7 @@ func resourceVcdRdeCreate(ctx context.Context, d *schema.ResourceData, meta inte
tenantContext := govcd.TenantContext{}
org, err := vcdClient.GetOrgFromResource(d)
if err != nil {
return diag.Errorf("could not create RDE with name '%s', error retrieving Org '%s': %s", name, d.Get("org").(string), err)
return diag.Errorf("could not create RDE with name '%s', error retrieving Org: %s", name, err)
}
tenantContext.OrgId = org.Org.ID
tenantContext.OrgName = org.Org.Name
Expand Down Expand Up @@ -306,6 +305,7 @@ func resourceVcdRdeUpdate(ctx context.Context, d *schema.ResourceData, meta inte

err = rde.Update(types.DefinedEntity{
Name: d.Get("name").(string),
EntityType: d.Get("rde_type_id").(string),
ExternalId: d.Get("external_id").(string),
Entity: jsonEntity,
})
Expand Down Expand Up @@ -429,6 +429,9 @@ func resourceVcdRdeImport(_ context.Context, d *schema.ResourceData, meta interf
}

d.SetId(rde.DefinedEntity.ID)
if rde.DefinedEntity.Org != nil {
dSet(d, "org", rde.DefinedEntity.Org.Name)
}
dSet(d, "rde_type_id", rde.DefinedEntity.EntityType)

return []*schema.ResourceData{d}, nil
Expand Down
Loading