@@ -41,6 +41,16 @@ func TestAccCloudStackPortForward_basic(t *testing.T) {
41
41
testAccCheckCloudStackPortForwardsExist ("cloudstack_port_forward.foo" ),
42
42
resource .TestCheckResourceAttr (
43
43
"cloudstack_port_forward.foo" , "forward.#" , "1" ),
44
+ resource .TestCheckResourceAttrSet (
45
+ "cloudstack_port_forward.foo" , "forward.0.uuid" ),
46
+ resource .TestCheckResourceAttr (
47
+ "cloudstack_port_forward.foo" , "forward.0.protocol" , "tcp" ),
48
+ resource .TestCheckResourceAttr (
49
+ "cloudstack_port_forward.foo" , "forward.0.private_port" , "443" ),
50
+ resource .TestCheckResourceAttr (
51
+ "cloudstack_port_forward.foo" , "forward.0.public_port" , "8443" ),
52
+ resource .TestCheckResourceAttrSet (
53
+ "cloudstack_port_forward.foo" , "forward.0.virtual_machine_id" ),
44
54
),
45
55
},
46
56
},
@@ -68,6 +78,41 @@ func TestAccCloudStackPortForward_update(t *testing.T) {
68
78
testAccCheckCloudStackPortForwardsExist ("cloudstack_port_forward.foo" ),
69
79
resource .TestCheckResourceAttr (
70
80
"cloudstack_port_forward.foo" , "forward.#" , "2" ),
81
+ // Validate first forward rule
82
+ resource .TestCheckResourceAttrSet (
83
+ "cloudstack_port_forward.foo" , "forward.0.uuid" ),
84
+ resource .TestCheckResourceAttr (
85
+ "cloudstack_port_forward.foo" , "forward.0.protocol" , "tcp" ),
86
+ resource .TestCheckResourceAttrSet (
87
+ "cloudstack_port_forward.foo" , "forward.0.virtual_machine_id" ),
88
+ // Validate second forward rule
89
+ resource .TestCheckResourceAttrSet (
90
+ "cloudstack_port_forward.foo" , "forward.1.uuid" ),
91
+ resource .TestCheckResourceAttr (
92
+ "cloudstack_port_forward.foo" , "forward.1.protocol" , "tcp" ),
93
+ resource .TestCheckResourceAttrSet (
94
+ "cloudstack_port_forward.foo" , "forward.1.virtual_machine_id" ),
95
+ ),
96
+ },
97
+ },
98
+ })
99
+ }
100
+
101
+ func TestAccCloudStackPortForward_portRange (t * testing.T ) {
102
+ resource .Test (t , resource.TestCase {
103
+ PreCheck : func () { testAccPreCheck (t ) },
104
+ Providers : testAccProviders ,
105
+ CheckDestroy : testAccCheckCloudStackPortForwardDestroy ,
106
+ Steps : []resource.TestStep {
107
+ {
108
+ Config : testAccCloudStackPortForward_portRange ,
109
+ Check : resource .ComposeTestCheckFunc (
110
+ testAccCheckCloudStackPortForwardsExist ("cloudstack_port_forward.foo" ),
111
+ resource .TestCheckResourceAttr (
112
+ "cloudstack_port_forward.foo" , "forward.#" , "2" ),
113
+ testAccCheckCloudStackPortForwardAttributes ("cloudstack_port_forward.foo" ),
114
+ // Note: We don't check specific indices since sets are unordered
115
+ // The testAccCheckCloudStackPortForwardAttributes function handles validation
71
116
),
72
117
},
73
118
},
@@ -106,6 +151,89 @@ func testAccCheckCloudStackPortForwardsExist(n string) resource.TestCheckFunc {
106
151
}
107
152
}
108
153
154
+ func testAccCheckCloudStackPortForwardAttributes (n string ) resource.TestCheckFunc {
155
+ return func (s * terraform.State ) error {
156
+ rs , ok := s .RootModule ().Resources [n ]
157
+ if ! ok {
158
+ return fmt .Errorf ("Not found: %s" , n )
159
+ }
160
+
161
+ if rs .Primary .ID == "" {
162
+ return fmt .Errorf ("No port forward ID is set" )
163
+ }
164
+
165
+ // Verify we have 2 forward rules
166
+ if rs .Primary .Attributes ["forward.#" ] != "2" {
167
+ return fmt .Errorf ("Expected 2 forward rules, got %s" , rs .Primary .Attributes ["forward.#" ])
168
+ }
169
+
170
+ var foundTCPRange , foundUDPSingle bool
171
+
172
+ // Check both forward rules to find the expected configurations
173
+ for i := 0 ; i < 2 ; i ++ {
174
+ protocolKey := fmt .Sprintf ("forward.%d.protocol" , i )
175
+ privatePortKey := fmt .Sprintf ("forward.%d.private_port" , i )
176
+ privateEndPortKey := fmt .Sprintf ("forward.%d.private_end_port" , i )
177
+ publicPortKey := fmt .Sprintf ("forward.%d.public_port" , i )
178
+ publicEndPortKey := fmt .Sprintf ("forward.%d.public_end_port" , i )
179
+ uuidKey := fmt .Sprintf ("forward.%d.uuid" , i )
180
+
181
+ protocol := rs .Primary .Attributes [protocolKey ]
182
+ privatePort := rs .Primary .Attributes [privatePortKey ]
183
+ privateEndPort := rs .Primary .Attributes [privateEndPortKey ]
184
+ publicPort := rs .Primary .Attributes [publicPortKey ]
185
+ publicEndPort := rs .Primary .Attributes [publicEndPortKey ]
186
+ uuid := rs .Primary .Attributes [uuidKey ]
187
+
188
+ // Verify basic required fields exist
189
+ if protocol == "" {
190
+ return fmt .Errorf ("Missing protocol for forward rule %d" , i )
191
+ }
192
+ if privatePort == "" {
193
+ return fmt .Errorf ("Missing private_port for forward rule %d" , i )
194
+ }
195
+ if publicPort == "" {
196
+ return fmt .Errorf ("Missing public_port for forward rule %d" , i )
197
+ }
198
+ if uuid == "" {
199
+ return fmt .Errorf ("Missing uuid for forward rule %d" , i )
200
+ }
201
+
202
+ // Check for TCP rule with port range (8080-8090)
203
+ if protocol == "tcp" && privatePort == "8080" && publicPort == "8080" {
204
+ if privateEndPort != "8090" {
205
+ return fmt .Errorf ("Expected TCP rule to have private_end_port=8090, got %s" , privateEndPort )
206
+ }
207
+ if publicEndPort != "8090" {
208
+ return fmt .Errorf ("Expected TCP rule to have public_end_port=8090, got %s" , publicEndPort )
209
+ }
210
+ foundTCPRange = true
211
+ }
212
+
213
+ // Check for UDP rule with single port (9000)
214
+ if protocol == "udp" && privatePort == "9000" && publicPort == "9000" {
215
+ // For single port rules, end ports should be empty, "0", or equal to start ports
216
+ if privateEndPort != "" && privateEndPort != "0" && privateEndPort != "9000" {
217
+ return fmt .Errorf ("Expected UDP rule to have empty, '0', or matching private_end_port, got %s" , privateEndPort )
218
+ }
219
+ if publicEndPort != "" && publicEndPort != "0" && publicEndPort != "9000" {
220
+ return fmt .Errorf ("Expected UDP rule to have empty, '0', or matching public_end_port, got %s" , publicEndPort )
221
+ }
222
+ foundUDPSingle = true
223
+ }
224
+ }
225
+
226
+ if ! foundTCPRange {
227
+ return fmt .Errorf ("Expected to find TCP rule with port range 8080-8090" )
228
+ }
229
+ if ! foundUDPSingle {
230
+ return fmt .Errorf ("Expected to find UDP rule with single port 9000" )
231
+ }
232
+
233
+ return nil
234
+ }
235
+ }
236
+
109
237
func testAccCheckCloudStackPortForwardDestroy (s * terraform.State ) error {
110
238
cs := testAccProvider .Meta ().(* cloudstack.CloudStackClient )
111
239
@@ -201,3 +329,43 @@ resource "cloudstack_port_forward" "foo" {
201
329
virtual_machine_id = cloudstack_instance.foobar.id
202
330
}
203
331
}`
332
+
333
+ const testAccCloudStackPortForward_portRange = `
334
+ resource "cloudstack_network" "foo" {
335
+ name = "terraform-network"
336
+ display_text = "terraform-network"
337
+ cidr = "10.1.1.0/24"
338
+ network_offering = "DefaultIsolatedNetworkOfferingWithSourceNatService"
339
+ source_nat_ip = true
340
+ zone = "Sandbox-simulator"
341
+ }
342
+
343
+ resource "cloudstack_instance" "foobar" {
344
+ name = "terraform-test"
345
+ display_name = "terraform-updated"
346
+ service_offering= "Medium Instance"
347
+ network_id = cloudstack_network.foo.id
348
+ template = "CentOS 5.6 (64-bit) no GUI (Simulator)"
349
+ zone = "Sandbox-simulator"
350
+ expunge = true
351
+ }
352
+
353
+ resource "cloudstack_port_forward" "foo" {
354
+ ip_address_id = cloudstack_network.foo.source_nat_ip_id
355
+
356
+ forward {
357
+ protocol = "tcp"
358
+ private_port = 8080
359
+ private_end_port = 8090
360
+ public_port = 8080
361
+ public_end_port = 8090
362
+ virtual_machine_id = cloudstack_instance.foobar.id
363
+ }
364
+
365
+ forward {
366
+ protocol = "udp"
367
+ private_port = 9000
368
+ public_port = 9000
369
+ virtual_machine_id = cloudstack_instance.foobar.id
370
+ }
371
+ }`
0 commit comments