From 1f7f731bc3abc6317ce9448b7a3d2cbc61f627ee Mon Sep 17 00:00:00 2001 From: Kobi Samoray Date: Thu, 26 Oct 2023 09:50:18 +0300 Subject: [PATCH] Implement ODS runbook invocation resource Signed-off-by: Kobi Samoray --- nsxt/provider.go | 1 + ...urce_nsxt_policy_ods_runbook_invocation.go | 202 ++++++++++++++++++ ...nsxt_policy_ods_runbook_invocation_test.go | 148 +++++++++++++ ...olicy_ods_runbook_invocation.html.markdown | 68 ++++++ 4 files changed, 419 insertions(+) create mode 100644 nsxt/resource_nsxt_policy_ods_runbook_invocation.go create mode 100644 nsxt/resource_nsxt_policy_ods_runbook_invocation_test.go create mode 100644 website/docs/r/policy_ods_runbook_invocation.html.markdown diff --git a/nsxt/provider.go b/nsxt/provider.go index 130e60a2d..d0ca24561 100644 --- a/nsxt/provider.go +++ b/nsxt/provider.go @@ -437,6 +437,7 @@ func Provider() *schema.Provider { "nsxt_policy_host_transport_node": resourceNsxtPolicyHostTransportNode(), "nsxt_edge_high_availability_profile": resourceNsxtEdgeHighAvailabilityProfile(), "nsxt_policy_host_transport_node_collection": resourceNsxtPolicyHostTransportNodeCollection(), + "nsxt_policy_ods_runbook_invocation": resourceNsxtPolicyODSRunbookInvocation(), }, ConfigureFunc: providerConfigure, diff --git a/nsxt/resource_nsxt_policy_ods_runbook_invocation.go b/nsxt/resource_nsxt_policy_ods_runbook_invocation.go new file mode 100644 index 000000000..23f2f7174 --- /dev/null +++ b/nsxt/resource_nsxt_policy_ods_runbook_invocation.go @@ -0,0 +1,202 @@ +/* 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, + }, + "description": getDescriptionSchema(), + "revision": getRevisionSchema(), + "tag": getTagsSchema(), + "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": { + Type: schema.TypeString, + Required: true, + Description: "Path of runbook object", + }, + "target_node": { + Type: schema.TypeString, + Optional: true, + Description: "Identifier of an appliance node or transport node", + }, + }, + } +} + +func getODSRunbookInvocationFromSchema(id string, d *schema.ResourceData) model.OdsRunbookInvocation { + displayName := d.Get("display_name").(string) + description := d.Get("description").(string) + tags := getPolicyTagsFromSchema(d) + 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, + Description: &description, + Tags: tags, + 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("description", obj.Description) + setPolicyTagsInSchema(d, obj.Tags) + 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 +} diff --git a/nsxt/resource_nsxt_policy_ods_runbook_invocation_test.go b/nsxt/resource_nsxt_policy_ods_runbook_invocation_test.go new file mode 100644 index 000000000..f8a3cf63e --- /dev/null +++ b/nsxt/resource_nsxt_policy_ods_runbook_invocation_test.go @@ -0,0 +1,148 @@ +/* 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) + 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) + 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) +} diff --git a/website/docs/r/policy_ods_runbook_invocation.html.markdown b/website/docs/r/policy_ods_runbook_invocation.html.markdown new file mode 100644 index 000000000..2c57f302a --- /dev/null +++ b/website/docs/r/policy_ods_runbook_invocation.html.markdown @@ -0,0 +1,68 @@ +--- +subcategory: "Beta" +layout: "nsxt" +page_title: "NSXT: nsxt_policy_ods_runbook_invocation" +description: A resource to configure ODS runbook invocation in NSX Policy manager. +--- + +# nsxt_policy_ods_runbook_invocation + +This resource provides a method for the management of a ODS runbook invocation. + +## Example Usage + +```hcl +data "nsxt_policy_host_transport_node" "tn1" { + display_name = "some_node_name.org" +} + +data "nsxt_policy_ods_pre_defined_runbook" "ot_runbook" { + display_name = "OverlayTunnel" +} + +resource "nsxt_policy_ods_runbook_invocation" "test" { + display_name = "overlay_tunnel_invocation" + runbook_path = data.nsxt_policy_ods_pre_defined_runbook.ot_runbook.path + argument { + key = "src" + value = "192.168.0.11" + } + argument { + key = "dst" + value = "192.168.0.10" + } + target_node = data.nsxt_policy_host_transport_node.tn1.id +} +``` + +## Argument Reference + +The following arguments are supported: + +* `display_name` - (Required) Display name of the resource. +* `description` - (Optional) Description of the resource. +* `tag` - (Optional) A list of scope + tag pairs to associate with this ODS runbook invocation. +* `nsx_id` - (Optional) The NSX ID of this resource. If set, this ID will be used to create the policy resource. +* `argument` - (Optional) Arguments for runbook invocation. + * `key` - (Required) Key + * `value` - (Required) Value +* `runbook_path` - (Required) Path of runbook object. +* `target_node` - (Optional) Identifier of an appliance node or transport node. + +## Attributes Reference + +In addition to arguments listed above, the following attributes are exported: + +* `revision` - Indicates current revision number of the object as seen by NSX-T API server. This attribute can be useful for debugging. +* `path` - The NSX path of the policy resource. + +## Importing + +An existing policy ODS runbook invocation can be [imported][docs-import] into this resource, via the following command: + +[docs-import]: https://www.terraform.io/cli/import + +``` +terraform import nsxt_policy_ods_runbook_invocation.test POLICY_PATH +``` +The above command imports the policy ODS runbook invocation named `test` for policy path `POLICY_PATH`.