diff --git a/go.mod b/go.mod index 7b30d85503..8eef33dc69 100644 --- a/go.mod +++ b/go.mod @@ -238,3 +238,6 @@ require ( // Use custom version that supports configuration as flags and newer tfexec // until it lands upstream. replace github.com/hashicorp/terraform-plugin-docs v0.5.1 => github.com/jacobbednarz/terraform-plugin-docs v0.5.1-0.20220314024219-d04ad37d2ee8 + +// TODO: Remove when fix is released +replace github.com/cloudflare/cloudflare-go => github.com/mapped/cloudflare-go v0.40.1-0.20220603175700-567edc574e2c diff --git a/go.sum b/go.sum index 3d060cef84..8472357c3a 100644 --- a/go.sum +++ b/go.sum @@ -186,8 +186,6 @@ github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6D github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudflare/cloudflare-go v0.40.0 h1:OjW+SYY7+NVSTj+/6kORqvu33LH7uZ0hUd/0qOqucxU= -github.com/cloudflare/cloudflare-go v0.40.0/go.mod h1:MmAqiRfD8rjKEuUe4MYNHfHjYhFWfW7PNe12CCQWqPY= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= @@ -703,6 +701,8 @@ github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czP github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/mapped/cloudflare-go v0.40.1-0.20220603175700-567edc574e2c h1:v0hceaBjJoeUHe6/Vd2SsDfK+EShdmX+Y7GutLYY+zs= +github.com/mapped/cloudflare-go v0.40.1-0.20220603175700-567edc574e2c/go.mod h1:MmAqiRfD8rjKEuUe4MYNHfHjYhFWfW7PNe12CCQWqPY= github.com/maratori/testpackage v1.0.1 h1:QtJ5ZjqapShm0w5DosRjg0PRlSdAdlx+W6cCKoALdbQ= github.com/maratori/testpackage v1.0.1/go.mod h1:ddKdw+XG0Phzhx8BFDTKgpWP4i7MpApTE5fXSKAqwDU= github.com/matoous/godox v0.0.0-20210227103229-6504466cf951 h1:pWxk9e//NbPwfxat7RXkts09K+dEBJWakUWwICVqYbA= diff --git a/internal/provider/provider.go b/internal/provider/provider.go index f27aaf8a54..6dc002c404 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -183,6 +183,7 @@ func New(version string) func() *schema.Provider { "cloudflare_teams_rule": resourceCloudflareTeamsRule(), "cloudflare_teams_proxy_endpoint": resourceCloudflareTeamsProxyEndpoint(), "cloudflare_tunnel_route": resourceCloudflareTunnelRoute(), + "cloudflare_tunnel_virtual_network": resourceCloudflareTunnelVirtualNetwork(), "cloudflare_waf_group": resourceCloudflareWAFGroup(), "cloudflare_waf_override": resourceCloudflareWAFOverride(), "cloudflare_waf_package": resourceCloudflareWAFPackage(), diff --git a/internal/provider/resource_cloudflare_tunnel_virtual_network.go b/internal/provider/resource_cloudflare_tunnel_virtual_network.go new file mode 100644 index 0000000000..436c78b94c --- /dev/null +++ b/internal/provider/resource_cloudflare_tunnel_virtual_network.go @@ -0,0 +1,137 @@ +package provider + +import ( + "context" + "errors" + "fmt" + "strings" + + "github.com/cloudflare/cloudflare-go" + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func resourceCloudflareTunnelVirtualNetwork() *schema.Resource { + return &schema.Resource{ + Schema: resourceCloudflareTunnelVirtualNetworkSchema(), + CreateContext: resourceCloudflareTunnelVirtualNetworkCreate, + ReadContext: resourceCloudflareTunnelVirtualNetworkRead, + UpdateContext: resourceCloudflareTunnelVirtualNetworkUpdate, + DeleteContext: resourceCloudflareTunnelVirtualNetworkDelete, + Importer: &schema.ResourceImporter{ + StateContext: resourceCloudflareTunnelVirtualNetworkImport, + }, + } +} + +func resourceCloudflareTunnelVirtualNetworkRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*cloudflare.API) + accountID := d.Get("account_id").(string) + + tunnelVirtualNetworks, err := client.ListTunnelVirtualNetworks(ctx, cloudflare.TunnelVirtualNetworksListParams{ + AccountID: accountID, + IsDeleted: cloudflare.BoolPtr(false), + ID: d.Id(), + }) + + if err != nil { + return diag.FromErr(fmt.Errorf("failed to fetch Tunnel Virtual Network: %w", err)) + } + + if len(tunnelVirtualNetworks) < 1 { + tflog.Info(ctx, fmt.Sprintf("Tunnel Virtual Network for ID %s in account %s not found", d.Id(), accountID)) + d.SetId("") + return nil + } + + tunnelVirtualNetwork := tunnelVirtualNetworks[0] + + d.Set("name", tunnelVirtualNetwork.Name) + d.Set("is_default_network", tunnelVirtualNetwork.IsDefaultNetwork) + + if len(tunnelVirtualNetwork.Comment) > 0 { + d.Set("comment", tunnelVirtualNetwork.Comment) + } + + return nil +} + +func resourceCloudflareTunnelVirtualNetworkCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*cloudflare.API) + name := d.Get("name").(string) + + resource := cloudflare.TunnelVirtualNetworkCreateParams{ + AccountID: d.Get("account_id").(string), + Name: name, + IsDefault: d.Get("is_default_network").(bool), + } + + if comment, ok := d.Get("comment").(string); ok { + resource.Comment = comment + } + + newTunnelVirtualNetwork, err := client.CreateTunnelVirtualNetwork(ctx, resource) + if err != nil { + return diag.FromErr(fmt.Errorf("error creating Tunnel Virtual Network %q: %w", name, err)) + } + + d.SetId(newTunnelVirtualNetwork.ID) + + return resourceCloudflareTunnelVirtualNetworkRead(ctx, d, meta) +} + +func resourceCloudflareTunnelVirtualNetworkUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*cloudflare.API) + + resource := cloudflare.TunnelVirtualNetworkUpdateParams{ + AccountID: d.Get("account_id").(string), + Name: d.Get("name").(string), + IsDefaultNetwork: cloudflare.BoolPtr(d.Get("is_default_network").(bool)), + } + + if comment, ok := d.Get("comment").(string); ok { + resource.Comment = comment + } + + _, err := client.UpdateTunnelVirtualNetwork(ctx, resource) + if err != nil { + return diag.FromErr(fmt.Errorf("error updating Tunnel Virtual Network %q: %w", d.Id(), err)) + } + + return resourceCloudflareTunnelVirtualNetworkRead(ctx, d, meta) +} + +func resourceCloudflareTunnelVirtualNetworkDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*cloudflare.API) + + err := client.DeleteTunnelVirtualNetwork(ctx, cloudflare.TunnelVirtualNetworkDeleteParams{ + AccountID: d.Get("account_id").(string), + VnetID: d.Id(), + }) + if err != nil { + return diag.FromErr(fmt.Errorf("error deleting Tunnel Virtual Network %q: %w", d.Id(), err)) + } + + return nil +} + +func resourceCloudflareTunnelVirtualNetworkImport(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + attributes := strings.SplitN(d.Id(), "/", 2) + + if len(attributes) != 2 { + return nil, fmt.Errorf(`invalid id (%q) specified, should be in format "accountID/vnetID"`, d.Id()) + } + + accountID, vnetID := attributes[0], attributes[1] + + d.SetId(vnetID) + d.Set("account_id", accountID) + + err := resourceCloudflareTunnelVirtualNetworkRead(ctx, d, meta) + if err != nil { + return nil, errors.New("failed to read Tunnel Virtual Network state") + } + + return []*schema.ResourceData{d}, nil +} diff --git a/internal/provider/resource_cloudflare_tunnel_virtual_network_test.go b/internal/provider/resource_cloudflare_tunnel_virtual_network_test.go new file mode 100644 index 0000000000..866b504eb0 --- /dev/null +++ b/internal/provider/resource_cloudflare_tunnel_virtual_network_test.go @@ -0,0 +1,152 @@ +package provider + +import ( + "context" + "errors" + "fmt" + "log" + "os" + "testing" + + "github.com/cloudflare/cloudflare-go" + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +func init() { + resource.AddTestSweepers("cloudflare_tunnel_virtual_network", &resource.Sweeper{ + Name: "cloudflare_tunnel_virtual_network", + F: testSweepCloudflareTunnelVirtualNetwork, + }) +} + +func testSweepCloudflareTunnelVirtualNetwork(r string) error { + ctx := context.Background() + client, clientErr := sharedClient() + if clientErr != nil { + tflog.Error(ctx, fmt.Sprintf("Failed to create Cloudflare client: %s", clientErr)) + } + + accountID := os.Getenv("CLOUDFLARE_ACCOUNT_ID") + if accountID == "" { + return errors.New("CLOUDFLARE_ACCOUNT_ID must be set") + } + + tunnelVirtualNetworks, err := client.ListTunnelVirtualNetworks(context.Background(), cloudflare.TunnelVirtualNetworksListParams{AccountID: accountID}) + if err != nil { + tflog.Error(ctx, fmt.Sprintf("Failed to fetch Cloudflare Tunnel Virtual Networks: %s", err)) + } + + if len(tunnelVirtualNetworks) == 0 { + log.Print("[DEBUG] No Cloudflare Tunnel Virtual Networks to sweep") + return nil + } + + for _, vnet := range tunnelVirtualNetworks { + tflog.Info(ctx, fmt.Sprintf("Deleting Cloudflare Tunnel Virtual Network %s", vnet.ID)) + //nolint:errcheck + client.DeleteTunnelVirtualNetwork(context.Background(), cloudflare.TunnelVirtualNetworkDeleteParams{AccountID: accountID, VnetID: vnet.ID}) + } + + return nil +} + +func TestAccCloudflareTunnelVirtualNetwork_Exists(t *testing.T) { + rnd := generateRandomResourceName() + name := fmt.Sprintf("cloudflare_tunnel_virtual_network.%s", rnd) + accountID := os.Getenv("CLOUDFLARE_ACCOUNT_ID") + + var TunnelVirtualNetwork cloudflare.TunnelVirtualNetwork + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccPreCheckAccount(t) + }, + ProviderFactories: providerFactories, + Steps: []resource.TestStep{ + { + Config: testAccCloudflareTunnelVirtualNetworkSimple(rnd, rnd, accountID, rnd, false), + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudflareTunnelVirtualNetworkExists(name, &TunnelVirtualNetwork), + resource.TestCheckResourceAttr(name, "account_id", accountID), + resource.TestCheckResourceAttr(name, "name", rnd), + resource.TestCheckResourceAttr(name, "comment", rnd), + resource.TestCheckResourceAttr(name, "is_default_network", "false"), + ), + }, + }, + }) +} + +func testAccCheckCloudflareTunnelVirtualNetworkExists(name string, virtualNetwork *cloudflare.TunnelVirtualNetwork) 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 Tunnel Virtual Network is set") + } + + client := testAccProvider.Meta().(*cloudflare.API) + foundTunnelVirtualNetworks, err := client.ListTunnelVirtualNetworks(context.Background(), cloudflare.TunnelVirtualNetworksListParams{ + AccountID: rs.Primary.Attributes["account_id"], + IsDeleted: cloudflare.BoolPtr(false), + ID: rs.Primary.ID, + }) + + if err != nil { + return err + } + + *virtualNetwork = foundTunnelVirtualNetworks[0] + + return nil + } +} + +func TestAccCloudflareTunnelVirtualNetwork_UpdateComment(t *testing.T) { + rnd := generateRandomResourceName() + name := fmt.Sprintf("cloudflare_tunnel_virtual_network.%s", rnd) + accountID := os.Getenv("CLOUDFLARE_ACCOUNT_ID") + + var TunnelVirtualNetwork cloudflare.TunnelVirtualNetwork + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccPreCheckAccount(t) + }, + ProviderFactories: providerFactories, + Steps: []resource.TestStep{ + { + Config: testAccCloudflareTunnelVirtualNetworkSimple(rnd, rnd, accountID, rnd, false), + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudflareTunnelVirtualNetworkExists(name, &TunnelVirtualNetwork), + resource.TestCheckResourceAttr(name, "comment", rnd), + ), + }, + { + Config: testAccCloudflareTunnelVirtualNetworkSimple(rnd, rnd+"-updated", accountID, rnd, false), + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudflareTunnelVirtualNetworkExists(name, &TunnelVirtualNetwork), + resource.TestCheckResourceAttr(name, "comment", rnd+"-updated"), + ), + }, + }, + }) +} + +func testAccCloudflareTunnelVirtualNetworkSimple(ID, comment, accountID, name string, isDefault bool) string { + return fmt.Sprintf(` +resource "cloudflare_tunnel_virtual_network" "%[1]s" { + account_id = "%[3]s" + name = "%[4]s" + comment = "%[2]s" + is_default_network = "%[5]t" +}`, ID, comment, accountID, name, isDefault) +} diff --git a/internal/provider/schema_cloudflare_tunnel_virtual_network.go b/internal/provider/schema_cloudflare_tunnel_virtual_network.go new file mode 100644 index 0000000000..215d5b09db --- /dev/null +++ b/internal/provider/schema_cloudflare_tunnel_virtual_network.go @@ -0,0 +1,27 @@ +package provider + +import ( + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func resourceCloudflareTunnelVirtualNetworkSchema() map[string]*schema.Schema { + return map[string]*schema.Schema{ + "account_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "name": { + Type: schema.TypeString, + Required: true, + }, + "is_default_network": { + Type: schema.TypeBool, + Optional: true, + }, + "comment": { + Type: schema.TypeString, + Optional: true, + }, + } +} diff --git a/website/cloudflare.erb b/website/cloudflare.erb index cd5e793983..ed1fbd854e 100644 --- a/website/cloudflare.erb +++ b/website/cloudflare.erb @@ -199,6 +199,9 @@