diff --git a/.changelog/2369.txt b/.changelog/2369.txt new file mode 100644 index 0000000000..1368b1c001 --- /dev/null +++ b/.changelog/2369.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +resource/cloudflare_tunnel: Adds config_src parameter +``` \ No newline at end of file diff --git a/docs/resources/tunnel.md b/docs/resources/tunnel.md index 19be7bdfb5..f0623cb00c 100644 --- a/docs/resources/tunnel.md +++ b/docs/resources/tunnel.md @@ -31,6 +31,10 @@ resource "cloudflare_tunnel" "example" { - `name` (String) A user-friendly name chosen when the tunnel is created. **Modifying this attribute will force creation of a new resource.** - `secret` (String, Sensitive) 32 or more bytes, encoded as a base64 string. The Create Argo Tunnel endpoint sets this as the tunnel's password. Anyone wishing to run the tunnel needs this password. **Modifying this attribute will force creation of a new resource.** +### Optional + +- `config_src` (String) Indicates if this is a locally or remotely configured tunnel. If `local`, manage the tunnel using a YAML file on the origin machine. If `cloudflare`, manage the tunnel on the Zero Trust dashboard or using tunnel_config, tunnel_route or tunnel_virtual_network resources. Available values: `local`, `cloudflare`. **Modifying this attribute will force creation of a new resource.** + ### Read-Only - `cname` (String) Usable CNAME for accessing the Tunnel. diff --git a/internal/sdkv2provider/resource_cloudflare_tunnel.go b/internal/sdkv2provider/resource_cloudflare_tunnel.go index 89cbd6bb07..ee8bb28625 100644 --- a/internal/sdkv2provider/resource_cloudflare_tunnel.go +++ b/internal/sdkv2provider/resource_cloudflare_tunnel.go @@ -38,8 +38,9 @@ func resourceCloudflareTunnelCreate(ctx context.Context, d *schema.ResourceData, accID := d.Get(consts.AccountIDSchemaKey).(string) name := d.Get("name").(string) secret := d.Get("secret").(string) + configSrc := d.Get("config_src").(string) - tunnel, err := client.CreateTunnel(ctx, cloudflare.AccountIdentifier(accID), cloudflare.TunnelCreateParams{Name: name, Secret: secret}) + tunnel, err := client.CreateTunnel(ctx, cloudflare.AccountIdentifier(accID), cloudflare.TunnelCreateParams{Name: name, Secret: secret, ConfigSrc: configSrc}) if err != nil { return diag.FromErr(errors.Wrap(err, fmt.Sprintf("failed to create Argo Tunnel"))) } @@ -69,6 +70,14 @@ func resourceCloudflareTunnelRead(ctx context.Context, d *schema.ResourceData, m d.Set("cname", fmt.Sprintf("%s.%s", tunnel.ID, argoTunnelCNAME)) d.Set("tunnel_token", token) + if d.Get("config_src").(string) != "" { + if tunnel.RemoteConfig { + d.Set("config_src", "cloudflare") + } else { + d.Set("config_src", "local") + } + } + return nil } diff --git a/internal/sdkv2provider/resource_cloudflare_tunnel_test.go b/internal/sdkv2provider/resource_cloudflare_tunnel_test.go index f923be8c7f..c6a8577623 100644 --- a/internal/sdkv2provider/resource_cloudflare_tunnel_test.go +++ b/internal/sdkv2provider/resource_cloudflare_tunnel_test.go @@ -52,6 +52,88 @@ func testAccCheckCloudflareTunnelBasic(accID, name string) string { }`, accID, name) } +func TestAccCloudflareTunnelCreate_Managed(t *testing.T) { + // Temporarily unset CLOUDFLARE_API_TOKEN if it is set as the Argo Tunnel + // endpoint does not yet support the API tokens. + if os.Getenv("CLOUDFLARE_API_TOKEN") != "" { + t.Setenv("CLOUDFLARE_API_TOKEN", "") + } + + accID := os.Getenv("CLOUDFLARE_ACCOUNT_ID") + rnd := generateRandomResourceName() + name := fmt.Sprintf("cloudflare_tunnel.%s", rnd) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + }, + ProviderFactories: providerFactories, + CheckDestroy: testAccCheckCloudflareTunnelDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckCloudflareTunnelManaged(accID, rnd), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(name, "name", rnd), + resource.TestCheckResourceAttr(name, "secret", "AQIDBAUGBwgBAgMEBQYHCAECAwQFBgcIAQIDBAUGBwg="), + resource.TestMatchResourceAttr(name, "cname", regexp.MustCompile(".*\\.cfargotunnel\\.com")), + resource.TestCheckResourceAttr(name, "config_src", "cloudflare"), + ), + }, + }, + }) +} + +func testAccCheckCloudflareTunnelManaged(accID, name string) string { + return fmt.Sprintf(` + resource "cloudflare_tunnel" "%[2]s" { + account_id = "%[1]s" + name = "%[2]s" + secret = "AQIDBAUGBwgBAgMEBQYHCAECAwQFBgcIAQIDBAUGBwg=" + config_src = "cloudflare" + }`, accID, name) +} + +func TestAccCloudflareTunnelCreate_Unmanaged(t *testing.T) { + // Temporarily unset CLOUDFLARE_API_TOKEN if it is set as the Argo Tunnel + // endpoint does not yet support the API tokens. + if os.Getenv("CLOUDFLARE_API_TOKEN") != "" { + t.Setenv("CLOUDFLARE_API_TOKEN", "") + } + + accID := os.Getenv("CLOUDFLARE_ACCOUNT_ID") + rnd := generateRandomResourceName() + name := fmt.Sprintf("cloudflare_tunnel.%s", rnd) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + }, + ProviderFactories: providerFactories, + CheckDestroy: testAccCheckCloudflareTunnelDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckCloudflareTunnelUnmanaged(accID, rnd), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(name, "name", rnd), + resource.TestCheckResourceAttr(name, "secret", "AQIDBAUGBwgBAgMEBQYHCAECAwQFBgcIAQIDBAUGBwg="), + resource.TestMatchResourceAttr(name, "cname", regexp.MustCompile(".*\\.cfargotunnel\\.com")), + resource.TestCheckResourceAttr(name, "config_src", "local"), + ), + }, + }, + }) +} + +func testAccCheckCloudflareTunnelUnmanaged(accID, name string) string { + return fmt.Sprintf(` + resource "cloudflare_tunnel" "%[2]s" { + account_id = "%[1]s" + name = "%[2]s" + secret = "AQIDBAUGBwgBAgMEBQYHCAECAwQFBgcIAQIDBAUGBwg=" + config_src = "local" + }`, accID, name) +} + func testAccCheckCloudflareTunnelDestroy(s *terraform.State) error { for _, rs := range s.RootModule().Resources { if rs.Type != "cloudflare_tunnel" { diff --git a/internal/sdkv2provider/schema_cloudflare_tunnel.go b/internal/sdkv2provider/schema_cloudflare_tunnel.go index ae643cda95..1a33f25d7c 100644 --- a/internal/sdkv2provider/schema_cloudflare_tunnel.go +++ b/internal/sdkv2provider/schema_cloudflare_tunnel.go @@ -1,8 +1,11 @@ package sdkv2provider import ( + "fmt" + "github.com/cloudflare/terraform-provider-cloudflare/internal/consts" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ) func resourceCloudflareTunnelSchema() map[string]*schema.Schema { @@ -26,6 +29,13 @@ func resourceCloudflareTunnelSchema() map[string]*schema.Schema { ForceNew: true, Description: "32 or more bytes, encoded as a base64 string. The Create Argo Tunnel endpoint sets this as the tunnel's password. Anyone wishing to run the tunnel needs this password.", }, + "config_src": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice([]string{"local", "cloudflare"}, false), + Description: fmt.Sprintf("Indicates if this is a locally or remotely configured tunnel. If `local`, manage the tunnel using a YAML file on the origin machine. If `cloudflare`, manage the tunnel on the Zero Trust dashboard or using tunnel_config, tunnel_route or tunnel_virtual_network resources. %s", renderAvailableDocumentationValuesStringSlice([]string{"local", "cloudflare"})), + }, "cname": { Type: schema.TypeString, Computed: true,