Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
2 changes: 1 addition & 1 deletion .github/workflows/acceptance.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ permissions:

env:
CLOUDSTACK_API_URL: http://localhost:8080/client/api
CLOUDSTACK_VERSIONS: "['4.19.0.1', '4.19.1.3', '4.19.2.0', '4.19.3.0', '4.20.1.0']"
CLOUDSTACK_VERSIONS: "['4.19.0.1', '4.19.1.3', '4.19.2.0', '4.19.3.0', '4.20.1.0', '4.22.0.0']"

jobs:
prepare-matrix:
Expand Down
50 changes: 50 additions & 0 deletions cloudstack/provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,11 @@ import (
"context"
"os"
"regexp"
"strconv"
"strings"
"testing"

"github.com/apache/cloudstack-go/v2/cloudstack"
"github.com/hashicorp/terraform-plugin-framework/providerserver"
"github.com/hashicorp/terraform-plugin-go/tfprotov6"
"github.com/hashicorp/terraform-plugin-mux/tf5to6server"
Expand Down Expand Up @@ -145,3 +148,50 @@ func testAccPreCheck(t *testing.T) {
t.Fatal("CLOUDSTACK_SECRET_KEY must be set for acceptance tests")
}
}

// testAccPreCheckStaticRouteNexthop checks if the CloudStack version supports
// the nexthop parameter for static routes (requires 4.22.0+)
func testAccPreCheckStaticRouteNexthop(t *testing.T) {
testAccPreCheck(t)
cs := testAccProvider.Meta().(*cloudstack.CloudStackClient)

// Check the API capabilities to get CloudStack version
p := cs.Configuration.NewListCapabilitiesParams()
caps, err := cs.Configuration.ListCapabilities(p)
if err != nil {
t.Skipf("Unable to check CloudStack capabilities: %v", err)
return
}

// Check CloudStack version - nexthop support was added in 4.22.0
if caps != nil && caps.Capabilities != nil && caps.Capabilities.Cloudstackversion != "" {
version := caps.Capabilities.Cloudstackversion

// Parse version string (e.g., "4.22.0.0" -> major=4, minor=22)
// Convert to numeric value: major * 1000 + minor (e.g., 4.22 -> 4022)
parts := strings.Split(version, ".")
Comment thread
bhouse-nexthop marked this conversation as resolved.
Outdated
if len(parts) >= 2 {
major := 0
minor := 0

// Parse major version - extract first numeric part
majorStr := regexp.MustCompile(`^\d+`).FindString(parts[0])
if majorStr != "" {
major, _ = strconv.Atoi(majorStr)
}

// Parse minor version - extract first numeric part
minorStr := regexp.MustCompile(`^\d+`).FindString(parts[1])
if minorStr != "" {
minor, _ = strconv.Atoi(minorStr)
}

versionNum := major*1000 + minor
const minVersionNum = 4022 // 4.22.0

if versionNum < minVersionNum {
t.Skipf("Static route nexthop parameter not supported in CloudStack version %s (requires 4.22.0+)", version)
}
}
}
}
49 changes: 44 additions & 5 deletions cloudstack/resource_cloudstack_static_route.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,26 @@ func resourceCloudStackStaticRoute() *schema.Resource {
},

"gateway_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Type: schema.TypeString,
Optional: true,
ForceNew: true,
ConflictsWith: []string{"nexthop", "vpc_id"},
},

"nexthop": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
ConflictsWith: []string{"gateway_id"},
RequiredWith: []string{"vpc_id"},
},

"vpc_id": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
ConflictsWith: []string{"gateway_id"},
RequiredWith: []string{"nexthop"},
},
Comment thread
bhouse-nexthop marked this conversation as resolved.
},
}
Expand All @@ -58,11 +75,20 @@ func resourceCloudStackStaticRouteCreate(d *schema.ResourceData, meta interface{
d.Get("cidr").(string),
)

// Set either gateway_id or nexthop+vpc_id (they are mutually exclusive)
if v, ok := d.GetOk("gateway_id"); ok {
p.SetGatewayid(v.(string))
}

// Create the new private gateway
if v, ok := d.GetOk("nexthop"); ok {
p.SetNexthop(v.(string))
}

if v, ok := d.GetOk("vpc_id"); ok {
p.SetVpcid(v.(string))
}
Comment thread
bhouse-nexthop marked this conversation as resolved.

// Create the new static route
r, err := cs.VPC.CreateStaticRoute(p)
if err != nil {
return fmt.Errorf("Error creating static route for %s: %s", d.Get("cidr").(string), err)
Expand All @@ -76,7 +102,7 @@ func resourceCloudStackStaticRouteCreate(d *schema.ResourceData, meta interface{
func resourceCloudStackStaticRouteRead(d *schema.ResourceData, meta interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)

// Get the virtual machine details
// Get the static route details
r, count, err := cs.VPC.GetStaticRouteByID(d.Id())
if err != nil {
if count == 0 {
Expand All @@ -90,6 +116,19 @@ func resourceCloudStackStaticRouteRead(d *schema.ResourceData, meta interface{})

d.Set("cidr", r.Cidr)

// Set gateway_id if it's not empty (indicates this route uses a gateway)
if r.Vpcgatewayid != "" {
d.Set("gateway_id", r.Vpcgatewayid)
}

// Set nexthop and vpc_id if nexthop is not empty (indicates this route uses nexthop)
if r.Nexthop != "" {
d.Set("nexthop", r.Nexthop)
if r.Vpcid != "" {
d.Set("vpc_id", r.Vpcid)
}
}

return nil
}

Expand Down
56 changes: 56 additions & 0 deletions cloudstack/resource_cloudstack_static_route_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,32 @@ func TestAccCloudStackStaticRoute_basic(t *testing.T) {
testAccCheckCloudStackStaticRouteExists(
"cloudstack_static_route.foo", &staticroute),
testAccCheckCloudStackStaticRouteAttributes(&staticroute),
resource.TestCheckResourceAttr(
"cloudstack_static_route.foo", "cidr", "172.16.0.0/16"),
),
},
},
})
}

func TestAccCloudStackStaticRoute_nexthop(t *testing.T) {
var staticroute cloudstack.StaticRoute

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheckStaticRouteNexthop(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckCloudStackStaticRouteDestroy,
Steps: []resource.TestStep{
{
Config: testAccCloudStackStaticRoute_nexthop,
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudStackStaticRouteExists(
"cloudstack_static_route.bar", &staticroute),
testAccCheckCloudStackStaticRouteNexthopAttributes(&staticroute),
resource.TestCheckResourceAttr(
"cloudstack_static_route.bar", "cidr", "192.168.0.0/16"),
resource.TestCheckResourceAttr(
"cloudstack_static_route.bar", "nexthop", "10.1.1.1"),
),
Comment thread
bhouse-nexthop marked this conversation as resolved.
},
},
Expand Down Expand Up @@ -89,6 +115,22 @@ func testAccCheckCloudStackStaticRouteAttributes(
}
}

func testAccCheckCloudStackStaticRouteNexthopAttributes(
staticroute *cloudstack.StaticRoute) resource.TestCheckFunc {
return func(s *terraform.State) error {

if staticroute.Cidr != "192.168.0.0/16" {
return fmt.Errorf("Bad CIDR: %s", staticroute.Cidr)
}

if staticroute.Nexthop != "10.1.1.1" {
return fmt.Errorf("Bad nexthop: %s", staticroute.Nexthop)
}

return nil
}
}

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

Expand Down Expand Up @@ -136,3 +178,17 @@ resource "cloudstack_static_route" "foo" {
cidr = "172.16.0.0/16"
gateway_id = cloudstack_private_gateway.foo.id
}`

const testAccCloudStackStaticRoute_nexthop = `
resource "cloudstack_vpc" "bar" {
name = "terraform-vpc-nexthop"
cidr = "10.0.0.0/8"
vpc_offering = "Default VPC offering"
zone = "Sandbox-simulator"
}

resource "cloudstack_static_route" "bar" {
cidr = "192.168.0.0/16"
nexthop = "10.1.1.1"
vpc_id = cloudstack_vpc.bar.id
}`
25 changes: 23 additions & 2 deletions website/docs/r/static_route.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,43 @@ Creates a static route for the given private gateway or VPC.

## Example Usage

Using a private gateway:

```hcl
resource "cloudstack_static_route" "default" {
cidr = "10.0.0.0/16"
gateway_id = "76f607e3-e8dc-4971-8831-b2a2b0cc4cb4"
}
```

Using a nexthop IP address:

```hcl
resource "cloudstack_static_route" "with_nexthop" {
cidr = "10.0.0.0/16"
nexthop = "192.168.1.1"
vpc_id = "76f607e3-e8dc-4971-8831-b2a2b0cc4cb4"
}
```

## Argument Reference

The following arguments are supported:

* `cidr` - (Required) The CIDR for the static route. Changing this forces
a new resource to be created.

* `gateway_id` - (Required) The ID of the Private gateway. Changing this forces
a new resource to be created.
* `gateway_id` - (Optional) The ID of the Private gateway. Changing this forces
a new resource to be created. Conflicts with `nexthop` and `vpc_id`.

* `nexthop` - (Optional) The IP address of the nexthop for the static route.
Changing this forces a new resource to be created. Conflicts with `gateway_id`.
Must be used together with `vpc_id`.

* `vpc_id` - (Optional) The ID of the VPC. Required when using `nexthop`.
Changing this forces a new resource to be created. Conflicts with `gateway_id`.

**Note:** Either `gateway_id` or (`nexthop` + `vpc_id`) must be specified.
Comment thread
bhouse-nexthop marked this conversation as resolved.

## Attributes Reference

Expand Down
Loading