Skip to content

Commit 1f7f731

Browse files
committed
Implement ODS runbook invocation resource
Signed-off-by: Kobi Samoray <[email protected]>
1 parent 03aad86 commit 1f7f731

4 files changed

+419
-0
lines changed

nsxt/provider.go

+1
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,7 @@ func Provider() *schema.Provider {
437437
"nsxt_policy_host_transport_node": resourceNsxtPolicyHostTransportNode(),
438438
"nsxt_edge_high_availability_profile": resourceNsxtEdgeHighAvailabilityProfile(),
439439
"nsxt_policy_host_transport_node_collection": resourceNsxtPolicyHostTransportNodeCollection(),
440+
"nsxt_policy_ods_runbook_invocation": resourceNsxtPolicyODSRunbookInvocation(),
440441
},
441442

442443
ConfigureFunc: providerConfigure,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
/* Copyright © 2023 VMware, Inc. All Rights Reserved.
2+
SPDX-License-Identifier: MPL-2.0 */
3+
4+
package nsxt
5+
6+
import (
7+
"fmt"
8+
9+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
10+
"github.com/vmware/vsphere-automation-sdk-go/runtime/protocol/client"
11+
"github.com/vmware/vsphere-automation-sdk-go/services/nsxt/infra/sha"
12+
"github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model"
13+
)
14+
15+
func resourceNsxtPolicyODSRunbookInvocation() *schema.Resource {
16+
return &schema.Resource{
17+
Create: resourceNsxtPolicyODSRunbookInvocationCreate,
18+
Read: resourceNsxtPolicyODSRunbookInvocationRead,
19+
Update: resourceNsxtPolicyODSRunbookInvocationUpdate,
20+
Delete: resourceNsxtPolicyODSRunbookInvocationDelete,
21+
Importer: &schema.ResourceImporter{
22+
State: nsxtPolicyPathResourceImporter,
23+
},
24+
25+
Schema: map[string]*schema.Schema{
26+
"nsx_id": getNsxIDSchema(),
27+
"path": getPathSchema(),
28+
// Due to a bug, invocations with a display_name specification fail, and when there's none set, NSX assigns
29+
// the id value to the display name attribute. This should work around that bug.
30+
"display_name": {
31+
Type: schema.TypeString,
32+
Description: "Display name for this resource",
33+
Optional: true,
34+
Computed: true,
35+
},
36+
"description": getDescriptionSchema(),
37+
"revision": getRevisionSchema(),
38+
"tag": getTagsSchema(),
39+
"argument": {
40+
Type: schema.TypeSet,
41+
Optional: true,
42+
Description: "Arguments for runbook invocation",
43+
Elem: &schema.Resource{
44+
Schema: map[string]*schema.Schema{
45+
"key": {
46+
Type: schema.TypeString,
47+
Required: true,
48+
Description: "Key",
49+
},
50+
"value": {
51+
Type: schema.TypeString,
52+
Required: true,
53+
Description: "Value",
54+
},
55+
},
56+
},
57+
},
58+
"runbook_path": {
59+
Type: schema.TypeString,
60+
Required: true,
61+
Description: "Path of runbook object",
62+
},
63+
"target_node": {
64+
Type: schema.TypeString,
65+
Optional: true,
66+
Description: "Identifier of an appliance node or transport node",
67+
},
68+
},
69+
}
70+
}
71+
72+
func getODSRunbookInvocationFromSchema(id string, d *schema.ResourceData) model.OdsRunbookInvocation {
73+
displayName := d.Get("display_name").(string)
74+
description := d.Get("description").(string)
75+
tags := getPolicyTagsFromSchema(d)
76+
runbookPath := d.Get("runbook_path").(string)
77+
targetNode := d.Get("target_node").(string)
78+
79+
var arguments []model.UnboundedKeyValuePair
80+
for _, arg := range d.Get("argument").(*schema.Set).List() {
81+
argMap := arg.(map[string]interface{})
82+
key := argMap["key"].(string)
83+
value := argMap["value"].(string)
84+
item := model.UnboundedKeyValuePair{
85+
Key: &key,
86+
Value: &value,
87+
}
88+
arguments = append(arguments, item)
89+
}
90+
91+
obj := model.OdsRunbookInvocation{
92+
Id: &id,
93+
Description: &description,
94+
Tags: tags,
95+
RunbookPath: &runbookPath,
96+
Arguments: arguments,
97+
TargetNode: &targetNode,
98+
}
99+
if displayName != "" {
100+
obj.DisplayName = &displayName
101+
}
102+
103+
return obj
104+
}
105+
106+
func resourceNsxtPolicyODSRunbookInvocationCreate(d *schema.ResourceData, m interface{}) error {
107+
// Initialize resource Id and verify this ID is not yet used
108+
id, err := getOrGenerateID(d, m, resourceNsxtPolicyODSRunbookInvocationExists)
109+
if err != nil {
110+
return err
111+
}
112+
113+
connector := getPolicyConnector(m)
114+
client := sha.NewRunbookInvocationsClient(connector)
115+
116+
obj := getODSRunbookInvocationFromSchema(id, d)
117+
err = client.Create(id, obj)
118+
if err != nil {
119+
return handleCreateError("OdsRunbookInvocation", id, err)
120+
}
121+
122+
d.SetId(id)
123+
d.Set("nsx_id", id)
124+
return resourceNsxtPolicyODSRunbookInvocationRead(d, m)
125+
}
126+
127+
func resourceNsxtPolicyODSRunbookInvocationExists(id string, connector client.Connector, isGlobalManager bool) (bool, error) {
128+
var err error
129+
client := sha.NewRunbookInvocationsClient(connector)
130+
_, err = client.Get(id)
131+
132+
if err == nil {
133+
return true, nil
134+
}
135+
136+
if isNotFoundError(err) {
137+
return false, nil
138+
}
139+
140+
return false, logAPIError("Error retrieving resource", err)
141+
}
142+
143+
func resourceNsxtPolicyODSRunbookInvocationRead(d *schema.ResourceData, m interface{}) error {
144+
connector := getPolicyConnector(m)
145+
146+
id := d.Id()
147+
if id == "" {
148+
return fmt.Errorf("error obtaining OdsRunbookInvocation ID")
149+
}
150+
151+
client := sha.NewRunbookInvocationsClient(connector)
152+
var err error
153+
obj, err := client.Get(id)
154+
if err != nil {
155+
return handleReadError(d, "OdsRunbookInvocation", id, err)
156+
}
157+
158+
if obj.DisplayName != nil && *obj.DisplayName != "" {
159+
d.Set("display_name", obj.DisplayName)
160+
}
161+
d.Set("description", obj.Description)
162+
setPolicyTagsInSchema(d, obj.Tags)
163+
d.Set("nsx_id", id)
164+
d.Set("path", obj.Path)
165+
d.Set("revision", obj.Revision)
166+
167+
d.Set("runbook_path", obj.RunbookPath)
168+
d.Set("target_node", obj.TargetNode)
169+
170+
var argList []map[string]interface{}
171+
for _, arg := range obj.Arguments {
172+
argData := make(map[string]interface{})
173+
argData["key"] = arg.Key
174+
argData["value"] = arg.Value
175+
argList = append(argList, argData)
176+
}
177+
d.Set("argument", argList)
178+
179+
return nil
180+
}
181+
182+
func resourceNsxtPolicyODSRunbookInvocationUpdate(d *schema.ResourceData, m interface{}) error {
183+
return resourceNsxtPolicyODSRunbookInvocationRead(d, m)
184+
}
185+
186+
func resourceNsxtPolicyODSRunbookInvocationDelete(d *schema.ResourceData, m interface{}) error {
187+
id := d.Id()
188+
if id == "" {
189+
return fmt.Errorf("error obtaining OdsRunbookInvocation ID")
190+
}
191+
192+
connector := getPolicyConnector(m)
193+
var err error
194+
client := sha.NewRunbookInvocationsClient(connector)
195+
err = client.Delete(id)
196+
197+
if err != nil {
198+
return handleDeleteError("OdsRunbookInvocation", id, err)
199+
}
200+
201+
return nil
202+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
/* Copyright © 2023 VMware, Inc. All Rights Reserved.
2+
SPDX-License-Identifier: MPL-2.0 */
3+
4+
package nsxt
5+
6+
import (
7+
"fmt"
8+
"testing"
9+
10+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
11+
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
12+
)
13+
14+
func TestAccResourceNsxtPolicyODSRunbookInvocation_basic(t *testing.T) {
15+
name := getAccTestResourceName()
16+
testResourceName := "nsxt_policy_ods_runbook_invocation.test"
17+
resource.Test(t, resource.TestCase{
18+
PreCheck: func() {
19+
testAccOnlyLocalManager(t)
20+
testAccPreCheck(t)
21+
testAccEnvDefined(t, "NSXT_TEST_HOST_TRANSPORT_NODE")
22+
},
23+
Providers: testAccProviders,
24+
CheckDestroy: func(state *terraform.State) error {
25+
return testAccNsxtPolicyODSRunbookInvocationCheckDestroy(state, name)
26+
},
27+
Steps: []resource.TestStep{
28+
{
29+
Config: testAccNsxtPolicyODSRunbookInvocationCreateTemplate(name, "OverlayTunnel", `
30+
argument {
31+
key = "src"
32+
value = "192.168.0.11"
33+
}
34+
argument {
35+
key = "dst"
36+
value = "192.168.0.10"
37+
}
38+
`),
39+
Check: resource.ComposeTestCheckFunc(
40+
testAccNsxtPolicyODSRunbookInvocationExists(name, testResourceName),
41+
resource.TestCheckResourceAttrSet(testResourceName, "target_node"),
42+
resource.TestCheckResourceAttrSet(testResourceName, "runbook_path"),
43+
),
44+
},
45+
},
46+
})
47+
}
48+
49+
func TestAccResourceNsxtPolicyODSRunbookInvocation_import(t *testing.T) {
50+
51+
name := getAccTestResourceName()
52+
testResourceName := "nsxt_policy_ods_runbook_invocation.test"
53+
resource.Test(t, resource.TestCase{
54+
PreCheck: func() {
55+
testAccPreCheck(t)
56+
testAccOnlyLocalManager(t)
57+
},
58+
Providers: testAccProviders,
59+
CheckDestroy: func(state *terraform.State) error {
60+
return testAccNsxtPolicyODSRunbookInvocationCheckDestroy(state, name)
61+
},
62+
Steps: []resource.TestStep{
63+
{
64+
Config: testAccNsxtPolicyODSRunbookInvocationCreateTemplate(name, "OverlayTunnel", `
65+
argument {
66+
key = "src"
67+
value = "192.168.0.11"
68+
}
69+
argument {
70+
key = "dst"
71+
value = "192.168.0.10"
72+
}
73+
`),
74+
},
75+
{
76+
ResourceName: testResourceName,
77+
ImportState: true,
78+
ImportStateVerify: true,
79+
ImportStateIdFunc: testAccResourceNsxtPolicyImportIDRetriever(testResourceName),
80+
},
81+
},
82+
})
83+
}
84+
85+
func testAccNsxtPolicyODSRunbookInvocationCheckDestroy(state *terraform.State, displayName string) error {
86+
connector := getPolicyConnector(testAccProvider.Meta().(nsxtClients))
87+
for _, rs := range state.RootModule().Resources {
88+
89+
if rs.Type != "nsxt_policy_ods_runbook_invocation" {
90+
continue
91+
}
92+
93+
resourceID := rs.Primary.Attributes["id"]
94+
exists, err := resourceNsxtPolicyODSRunbookInvocationExists(resourceID, connector, testAccIsGlobalManager())
95+
if err == nil {
96+
return err
97+
}
98+
99+
if exists {
100+
return fmt.Errorf("policy ODSRunbookInvocation %s still exists", displayName)
101+
}
102+
}
103+
return nil
104+
}
105+
106+
func testAccNsxtPolicyODSRunbookInvocationExists(displayName string, resourceName string) resource.TestCheckFunc {
107+
return func(state *terraform.State) error {
108+
109+
connector := getPolicyConnector(testAccProvider.Meta().(nsxtClients))
110+
111+
rs, ok := state.RootModule().Resources[resourceName]
112+
if !ok {
113+
return fmt.Errorf("policy ODSRunbookInvocation resource %s not found in resources", resourceName)
114+
}
115+
116+
resourceID := rs.Primary.ID
117+
if resourceID == "" {
118+
return fmt.Errorf("policy ODSRunbookInvocation resource ID not set in resources")
119+
}
120+
121+
exists, err := resourceNsxtPolicyODSRunbookInvocationExists(resourceID, connector, testAccIsGlobalManager())
122+
if err != nil {
123+
return err
124+
}
125+
if !exists {
126+
return fmt.Errorf("policy ODSRunbookInvocation %s does not exist", resourceID)
127+
}
128+
129+
return nil
130+
}
131+
}
132+
133+
func testAccNsxtPolicyODSRunbookInvocationCreateTemplate(name, runbook, arguments string) string {
134+
htnName := getHostTransportNodeName()
135+
return testAccNsxtPolicyODSPredefinedRunbookReadTemplate(runbook) + fmt.Sprintf(`
136+
data "nsxt_policy_host_transport_node" "test" {
137+
display_name = "%s"
138+
}
139+
140+
resource "nsxt_policy_ods_runbook_invocation" "test" {
141+
// Use nsx_id here to address a backend issue.
142+
nsx_id = "%s"
143+
runbook_path = data.nsxt_policy_ods_pre_defined_runbook.test.path
144+
%s
145+
target_node = data.nsxt_policy_host_transport_node.test.unique_id
146+
}
147+
`, htnName, name, arguments)
148+
}

0 commit comments

Comments
 (0)