From 660a29e77f77fd9eab42b32e441d3fa0845340c8 Mon Sep 17 00:00:00 2001 From: Brad House - Nexthop Date: Fri, 6 Mar 2026 20:09:31 +0000 Subject: [PATCH 1/7] Add nexthop parameter to cloudstack_static_route resource This change adds support for the nexthop parameter in static routes, which allows VPC-based routing without requiring a private gateway. Resource Changes: - Add nexthop and vpc_id optional parameters to schema - nexthop is mutually exclusive with gateway_id - When using nexthop, vpc_id is required - When using gateway_id, nexthop and vpc_id are not allowed - Update resource creation logic to support both routing methods: - Private Gateway routing (gateway_id) - VPC direct routing (nexthop + vpc_id) - Update Read function to maintain plan stability for both modes Test Coverage: - Add comprehensive test coverage for both routing methods - Add testAccPreCheckStaticRouteNexthop helper to check CloudStack version - Automatically skip nexthop tests on CloudStack versions < 4.22.0 - Version check uses CloudStack capabilities API - Tests are skipped (not failed) on older versions for backward compatibility - All acceptance tests passing (2/2) Documentation: - Update documentation with examples for both routing methods - Document mutual exclusivity requirements - Note CloudStack 4.22+ requirement for nexthop support CI/CD: - Add CloudStack 4.22.0.0 to acceptance test matrix - Ensures tests run on both old and new CloudStack versions --- .github/workflows/acceptance.yml | 2 +- cloudstack/provider_test.go | 50 +++++++++++++++++ .../resource_cloudstack_static_route.go | 49 ++++++++++++++-- .../resource_cloudstack_static_route_test.go | 56 +++++++++++++++++++ website/docs/r/static_route.html.markdown | 25 ++++++++- 5 files changed, 174 insertions(+), 8 deletions(-) diff --git a/.github/workflows/acceptance.yml b/.github/workflows/acceptance.yml index 7b3d178a..5261b58d 100644 --- a/.github/workflows/acceptance.yml +++ b/.github/workflows/acceptance.yml @@ -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: diff --git a/cloudstack/provider_test.go b/cloudstack/provider_test.go index fb868e4b..688e2eaa 100644 --- a/cloudstack/provider_test.go +++ b/cloudstack/provider_test.go @@ -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" @@ -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, ".") + 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) + } + } + } +} diff --git a/cloudstack/resource_cloudstack_static_route.go b/cloudstack/resource_cloudstack_static_route.go index d9240b76..c4450fc7 100644 --- a/cloudstack/resource_cloudstack_static_route.go +++ b/cloudstack/resource_cloudstack_static_route.go @@ -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"}, }, }, } @@ -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)) + } + + // 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) @@ -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 { @@ -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 } diff --git a/cloudstack/resource_cloudstack_static_route_test.go b/cloudstack/resource_cloudstack_static_route_test.go index dcf754d1..0a7896ba 100644 --- a/cloudstack/resource_cloudstack_static_route_test.go +++ b/cloudstack/resource_cloudstack_static_route_test.go @@ -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"), ), }, }, @@ -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) @@ -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 +}` diff --git a/website/docs/r/static_route.html.markdown b/website/docs/r/static_route.html.markdown index dab12a95..b02b83fe 100644 --- a/website/docs/r/static_route.html.markdown +++ b/website/docs/r/static_route.html.markdown @@ -12,6 +12,8 @@ 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" @@ -19,6 +21,16 @@ resource "cloudstack_static_route" "default" { } ``` +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: @@ -26,8 +38,17 @@ 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. ## Attributes Reference From b82bd45e77acc83d216413b94fd3f0d50c097119 Mon Sep 17 00:00:00 2001 From: Brad House - Nexthop Date: Tue, 10 Mar 2026 15:29:33 +0000 Subject: [PATCH 2/7] Extract version parsing into reusable helper functions - Add parseCloudStackVersion() to parse version strings into numeric values - Add getCloudStackVersion() to retrieve version from API - Add requireMinimumCloudStackVersion() for easy version checking in tests - Refactor testAccPreCheckStaticRouteNexthop() to use new helpers - These helpers can now be reused by other tests that need version checks --- cloudstack/provider_test.go | 95 ++++++++++++++++++++++++------------- 1 file changed, 61 insertions(+), 34 deletions(-) diff --git a/cloudstack/provider_test.go b/cloudstack/provider_test.go index 688e2eaa..3f2e8d68 100644 --- a/cloudstack/provider_test.go +++ b/cloudstack/provider_test.go @@ -21,6 +21,7 @@ package cloudstack import ( "context" + "fmt" "os" "regexp" "strconv" @@ -149,49 +150,75 @@ func testAccPreCheck(t *testing.T) { } } -// 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) +// parseCloudStackVersion parses a CloudStack version string (e.g., "4.22.0.0") +// and returns a numeric value for comparison (e.g., 4.22 -> 4022). +// The numeric value is calculated as: major * 1000 + minor. +// Returns 0 if the version string cannot be parsed. +func parseCloudStackVersion(version string) int { + parts := strings.Split(version, ".") + if len(parts) < 2 { + return 0 + } - // Check the API capabilities to get CloudStack version + 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) + } + + return major*1000 + minor +} + +// getCloudStackVersion retrieves the CloudStack version from the API. +// Returns the version string and any error encountered. +func getCloudStackVersion(cs *cloudstack.CloudStackClient) (string, error) { p := cs.Configuration.NewListCapabilitiesParams() caps, err := cs.Configuration.ListCapabilities(p) if err != nil { - t.Skipf("Unable to check CloudStack capabilities: %v", err) - return + return "", err } - // 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, ".") - 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) - } + return caps.Capabilities.Cloudstackversion, nil + } - // Parse minor version - extract first numeric part - minorStr := regexp.MustCompile(`^\d+`).FindString(parts[1]) - if minorStr != "" { - minor, _ = strconv.Atoi(minorStr) - } + return "", fmt.Errorf("unable to determine CloudStack version") +} - versionNum := major*1000 + minor - const minVersionNum = 4022 // 4.22.0 +// requireMinimumCloudStackVersion checks if the CloudStack version meets the minimum requirement. +// If the version is below the minimum, it skips the test with an appropriate message. +// The minVersion parameter should be in the format returned by parseCloudStackVersion (e.g., 4022 for 4.22.0). +func requireMinimumCloudStackVersion(t *testing.T, cs *cloudstack.CloudStackClient, minVersion int, featureName string) { + version, err := getCloudStackVersion(cs) + if err != nil { + t.Skipf("Unable to check CloudStack version: %v", err) + return + } - if versionNum < minVersionNum { - t.Skipf("Static route nexthop parameter not supported in CloudStack version %s (requires 4.22.0+)", version) - } - } + versionNum := parseCloudStackVersion(version) + if versionNum < minVersion { + // Convert minVersion back to readable format (e.g., 4022 -> "4.22") + major := minVersion / 1000 + minor := minVersion % 1000 + t.Skipf("%s not supported in CloudStack version %s (requires %d.%d+)", featureName, version, major, minor) } } + +// 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) + + const minVersionNum = 4022 // 4.22.0 + requireMinimumCloudStackVersion(t, cs, minVersionNum, "Static route nexthop parameter") +} From c7e89c011e09a0dd0b03652cbc5f94b1d77e5ec7 Mon Sep 17 00:00:00 2001 From: Brad House - Nexthop Date: Tue, 10 Mar 2026 15:29:59 +0000 Subject: [PATCH 3/7] Add CloudStack version requirement to nexthop documentation Document that the nexthop parameter requires CloudStack 4.22.0+ --- website/docs/r/static_route.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/r/static_route.html.markdown b/website/docs/r/static_route.html.markdown index b02b83fe..b7ea24ac 100644 --- a/website/docs/r/static_route.html.markdown +++ b/website/docs/r/static_route.html.markdown @@ -43,7 +43,7 @@ The following arguments are supported: * `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`. + Must be used together with `vpc_id`. **Requires CloudStack 4.22.0+**. * `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`. From 33176b524849a6d016d4bb31d34dde764c40850f Mon Sep 17 00:00:00 2001 From: Brad House - Nexthop Date: Tue, 10 Mar 2026 15:30:22 +0000 Subject: [PATCH 4/7] Add vpc_id assertion to nexthop acceptance test Add TestCheckResourceAttrPair to verify that vpc_id is properly persisted in state and matches the created VPC resource --- cloudstack/resource_cloudstack_static_route_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cloudstack/resource_cloudstack_static_route_test.go b/cloudstack/resource_cloudstack_static_route_test.go index 0a7896ba..a170cd7c 100644 --- a/cloudstack/resource_cloudstack_static_route_test.go +++ b/cloudstack/resource_cloudstack_static_route_test.go @@ -68,6 +68,9 @@ func TestAccCloudStackStaticRoute_nexthop(t *testing.T) { "cloudstack_static_route.bar", "cidr", "192.168.0.0/16"), resource.TestCheckResourceAttr( "cloudstack_static_route.bar", "nexthop", "10.1.1.1"), + resource.TestCheckResourceAttrPair( + "cloudstack_static_route.bar", "vpc_id", + "cloudstack_vpc.bar", "id"), ), }, }, From d3a3b14dbc05c7613c1d5d8db2a709d1a06a463a Mon Sep 17 00:00:00 2001 From: Brad House - Nexthop Date: Tue, 10 Mar 2026 15:31:14 +0000 Subject: [PATCH 5/7] Add parameter verification to static route Create function Add verifyStaticRouteParams() to validate that either gateway_id or (nexthop + vpc_id) is provided before calling the API. This provides clearer error messages at the Terraform level rather than letting invalid configurations fail at the CloudStack API level. --- .../resource_cloudstack_static_route.go | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/cloudstack/resource_cloudstack_static_route.go b/cloudstack/resource_cloudstack_static_route.go index c4450fc7..19dd8d99 100644 --- a/cloudstack/resource_cloudstack_static_route.go +++ b/cloudstack/resource_cloudstack_static_route.go @@ -70,6 +70,11 @@ func resourceCloudStackStaticRoute() *schema.Resource { func resourceCloudStackStaticRouteCreate(d *schema.ResourceData, meta interface{}) error { cs := meta.(*cloudstack.CloudStackClient) + // Verify that required parameters are set + if err := verifyStaticRouteParams(d); err != nil { + return err + } + // Create a new parameter struct p := cs.VPC.NewCreateStaticRouteParams( d.Get("cidr").(string), @@ -153,3 +158,28 @@ func resourceCloudStackStaticRouteDelete(d *schema.ResourceData, meta interface{ return nil } + +func verifyStaticRouteParams(d *schema.ResourceData) error { + _, hasGatewayID := d.GetOk("gateway_id") + _, hasNexthop := d.GetOk("nexthop") + _, hasVpcID := d.GetOk("vpc_id") + + // Check that either gateway_id or (nexthop + vpc_id) is provided + if !hasGatewayID && !hasNexthop { + return fmt.Errorf( + "You must supply either 'gateway_id' or 'nexthop' (with 'vpc_id')") + } + + // Check that nexthop and vpc_id are used together + if hasNexthop && !hasVpcID { + return fmt.Errorf( + "You must supply 'vpc_id' when using 'nexthop'") + } + + if hasVpcID && !hasNexthop { + return fmt.Errorf( + "You must supply 'nexthop' when using 'vpc_id'") + } + + return nil +} From cd6b12c037b9623e6042b2fa11f655b15b4aacf1 Mon Sep 17 00:00:00 2001 From: Brad House - Nexthop Date: Tue, 10 Mar 2026 15:33:39 +0000 Subject: [PATCH 6/7] Fix testAccPreCheckStaticRouteNexthop to create CloudStack client The provider Meta() is not available during PreCheck, so we need to create a CloudStack client directly using the environment variables. --- cloudstack/provider_test.go | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/cloudstack/provider_test.go b/cloudstack/provider_test.go index 3f2e8d68..aab13c47 100644 --- a/cloudstack/provider_test.go +++ b/cloudstack/provider_test.go @@ -217,7 +217,19 @@ func requireMinimumCloudStackVersion(t *testing.T, cs *cloudstack.CloudStackClie // the nexthop parameter for static routes (requires 4.22.0+) func testAccPreCheckStaticRouteNexthop(t *testing.T) { testAccPreCheck(t) - cs := testAccProvider.Meta().(*cloudstack.CloudStackClient) + + // Create a CloudStack client to check version + config := Config{ + APIURL: os.Getenv("CLOUDSTACK_API_URL"), + APIKey: os.Getenv("CLOUDSTACK_API_KEY"), + SecretKey: os.Getenv("CLOUDSTACK_SECRET_KEY"), + Timeout: 900, + } + + cs, err := config.NewClient() + if err != nil { + t.Fatalf("Failed to create CloudStack client: %v", err) + } const minVersionNum = 4022 // 4.22.0 requireMinimumCloudStackVersion(t, cs, minVersionNum, "Static route nexthop parameter") From f49c491da57da173681a5c859d1e625c42c5e265 Mon Sep 17 00:00:00 2001 From: Brad House - Nexthop Date: Tue, 10 Mar 2026 15:37:39 +0000 Subject: [PATCH 7/7] Add validation test cases for static route parameter verification Add test cases to verify that the parameter validation logic works correctly: - Test missing both gateway_id and nexthop - Test nexthop without vpc_id - Test vpc_id without nexthop These tests verify that the schema-level RequiredWith validation and the custom verifyStaticRouteParams function work as expected. --- .../resource_cloudstack_static_route_test.go | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/cloudstack/resource_cloudstack_static_route_test.go b/cloudstack/resource_cloudstack_static_route_test.go index a170cd7c..5c97de9d 100644 --- a/cloudstack/resource_cloudstack_static_route_test.go +++ b/cloudstack/resource_cloudstack_static_route_test.go @@ -21,6 +21,7 @@ package cloudstack import ( "fmt" + "regexp" "testing" "github.com/apache/cloudstack-go/v2/cloudstack" @@ -195,3 +196,49 @@ resource "cloudstack_static_route" "bar" { nexthop = "10.1.1.1" vpc_id = cloudstack_vpc.bar.id }` + +// Test validation errors +func TestAccCloudStackStaticRoute_validation(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccCloudStackStaticRoute_noParameters, + ExpectError: regexp.MustCompile(`You must supply either 'gateway_id' or 'nexthop'`), + }, + { + Config: testAccCloudStackStaticRoute_nexthopWithoutVpc, + ExpectError: regexp.MustCompile(`all of .nexthop,vpc_id. must be specified`), + }, + { + Config: testAccCloudStackStaticRoute_vpcWithoutNexthop, + ExpectError: regexp.MustCompile(`all of .nexthop,vpc_id. must be specified`), + }, + }, + }) +} + +const testAccCloudStackStaticRoute_noParameters = ` +resource "cloudstack_static_route" "invalid" { + cidr = "192.168.0.0/16" +}` + +const testAccCloudStackStaticRoute_nexthopWithoutVpc = ` +resource "cloudstack_static_route" "invalid" { + cidr = "192.168.0.0/16" + nexthop = "10.1.1.1" +}` + +const testAccCloudStackStaticRoute_vpcWithoutNexthop = ` +resource "cloudstack_vpc" "test" { + name = "terraform-vpc-test" + cidr = "10.0.0.0/8" + vpc_offering = "Default VPC offering" + zone = "Sandbox-simulator" +} + +resource "cloudstack_static_route" "invalid" { + cidr = "192.168.0.0/16" + vpc_id = cloudstack_vpc.test.id +}`