Skip to content

Commit

Permalink
Implement ODS runbook invocation resource
Browse files Browse the repository at this point in the history
Signed-off-by: Kobi Samoray <ksamoray@vmware.com>
  • Loading branch information
ksamoray committed Jan 23, 2024
1 parent 0135a64 commit 2efb4ac
Show file tree
Hide file tree
Showing 4 changed files with 408 additions and 0 deletions.
1 change: 1 addition & 0 deletions nsxt/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,7 @@ func Provider() *schema.Provider {
"nsxt_policy_lb_passive_monitor_profile": resourceNsxtPolicyLBPassiveMonitorProfile(),
"nsxt_policy_lb_tcp_monitor_profile": resourceNsxtPolicyLBTcpMonitorProfile(),
"nsxt_policy_lb_udp_monitor_profile": resourceNsxtPolicyLBUdpMonitorProfile(),
"nsxt_policy_ods_runbook_invocation": resourceNsxtPolicyODSRunbookInvocation(),
},

ConfigureFunc: providerConfigure,
Expand Down
191 changes: 191 additions & 0 deletions nsxt/resource_nsxt_policy_ods_runbook_invocation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
/* Copyright © 2023 VMware, Inc. All Rights Reserved.
SPDX-License-Identifier: MPL-2.0 */

package nsxt

import (
"fmt"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/vmware/vsphere-automation-sdk-go/runtime/protocol/client"
"github.com/vmware/vsphere-automation-sdk-go/services/nsxt/infra/sha"
"github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model"
)

func resourceNsxtPolicyODSRunbookInvocation() *schema.Resource {
return &schema.Resource{
Create: resourceNsxtPolicyODSRunbookInvocationCreate,
Read: resourceNsxtPolicyODSRunbookInvocationRead,
Update: resourceNsxtPolicyODSRunbookInvocationUpdate,
Delete: resourceNsxtPolicyODSRunbookInvocationDelete,
Importer: &schema.ResourceImporter{
State: nsxtPolicyPathResourceImporter,
},

Schema: map[string]*schema.Schema{
"nsx_id": getNsxIDSchema(),
"path": getPathSchema(),
// Due to a bug, invocations with a display_name specification fail, and when there's none set, NSX assigns
// the id value to the display name attribute. This should work around that bug.
"display_name": {
Type: schema.TypeString,
Description: "Display name for this resource",
Optional: true,
Computed: true,
},
"revision": getRevisionSchema(),
"argument": {
Type: schema.TypeSet,
Optional: true,
Description: "Arguments for runbook invocation",
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"key": {
Type: schema.TypeString,
Required: true,
Description: "Key",
},
"value": {
Type: schema.TypeString,
Required: true,
Description: "Value",
},
},
},
},
"runbook_path": getPolicyPathSchema(true, true, "Path of runbook object"),
"target_node": {
Type: schema.TypeString,
Optional: true,
Description: "Identifier of an appliance node or transport node",
ForceNew: true,
},
},
}
}

func getODSRunbookInvocationFromSchema(id string, d *schema.ResourceData) model.OdsRunbookInvocation {
displayName := d.Get("display_name").(string)
runbookPath := d.Get("runbook_path").(string)
targetNode := d.Get("target_node").(string)

var arguments []model.UnboundedKeyValuePair
for _, arg := range d.Get("argument").(*schema.Set).List() {
argMap := arg.(map[string]interface{})
key := argMap["key"].(string)
value := argMap["value"].(string)
item := model.UnboundedKeyValuePair{
Key: &key,
Value: &value,
}
arguments = append(arguments, item)
}

obj := model.OdsRunbookInvocation{
Id: &id,
RunbookPath: &runbookPath,
Arguments: arguments,
TargetNode: &targetNode,
}
if displayName != "" {
obj.DisplayName = &displayName
}

return obj
}

func resourceNsxtPolicyODSRunbookInvocationCreate(d *schema.ResourceData, m interface{}) error {
// Initialize resource Id and verify this ID is not yet used
id, err := getOrGenerateID(d, m, resourceNsxtPolicyODSRunbookInvocationExists)
if err != nil {
return err
}

connector := getPolicyConnector(m)
client := sha.NewRunbookInvocationsClient(connector)

obj := getODSRunbookInvocationFromSchema(id, d)
err = client.Create(id, obj)
if err != nil {
return handleCreateError("OdsRunbookInvocation", id, err)
}

d.SetId(id)
d.Set("nsx_id", id)
return resourceNsxtPolicyODSRunbookInvocationRead(d, m)
}

func resourceNsxtPolicyODSRunbookInvocationExists(id string, connector client.Connector, isGlobalManager bool) (bool, error) {
var err error
client := sha.NewRunbookInvocationsClient(connector)
_, err = client.Get(id)

if err == nil {
return true, nil
}

if isNotFoundError(err) {
return false, nil
}

return false, logAPIError("Error retrieving resource", err)
}

func resourceNsxtPolicyODSRunbookInvocationRead(d *schema.ResourceData, m interface{}) error {
connector := getPolicyConnector(m)

id := d.Id()
if id == "" {
return fmt.Errorf("error obtaining OdsRunbookInvocation ID")
}

client := sha.NewRunbookInvocationsClient(connector)
var err error
obj, err := client.Get(id)
if err != nil {
return handleReadError(d, "OdsRunbookInvocation", id, err)
}

if obj.DisplayName != nil && *obj.DisplayName != "" {
d.Set("display_name", obj.DisplayName)
}
d.Set("nsx_id", id)
d.Set("path", obj.Path)
d.Set("revision", obj.Revision)

d.Set("runbook_path", obj.RunbookPath)
d.Set("target_node", obj.TargetNode)

var argList []map[string]interface{}
for _, arg := range obj.Arguments {
argData := make(map[string]interface{})
argData["key"] = arg.Key
argData["value"] = arg.Value
argList = append(argList, argData)
}
d.Set("argument", argList)

return nil
}

func resourceNsxtPolicyODSRunbookInvocationUpdate(d *schema.ResourceData, m interface{}) error {
return resourceNsxtPolicyODSRunbookInvocationRead(d, m)
}

func resourceNsxtPolicyODSRunbookInvocationDelete(d *schema.ResourceData, m interface{}) error {
id := d.Id()
if id == "" {
return fmt.Errorf("error obtaining OdsRunbookInvocation ID")
}

connector := getPolicyConnector(m)
var err error
client := sha.NewRunbookInvocationsClient(connector)
err = client.Delete(id)

if err != nil {
return handleDeleteError("OdsRunbookInvocation", id, err)
}

return nil
}
150 changes: 150 additions & 0 deletions nsxt/resource_nsxt_policy_ods_runbook_invocation_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
/* Copyright © 2023 VMware, Inc. All Rights Reserved.
SPDX-License-Identifier: MPL-2.0 */

package nsxt

import (
"fmt"
"testing"

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

func TestAccResourceNsxtPolicyODSRunbookInvocation_basic(t *testing.T) {
name := getAccTestResourceName()
testResourceName := "nsxt_policy_ods_runbook_invocation.test"
resource.Test(t, resource.TestCase{
PreCheck: func() {
testAccOnlyLocalManager(t)
testAccPreCheck(t)
testAccNSXVersion(t, "4.1.1")
testAccEnvDefined(t, "NSXT_TEST_HOST_TRANSPORT_NODE")
},
Providers: testAccProviders,
CheckDestroy: func(state *terraform.State) error {
return testAccNsxtPolicyODSRunbookInvocationCheckDestroy(state, name)
},
Steps: []resource.TestStep{
{
Config: testAccNsxtPolicyODSRunbookInvocationCreateTemplate(name, "OverlayTunnel", `
argument {
key = "src"
value = "192.168.0.11"
}
argument {
key = "dst"
value = "192.168.0.10"
}
`),
Check: resource.ComposeTestCheckFunc(
testAccNsxtPolicyODSRunbookInvocationExists(name, testResourceName),
resource.TestCheckResourceAttrSet(testResourceName, "target_node"),
resource.TestCheckResourceAttrSet(testResourceName, "runbook_path"),
),
},
},
})
}

func TestAccResourceNsxtPolicyODSRunbookInvocation_import(t *testing.T) {

name := getAccTestResourceName()
testResourceName := "nsxt_policy_ods_runbook_invocation.test"
resource.Test(t, resource.TestCase{
PreCheck: func() {
testAccPreCheck(t)
testAccNSXVersion(t, "4.1.1")
testAccOnlyLocalManager(t)
},
Providers: testAccProviders,
CheckDestroy: func(state *terraform.State) error {
return testAccNsxtPolicyODSRunbookInvocationCheckDestroy(state, name)
},
Steps: []resource.TestStep{
{
Config: testAccNsxtPolicyODSRunbookInvocationCreateTemplate(name, "OverlayTunnel", `
argument {
key = "src"
value = "192.168.0.11"
}
argument {
key = "dst"
value = "192.168.0.10"
}
`),
},
{
ResourceName: testResourceName,
ImportState: true,
ImportStateVerify: true,
ImportStateIdFunc: testAccResourceNsxtPolicyImportIDRetriever(testResourceName),
},
},
})
}

func testAccNsxtPolicyODSRunbookInvocationCheckDestroy(state *terraform.State, displayName string) error {
connector := getPolicyConnector(testAccProvider.Meta().(nsxtClients))
for _, rs := range state.RootModule().Resources {

if rs.Type != "nsxt_policy_ods_runbook_invocation" {
continue
}

resourceID := rs.Primary.Attributes["id"]
exists, err := resourceNsxtPolicyODSRunbookInvocationExists(resourceID, connector, testAccIsGlobalManager())
if err == nil {
return err
}

if exists {
return fmt.Errorf("policy ODSRunbookInvocation %s still exists", displayName)
}
}
return nil
}

func testAccNsxtPolicyODSRunbookInvocationExists(displayName string, resourceName string) resource.TestCheckFunc {
return func(state *terraform.State) error {

connector := getPolicyConnector(testAccProvider.Meta().(nsxtClients))

rs, ok := state.RootModule().Resources[resourceName]
if !ok {
return fmt.Errorf("policy ODSRunbookInvocation resource %s not found in resources", resourceName)
}

resourceID := rs.Primary.ID
if resourceID == "" {
return fmt.Errorf("policy ODSRunbookInvocation resource ID not set in resources")
}

exists, err := resourceNsxtPolicyODSRunbookInvocationExists(resourceID, connector, testAccIsGlobalManager())
if err != nil {
return err
}
if !exists {
return fmt.Errorf("policy ODSRunbookInvocation %s does not exist", resourceID)
}

return nil
}
}

func testAccNsxtPolicyODSRunbookInvocationCreateTemplate(name, runbook, arguments string) string {
htnName := getHostTransportNodeName()
return testAccNsxtPolicyODSPredefinedRunbookReadTemplate(runbook) + fmt.Sprintf(`
data "nsxt_policy_host_transport_node" "test" {
display_name = "%s"
}
resource "nsxt_policy_ods_runbook_invocation" "test" {
// Use nsx_id here to address a backend issue.
nsx_id = "%s"
runbook_path = data.nsxt_policy_ods_pre_defined_runbook.test.path
%s
target_node = data.nsxt_policy_host_transport_node.test.unique_id
}
`, htnName, name, arguments)
}
Loading

0 comments on commit 2efb4ac

Please sign in to comment.