Skip to content

Commit

Permalink
feat(router): static routes support
Browse files Browse the repository at this point in the history
  • Loading branch information
villevsv-upcloud committed Sep 28, 2023
1 parent 106d26f commit 1a7ce97
Show file tree
Hide file tree
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
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
107 changes: 94 additions & 13 deletions internal/service/router/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
}
Expand All @@ -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(),
}
Expand All @@ -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(),
Expand Down
55 changes: 48 additions & 7 deletions upcloud/resource_upcloud_router_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
}),
),
},
},
Expand All @@ -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",
}),
),
},
},
Expand All @@ -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),
),
Expand Down Expand Up @@ -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 {
Expand Down

0 comments on commit 1a7ce97

Please sign in to comment.