Skip to content

Commit

Permalink
feat(router): static routes support
Browse files Browse the repository at this point in the history
villevsv-upcloud committed Sep 28, 2023
1 parent 106d26f commit 1a7ce97
Showing 5 changed files with 146 additions and 23 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -8,6 +8,7 @@ See updating [Changelog example here](https://keepachangelog.com/en/1.0.0/)
### Added
- gateway: add read-only `addresses` field
- kubernetes: `control_plane_ip_filter` field to `upcloud_kubernetes_cluster` resource
- router: `static_routes` block to `upcloud_router` resource

### Changed
- kubernetes: remove node group maximum value validation. The maximum number of nodes (in the cluster) is determined by the cluster plan and the validation is done on the API side.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -3,7 +3,7 @@ module github.com/UpCloudLtd/terraform-provider-upcloud
go 1.20

require (
github.com/UpCloudLtd/upcloud-go-api/v6 v6.6.0
github.com/UpCloudLtd/upcloud-go-api/v6 v6.6.1-0.20230927070743-7dd13ddf6ef5
github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320
github.com/hashicorp/go-retryablehttp v0.6.8
github.com/hashicorp/go-uuid v1.0.3
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
@@ -4,8 +4,8 @@ github.com/Microsoft/go-winio v0.4.16 h1:FtSW/jqD+l4ba5iPBj9CODVtgfYAD8w2wS923g/
github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=
github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7 h1:YoJbenK9C67SkzkDfmQuVln04ygHj3vjZfd9FL+GmQQ=
github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
github.com/UpCloudLtd/upcloud-go-api/v6 v6.6.0 h1:Fc9a083OBzl8i4pDV2KXCAfxo4gCjJFHgRuPvRnroBY=
github.com/UpCloudLtd/upcloud-go-api/v6 v6.6.0/go.mod h1:I8rWmBBl+OhiY3AGzKbrobiE5TsLCLNYkCQxE4eJcTg=
github.com/UpCloudLtd/upcloud-go-api/v6 v6.6.1-0.20230927070743-7dd13ddf6ef5 h1:gwwrCax+n27YwYXHmki5DAcDBNFuB5wFJEWkGhPZ8Gs=
github.com/UpCloudLtd/upcloud-go-api/v6 v6.6.1-0.20230927070743-7dd13ddf6ef5/go.mod h1:I8rWmBBl+OhiY3AGzKbrobiE5TsLCLNYkCQxE4eJcTg=
github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk=
github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4=
github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo=
107 changes: 94 additions & 13 deletions internal/service/router/router.go
Original file line number Diff line number Diff line change
@@ -5,10 +5,13 @@ import (
"fmt"

"github.com/UpCloudLtd/terraform-provider-upcloud/internal/utils"

"github.com/UpCloudLtd/upcloud-go-api/v6/upcloud"
"github.com/UpCloudLtd/upcloud-go-api/v6/upcloud/request"
"github.com/UpCloudLtd/upcloud-go-api/v6/upcloud/service"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
)

func ResourceRouter() *schema.Resource {
@@ -42,20 +45,59 @@ func ResourceRouter() *schema.Resource {
Type: schema.TypeString,
},
},
"static_route": {
Description: "A collection of static routes for this router",
Optional: true,
Type: schema.TypeSet,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"name": {
Description: "Name or description of the route.",
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"nexthop": {
Description: "Next hop address. NOTE: For static route to be active the next hop has to be an address of a reachable running Cloud Server in one of the Private Networks attached to the router.",
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.Any(validation.IsIPv4Address, validation.IsIPv6Address),
},
"route": {
Description: "Destination prefix of the route.",
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.Any(validation.IsCIDR),
},
},
},
},
},
}
}

func resourceRouterCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
func resourceRouterCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) (diags diag.Diagnostics) {
client := meta.(*service.Service)

var diags diag.Diagnostics

opts := &request.CreateRouterRequest{
req := &request.CreateRouterRequest{
Name: d.Get("name").(string),
}

router, err := client.CreateRouter(ctx, opts)
if v, ok := d.GetOk("static_route"); ok {
for _, staticRoute := range v.(*schema.Set).List() {
staticRouteData := staticRoute.(map[string]interface{})

r := upcloud.StaticRoute{
Name: staticRouteData["name"].(string),
Nexthop: staticRouteData["nexthop"].(string),
Route: staticRouteData["route"].(string),
}

req.StaticRoutes = append(req.StaticRoutes, r)
}
}

router, err := client.CreateRouter(ctx, req)
if err != nil {
return diag.FromErr(err)
}
@@ -70,16 +112,27 @@ func resourceRouterCreate(ctx context.Context, d *schema.ResourceData, meta inte
return diag.FromErr(err)
}

var staticRoutes []map[string]interface{}
for _, staticRoute := range router.StaticRoutes {
staticRoutes = append(staticRoutes, map[string]interface{}{
"name": staticRoute.Name,
"nexthop": staticRoute.Nexthop,
"route": staticRoute.Route,
})
}

if err := d.Set("static_route", staticRoutes); err != nil {
return diag.FromErr(err)
}

d.SetId(router.UUID)

return diags
}

func resourceRouterRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
func resourceRouterRead(ctx context.Context, d *schema.ResourceData, meta interface{}) (diags diag.Diagnostics) {
client := meta.(*service.Service)

var diags diag.Diagnostics

opts := &request.GetRouterDetailsRequest{
UUID: d.Id(),
}
@@ -105,31 +158,59 @@ func resourceRouterRead(ctx context.Context, d *schema.ResourceData, meta interf
return diag.FromErr(err)
}

var staticRoutes []map[string]interface{}
for _, staticRoute := range router.StaticRoutes {
staticRoutes = append(staticRoutes, map[string]interface{}{
"name": staticRoute.Name,
"nexthop": staticRoute.Nexthop,
"route": staticRoute.Route,
})
}

if err := d.Set("static_route", staticRoutes); err != nil {
return diag.FromErr(err)
}

return diags
}

func resourceRouterUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*service.Service)

opts := &request.ModifyRouterRequest{
req := &request.ModifyRouterRequest{
UUID: d.Id(),
}

if v, ok := d.GetOk("name"); ok {
opts.Name = v.(string)
req.Name = v.(string)
}

_, err := client.ModifyRouter(ctx, opts)
var staticRoutes []upcloud.StaticRoute

if v, ok := d.GetOk("static_route"); ok {
for _, staticRoute := range v.(*schema.Set).List() {
staticRouteData := staticRoute.(map[string]interface{})

staticRoutes = append(staticRoutes, upcloud.StaticRoute{
Name: staticRouteData["name"].(string),
Nexthop: staticRouteData["nexthop"].(string),
Route: staticRouteData["route"].(string),
})
}
}

req.StaticRoutes = &staticRoutes

_, err := client.ModifyRouter(ctx, req)
if err != nil {
return diag.FromErr(err)
}

return resourceRouterRead(ctx, d, meta)
}

func resourceRouterDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
func resourceRouterDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) (diags diag.Diagnostics) {
client := meta.(*service.Service)
var diags diag.Diagnostics

router, err := client.GetRouterDetails(ctx, &request.GetRouterDetailsRequest{
UUID: d.Id(),
55 changes: 48 additions & 7 deletions upcloud/resource_upcloud_router_test.go
Original file line number Diff line number Diff line change
@@ -20,16 +20,22 @@ func TestAccUpCloudRouter(t *testing.T) {
var router upcloud.Router
name := fmt.Sprintf("tf-test-%s", acctest.RandString(10))

staticRoutes := []upcloud.StaticRoute{{Name: "my-example-route", Nexthop: "10.0.0.100", Route: "0.0.0.0/0"}}
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProviderFactories: testAccProviderFactories(&providers),
CheckDestroy: testAccCheckRouterDestroy,
Steps: []resource.TestStep{
{
Config: testAccRouterConfig(name),
Config: testAccRouterConfig(name, staticRoutes),
Check: resource.ComposeTestCheckFunc(
testAccCheckRouterExists("upcloud_router.my_example_router", &router),
testAccCheckUpCloudRouterAttributes(&router, name),
resource.TestCheckTypeSetElemNestedAttrs("upcloud_router.my_example_router", "static_route.*", map[string]string{
"name": "my-example-route",
"nexthop": "10.0.0.100",
"route": "0.0.0.0/0",
}),
),
},
},
@@ -43,23 +49,36 @@ func TestAccUpCloudRouter_update(t *testing.T) {
name := fmt.Sprintf("tf-test-%s", acctest.RandString(10))
updateName := fmt.Sprintf("tf-test-update-%s", acctest.RandString(10))

staticRoutes := []upcloud.StaticRoute{{Nexthop: "10.0.0.100", Route: "0.0.0.0/0"}}
updateStaticRoutes := []upcloud.StaticRoute{{Name: "my-example-route-2", Nexthop: "10.0.0.101", Route: "0.0.0.0/0"}}

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProviderFactories: testAccProviderFactories(&providers),
CheckDestroy: testAccCheckRouterDestroy,
Steps: []resource.TestStep{
{
Config: testAccRouterConfig(name),
Config: testAccRouterConfig(name, staticRoutes),
Check: resource.ComposeTestCheckFunc(
testAccCheckRouterExists("upcloud_router.my_example_router", &router),
testAccCheckUpCloudRouterAttributes(&router, name),
resource.TestCheckTypeSetElemNestedAttrs("upcloud_router.my_example_router", "static_route.*", map[string]string{
"name": "static-route-0",
"nexthop": "10.0.0.100",
"route": "0.0.0.0/0",
}),
),
},
{
Config: testAccRouterConfig(updateName),
Config: testAccRouterConfig(updateName, updateStaticRoutes),
Check: resource.ComposeTestCheckFunc(
testAccCheckRouterExists("upcloud_router.my_example_router", &router),
testAccCheckUpCloudRouterAttributes(&router, updateName),
resource.TestCheckTypeSetElemNestedAttrs("upcloud_router.my_example_router", "static_route.*", map[string]string{
"name": "my-example-route-2",
"nexthop": "10.0.0.101",
"route": "0.0.0.0/0",
}),
),
},
},
@@ -77,7 +96,7 @@ func TestAccUpCloudRouter_import(t *testing.T) {
CheckDestroy: testAccCheckRouterDestroy,
Steps: []resource.TestStep{
{
Config: testAccRouterConfig(name),
Config: testAccRouterConfig(name, nil),
Check: resource.ComposeTestCheckFunc(
testAccCheckRouterExists("upcloud_router.my_example_router", &router),
),
@@ -373,11 +392,33 @@ func testAccCheckRouterNetworkDestroy(s *terraform.State) error {
return nil
}

func testAccRouterConfig(name string) string {
return fmt.Sprintf(`
func testAccRouterConfig(name string, staticRoutes []upcloud.StaticRoute) string {
s := fmt.Sprintf(`
resource "upcloud_router" "my_example_router" {
name = "%s"
}`, name)
`, name)

if len(staticRoutes) > 0 {
for _, staticRoute := range staticRoutes {
s = s + fmt.Sprintf(`
static_route {
nexthop = "%s"
route = "%s"
`, staticRoute.Nexthop, staticRoute.Route)

if len(staticRoute.Name) > 0 {
s = s + fmt.Sprintf(`
name = "%s"
`, staticRoute.Name)
}
}
s = s + `
}`
}
s = s + `
}
`
return s
}

func testAccNetworkRouterAttached(network *upcloud.Network, router *upcloud.Router) resource.TestCheckFunc {

0 comments on commit 1a7ce97

Please sign in to comment.