Skip to content
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
33 changes: 33 additions & 0 deletions cloudstack/resource_cloudstack_port_forward.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,23 @@ func resourceCloudStackPortForward() *schema.Resource {
Required: true,
},

"private_end_port": {
Type: schema.TypeInt,
Optional: true,
Computed: true,
},

"public_port": {
Type: schema.TypeInt,
Required: true,
},

"public_end_port": {
Type: schema.TypeInt,
Optional: true,
Computed: true,
},

"virtual_machine_id": {
Type: schema.TypeString,
Required: true,
Expand Down Expand Up @@ -175,6 +187,12 @@ func createPortForward(d *schema.ResourceData, meta interface{}, forward map[str
// Create a new parameter struct
p := cs.Firewall.NewCreatePortForwardingRuleParams(d.Id(), forward["private_port"].(int),
forward["protocol"].(string), forward["public_port"].(int), vm.Id)
if val, ok := forward["private_end_port"]; ok && val != nil && val.(int) != 0 {
p.SetPrivateendport(val.(int))
}
if val, ok := forward["public_end_port"]; ok && val != nil && val.(int) != 0 {
p.SetPublicendport(val.(int))
}
Comment thread
vishesh92 marked this conversation as resolved.

if vmGuestIP, ok := forward["vm_guest_ip"]; ok && vmGuestIP.(string) != "" {
p.SetVmguestip(vmGuestIP.(string))
Expand Down Expand Up @@ -288,6 +306,21 @@ func resourceCloudStackPortForwardRead(d *schema.ResourceData, meta interface{})
forward["protocol"] = f.Protocol
forward["private_port"] = privPort
forward["public_port"] = pubPort
// Only set end ports if they differ from start ports (indicating a range)
if f.Privateendport != "" && f.Privateendport != f.Privateport {
privEndPort, err := strconv.Atoi(f.Privateendport)
if err != nil {
return err
}
forward["private_end_port"] = privEndPort
}
if f.Publicendport != "" && f.Publicendport != f.Publicport {
pubEndPort, err := strconv.Atoi(f.Publicendport)
if err != nil {
return err
}
forward["public_end_port"] = pubEndPort
Comment thread
vishesh92 marked this conversation as resolved.
Comment thread
vishesh92 marked this conversation as resolved.
Comment thread
vishesh92 marked this conversation as resolved.
Comment thread
vishesh92 marked this conversation as resolved.
}
forward["virtual_machine_id"] = f.Virtualmachineid

// This one is a bit tricky. We only want to update this optional value
Expand Down
168 changes: 168 additions & 0 deletions cloudstack/resource_cloudstack_port_forward_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,16 @@ func TestAccCloudStackPortForward_basic(t *testing.T) {
testAccCheckCloudStackPortForwardsExist("cloudstack_port_forward.foo"),
resource.TestCheckResourceAttr(
"cloudstack_port_forward.foo", "forward.#", "1"),
resource.TestCheckResourceAttrSet(
"cloudstack_port_forward.foo", "forward.0.uuid"),
resource.TestCheckResourceAttr(
"cloudstack_port_forward.foo", "forward.0.protocol", "tcp"),
resource.TestCheckResourceAttr(
"cloudstack_port_forward.foo", "forward.0.private_port", "443"),
resource.TestCheckResourceAttr(
"cloudstack_port_forward.foo", "forward.0.public_port", "8443"),
resource.TestCheckResourceAttrSet(
"cloudstack_port_forward.foo", "forward.0.virtual_machine_id"),
),
},
},
Expand Down Expand Up @@ -68,6 +78,41 @@ func TestAccCloudStackPortForward_update(t *testing.T) {
testAccCheckCloudStackPortForwardsExist("cloudstack_port_forward.foo"),
resource.TestCheckResourceAttr(
"cloudstack_port_forward.foo", "forward.#", "2"),
// Validate first forward rule
resource.TestCheckResourceAttrSet(
"cloudstack_port_forward.foo", "forward.0.uuid"),
resource.TestCheckResourceAttr(
"cloudstack_port_forward.foo", "forward.0.protocol", "tcp"),
resource.TestCheckResourceAttrSet(
"cloudstack_port_forward.foo", "forward.0.virtual_machine_id"),
// Validate second forward rule
resource.TestCheckResourceAttrSet(
"cloudstack_port_forward.foo", "forward.1.uuid"),
resource.TestCheckResourceAttr(
"cloudstack_port_forward.foo", "forward.1.protocol", "tcp"),
resource.TestCheckResourceAttrSet(
"cloudstack_port_forward.foo", "forward.1.virtual_machine_id"),
),
},
},
})
}

func TestAccCloudStackPortForward_portRange(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckCloudStackPortForwardDestroy,
Steps: []resource.TestStep{
{
Config: testAccCloudStackPortForward_portRange,
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudStackPortForwardsExist("cloudstack_port_forward.foo"),
resource.TestCheckResourceAttr(
"cloudstack_port_forward.foo", "forward.#", "2"),
testAccCheckCloudStackPortForwardAttributes("cloudstack_port_forward.foo"),
// Note: We don't check specific indices since sets are unordered
// The testAccCheckCloudStackPortForwardAttributes function handles validation
),
},
},
Expand Down Expand Up @@ -106,6 +151,89 @@ func testAccCheckCloudStackPortForwardsExist(n string) resource.TestCheckFunc {
}
}

func testAccCheckCloudStackPortForwardAttributes(n string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
if !ok {
return fmt.Errorf("Not found: %s", n)
}

if rs.Primary.ID == "" {
return fmt.Errorf("No port forward ID is set")
}

// Verify we have 2 forward rules
if rs.Primary.Attributes["forward.#"] != "2" {
return fmt.Errorf("Expected 2 forward rules, got %s", rs.Primary.Attributes["forward.#"])
}

var foundTCPRange, foundUDPSingle bool

// Check both forward rules to find the expected configurations
for i := 0; i < 2; i++ {
protocolKey := fmt.Sprintf("forward.%d.protocol", i)
privatePortKey := fmt.Sprintf("forward.%d.private_port", i)
privateEndPortKey := fmt.Sprintf("forward.%d.private_end_port", i)
publicPortKey := fmt.Sprintf("forward.%d.public_port", i)
publicEndPortKey := fmt.Sprintf("forward.%d.public_end_port", i)
uuidKey := fmt.Sprintf("forward.%d.uuid", i)

protocol := rs.Primary.Attributes[protocolKey]
privatePort := rs.Primary.Attributes[privatePortKey]
privateEndPort := rs.Primary.Attributes[privateEndPortKey]
publicPort := rs.Primary.Attributes[publicPortKey]
publicEndPort := rs.Primary.Attributes[publicEndPortKey]
uuid := rs.Primary.Attributes[uuidKey]

// Verify basic required fields exist
if protocol == "" {
return fmt.Errorf("Missing protocol for forward rule %d", i)
}
if privatePort == "" {
return fmt.Errorf("Missing private_port for forward rule %d", i)
}
if publicPort == "" {
return fmt.Errorf("Missing public_port for forward rule %d", i)
}
if uuid == "" {
return fmt.Errorf("Missing uuid for forward rule %d", i)
}

// Check for TCP rule with port range (8080-8090)
if protocol == "tcp" && privatePort == "8080" && publicPort == "8080" {
if privateEndPort != "8090" {
return fmt.Errorf("Expected TCP rule to have private_end_port=8090, got %s", privateEndPort)
}
if publicEndPort != "8090" {
return fmt.Errorf("Expected TCP rule to have public_end_port=8090, got %s", publicEndPort)
}
foundTCPRange = true
}

// Check for UDP rule with single port (9000)
if protocol == "udp" && privatePort == "9000" && publicPort == "9000" {
// For single port rules, end ports should be empty, "0", or equal to start ports
if privateEndPort != "" && privateEndPort != "0" && privateEndPort != "9000" {
return fmt.Errorf("Expected UDP rule to have empty, '0', or matching private_end_port, got %s", privateEndPort)
}
if publicEndPort != "" && publicEndPort != "0" && publicEndPort != "9000" {
return fmt.Errorf("Expected UDP rule to have empty, '0', or matching public_end_port, got %s", publicEndPort)
}
foundUDPSingle = true
}
}

if !foundTCPRange {
return fmt.Errorf("Expected to find TCP rule with port range 8080-8090")
}
if !foundUDPSingle {
return fmt.Errorf("Expected to find UDP rule with single port 9000")
}

return nil
}
}

func testAccCheckCloudStackPortForwardDestroy(s *terraform.State) error {
cs := testAccProvider.Meta().(*cloudstack.CloudStackClient)

Expand Down Expand Up @@ -201,3 +329,43 @@ resource "cloudstack_port_forward" "foo" {
virtual_machine_id = cloudstack_instance.foobar.id
}
}`

const testAccCloudStackPortForward_portRange = `
resource "cloudstack_network" "foo" {
name = "terraform-network"
display_text = "terraform-network"
cidr = "10.1.1.0/24"
network_offering = "DefaultIsolatedNetworkOfferingWithSourceNatService"
source_nat_ip = true
zone = "Sandbox-simulator"
}

resource "cloudstack_instance" "foobar" {
name = "terraform-test"
display_name = "terraform-updated"
service_offering= "Medium Instance"
network_id = cloudstack_network.foo.id
template = "CentOS 5.6 (64-bit) no GUI (Simulator)"
zone = "Sandbox-simulator"
expunge = true
}

resource "cloudstack_port_forward" "foo" {
ip_address_id = cloudstack_network.foo.source_nat_ip_id

forward {
protocol = "tcp"
private_port = 8080
private_end_port = 8090
public_port = 8080
public_end_port = 8090
virtual_machine_id = cloudstack_instance.foobar.id
}

forward {
protocol = "udp"
private_port = 9000
public_port = 9000
virtual_machine_id = cloudstack_instance.foobar.id
}
}`
10 changes: 8 additions & 2 deletions website/docs/r/port_forward.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,15 @@ The `forward` block supports:
* `protocol` - (Required) The name of the protocol to allow. Valid options are:
`tcp` and `udp`.

* `private_port` - (Required) The private port to forward to.
* `private_port` - (Required) The starting port of port forwarding rule's private port range.

* `public_port` - (Required) The public port to forward from.
* `private_end_port` - (Optional) The ending port of port forwarding rule's private port range.
If not specified, the private port will be used as the end port.

* `public_port` - (Required) The starting port of port forwarding rule's public port range.

* `public_end_port` - (Optional) The ending port of port forwarding rule's public port range.
If not specified, the public port will be used as the end port.

* `virtual_machine_id` - (Required) The ID of the virtual machine to forward to.

Expand Down
Loading