Skip to content

Commit

Permalink
Merge pull request #1572 from terinjokes/teams_route
Browse files Browse the repository at this point in the history
adds cloudflare_teams_route and acceptance tests
  • Loading branch information
jacobbednarz authored Apr 22, 2022
2 parents 1e20b4a + 722b1ba commit 1cc74ba
Show file tree
Hide file tree
Showing 7 changed files with 316 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .changelog/1572.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:new-resource
cloudflare_teams_route
```
1 change: 1 addition & 0 deletions cloudflare/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ func Provider() *schema.Provider {
"cloudflare_teams_account": resourceCloudflareTeamsAccount(),
"cloudflare_teams_list": resourceCloudflareTeamsList(),
"cloudflare_teams_location": resourceCloudflareTeamsLocation(),
"cloudflare_teams_route": resourceCloudflareTeamsRoute(),
"cloudflare_teams_rule": resourceCloudflareTeamsRule(),
"cloudflare_teams_proxy_endpoint": resourceCloudflareTeamsProxyEndpoint(),
"cloudflare_waf_group": resourceCloudflareWAFGroup(),
Expand Down
134 changes: 134 additions & 0 deletions cloudflare/resource_cloudflare_teams_route.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package cloudflare

import (
"context"
"errors"
"fmt"
"strings"

"github.com/cloudflare/cloudflare-go"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

func resourceCloudflareTeamsRoute() *schema.Resource {
return &schema.Resource{
Schema: resourceCloudflareTeamsRouteSchema(),
Create: resourceCloudflareTeamsRouteCreate,
Read: resourceCloudflareTeamsRouteRead,
Update: resourceCloudflareTeamsRouteUpdate,
Delete: resourceCloudflareTeamsRouteDelete,
Importer: &schema.ResourceImporter{
State: resourceCloudflareTeamsRouteImport,
},
}
}

func resourceCloudflareTeamsRouteRead(d *schema.ResourceData, meta interface{}) error {
client := meta.(*cloudflare.API)

tunnelRoute, err := client.GetTunnelRouteForIP(context.Background(), cloudflare.TunnelRoutesForIPParams{
AccountID: d.Get("account_id").(string),
Network: d.Get("network").(string),
})
if err != nil {
// FIXME(2022-04-21): Until the API returns a valid v4 compatible envelope, we need to
// check if the error message is related to problems unmarshalling the response _or_
// an expected not found error.
var notFoundError *cloudflare.NotFoundError
if strings.Contains(err.Error(), "error unmarshalling the JSON response error body") || errors.As(err, &notFoundError) {
d.SetId("")
return nil
}

return fmt.Errorf("error reading Tunnel Route for Network %q: %w", d.Id(), err)
}

d.Set("tunnel_id", tunnelRoute.TunnelID)
d.Set("network", tunnelRoute.Network)

if len(tunnelRoute.Comment) > 0 {
d.Set("comment", tunnelRoute.Comment)
}

return nil
}

func resourceCloudflareTeamsRouteCreate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*cloudflare.API)

resource := cloudflare.TunnelRoutesCreateParams{
AccountID: d.Get("account_id").(string),
TunnelID: d.Get("tunnel_id").(string),
Network: d.Get("network").(string),
}

if comment, ok := d.Get("comment").(string); ok {
resource.Comment = comment
}

newTunnelRoute, err := client.CreateTunnelRoute(context.Background(), resource)
if err != nil {
return fmt.Errorf("error creating Tunnel Route for Network %q: %w", d.Get("network").(string), err)
}

d.SetId(newTunnelRoute.Network)

return resourceCloudflareTeamsRouteRead(d, meta)
}

func resourceCloudflareTeamsRouteUpdate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*cloudflare.API)

resource := cloudflare.TunnelRoutesUpdateParams{
AccountID: d.Get("account_id").(string),
TunnelID: d.Get("tunnel_id").(string),
Network: d.Get("network").(string),
}

if comment, ok := d.Get("comment").(string); ok {
resource.Comment = comment
}

_, err := client.UpdateTunnelRoute(context.Background(), resource)
if err != nil {
return fmt.Errorf("error updating Tunnel Route for Network %q: %w", d.Get("network").(string), err)
}

return resourceCloudflareTeamsRouteRead(d, meta)
}

func resourceCloudflareTeamsRouteDelete(d *schema.ResourceData, meta interface{}) error {
client := meta.(*cloudflare.API)

err := client.DeleteTunnelRoute(context.Background(), cloudflare.TunnelRoutesDeleteParams{
AccountID: d.Get("account_id").(string),
Network: d.Get("network").(string),
})
if err != nil {
return fmt.Errorf("error deleting Tunnel Route for Network %q: %w", d.Get("network").(string), err)
}

return nil
}

func resourceCloudflareTeamsRouteImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
attributes := strings.SplitN(d.Id(), "/", 3)

if len(attributes) != 2 {
return nil, fmt.Errorf(`invalid id (%q) specified, should be in format "accountID/tunnelID/network"`, d.Id())
}

accountID, tunnelID, network := attributes[0], attributes[1], attributes[2]

d.SetId(network)
d.Set("account_id", accountID)
d.Set("tunnel_id", tunnelID)
d.Set("network", network)

err := resourceCloudflareTeamsRouteRead(d, meta)
if err != nil {
return nil, err
}

return []*schema.ResourceData{d}, nil
}
109 changes: 109 additions & 0 deletions cloudflare/resource_cloudflare_teams_route_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package cloudflare

import (
"context"
"errors"
"fmt"
"os"
"testing"

"github.com/cloudflare/cloudflare-go"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
)

func TestAccCloudflareTeamsRouteExists(t *testing.T) {
rnd := generateRandomResourceName()
name := fmt.Sprintf("cloudflare_teams_route.%s", rnd)
accountID := os.Getenv("CLOUDFLARE_ACCOUNT_ID")

var TunnelRoute cloudflare.TunnelRoute

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheckAccount(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccCloudflareTeamsRouteSimple(rnd, rnd, accountID, "10.0.0.20/32"),
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudflareTeamsRouteExists(name, &TunnelRoute),
resource.TestCheckResourceAttr(name, "account_id", accountID),
resource.TestCheckResourceAttrSet(name, "tunnel_id"),
resource.TestCheckResourceAttr(name, "network", "10.0.0.20/32"),
resource.TestCheckResourceAttr(name, "comment", rnd),
),
},
},
})
}

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

if rs.Primary.ID == "" {
return errors.New("No Teams route is set")
}

client := testAccProvider.Meta().(*cloudflare.API)
foundTunnelRoute, err := client.GetTunnelRouteForIP(context.Background(), cloudflare.TunnelRoutesForIPParams{
Network: rs.Primary.ID,
})

if err != nil {
return err
}

*route = foundTunnelRoute

return nil
}
}

func TestAccCloudflareTeamsRouteUpdateComment(t *testing.T) {
rnd := generateRandomResourceName()
name := fmt.Sprintf("cloudflare_teams_route.%s", rnd)
accountID := os.Getenv("CLOUDFLARE_ACCOUNT_ID")

var TunnelRoute cloudflare.TunnelRoute

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheckAccount(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccCloudflareTeamsRouteSimple(rnd, rnd, accountID, "10.0.0.10/32"),
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudflareTeamsRouteExists(name, &TunnelRoute),
resource.TestCheckResourceAttr(name, "comment", rnd),
),
},
{
Config: testAccCloudflareTeamsRouteSimple(rnd, rnd+"-updated", accountID, "10.0.0.10/32"),
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudflareTeamsRouteExists(name, &TunnelRoute),
resource.TestCheckResourceAttr(name, "comment", rnd+"-updated"),
),
},
},
})
}

func testAccCloudflareTeamsRouteSimple(ID, comment, accountID, network string) string {
return fmt.Sprintf(`
resource "cloudflare_argo_tunnel" "%[1]s" {
account_id = "%[3]s"
name = "%[1]s"
secret = "AQIDBAUGBwgBAgMEBQYHCAECAwQFBgcIAQIDBAUGBwg="
}
resource "cloudflare_teams_route" "%[1]s" {
account_id = "%[3]s"
tunnel_id = cloudflare_argo_tunnel.%[1]s.id
network = "%[4]s"
comment = "%[2]s"
}`, ID, comment, accountID, network)
}
27 changes: 27 additions & 0 deletions cloudflare/schema_cloudflare_teams_route.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package cloudflare

import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

func resourceCloudflareTeamsRouteSchema() map[string]*schema.Schema {
return map[string]*schema.Schema{
"account_id": {
Type: schema.TypeString,
Required: true,
},
"tunnel_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"network": {
Type: schema.TypeString,
Required: true,
},
"comment": {
Type: schema.TypeString,
Optional: true,
},
}
}
3 changes: 3 additions & 0 deletions website/cloudflare.erb
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,9 @@
<li<%= sidebar_current("docs-cloudflare-teams-location") %>>
<a href="/docs/providers/cloudflare/r/teams_location.html">cloudflare_teams_location</a>
</li>
<li<%= sidebar_current("docs-cloudflare-teams-route") %>>
<a href="/docs/providers/cloudflare/r/teams_route.html">cloudflare_teams_route</a>
</li>
<li<%= sidebar_current("docs-cloudflare-teams-rule") %>>
<a href="/docs/providers/cloudflare/r/teams_rule.html">cloudflare_teams_rule</a>
</li>
Expand Down
39 changes: 39 additions & 0 deletions website/docs/r/teams_route.html.markdown
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
---
layout: "cloudflare"
page_title: "Cloudflare: cloudflare_teams_route"
sidebar_content: "docs-cloudflare-teams-route"
description: |-
Provides a resource which manages Cloudflare Tunnel Routes for Zero Trust
---

# cloudflare_teams_route

Provides a resource, that manages Cloudflare tunnel routes for Zero Trust. Tunnel
routes are used to direct IP traffic through Cloudflare Tunnels.

## Example Usage

```hcl
resource "cloudflare_teams_route" "example"
account_id = "c4a7362d577a6c3019a474fd6f485821"
tunnel_id = "f70ff985-a4ef-4643-bbbc-4a0ed4fc8415"
network = "192.0.2.24/32"
comment = "New tunnel route for documentation"
```

## Argument Reference

The following arguments are supported:

* `account_id` - (Required) The ID of the account where the tunnel route is being created
* `tunnel_id` - (Required) The ID of the tunnel that will service the tunnel route
* `network` - (Required) The IPv4 or IPv6 network that should use this tunnel route, in CIDR notation
* `comment` - (Optional) Description of the tunnel route

## Import

An existing tunnel route can be imported using the account ID, tunnel ID, and network CIDR.

```
$ terraform import cloudflare_teams_route c4a7362d577a6c3019a474fd6f485821/f70ff985-a4ef-4643-bbbc-4a0ed4fc8415/192.0.2.24/32
```

0 comments on commit 1cc74ba

Please sign in to comment.