From 2ea6a343d2317661c27bf02a74944a9b983db5d1 Mon Sep 17 00:00:00 2001 From: njucz Date: Fri, 24 Jul 2020 17:57:42 +0800 Subject: [PATCH 1/6] new resource for "azurerm_synapse_firewall_rule" --- .../services/synapse/client/client.go | 5 + .../synapse/parse/synapse_firewall_rule.go | 37 ++++ .../parse/synapse_firewall_rule_test.go | 79 +++++++++ .../synapse/parse/synapse_workspace.go | 9 +- .../internal/services/synapse/registration.go | 3 +- .../synapse/synapse_firewall_rule_resource.go | 166 ++++++++++++++++++ .../synapse/validate/synapse_workspace.go | 17 ++ 7 files changed, 313 insertions(+), 3 deletions(-) create mode 100644 azurerm/internal/services/synapse/parse/synapse_firewall_rule.go create mode 100644 azurerm/internal/services/synapse/parse/synapse_firewall_rule_test.go create mode 100644 azurerm/internal/services/synapse/synapse_firewall_rule_resource.go diff --git a/azurerm/internal/services/synapse/client/client.go b/azurerm/internal/services/synapse/client/client.go index 508d5f66ee8a..47cfa61c571b 100644 --- a/azurerm/internal/services/synapse/client/client.go +++ b/azurerm/internal/services/synapse/client/client.go @@ -6,11 +6,15 @@ import ( ) type Client struct { + FirewallRulesClient *synapse.IPFirewallRulesClient WorkspaceClient *synapse.WorkspacesClient WorkspaceAadAdminsClient *synapse.WorkspaceAadAdminsClient } func NewClient(o *common.ClientOptions) *Client { + firewallRuleClient := synapse.NewIPFirewallRulesClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) + o.ConfigureClient(&firewallRuleClient.Client, o.ResourceManagerAuthorizer) + workspaceClient := synapse.NewWorkspacesClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) o.ConfigureClient(&workspaceClient.Client, o.ResourceManagerAuthorizer) @@ -18,6 +22,7 @@ func NewClient(o *common.ClientOptions) *Client { o.ConfigureClient(&workspaceAadAdminsClient.Client, o.ResourceManagerAuthorizer) return &Client{ + FirewallRulesClient: &firewallRuleClient, WorkspaceClient: &workspaceClient, WorkspaceAadAdminsClient: &workspaceAadAdminsClient, } diff --git a/azurerm/internal/services/synapse/parse/synapse_firewall_rule.go b/azurerm/internal/services/synapse/parse/synapse_firewall_rule.go new file mode 100644 index 000000000000..4efecaa3bafc --- /dev/null +++ b/azurerm/internal/services/synapse/parse/synapse_firewall_rule.go @@ -0,0 +1,37 @@ +package parse + +import ( + "fmt" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" +) + +type SynapseFirewallRuleId struct { + Workspace *SynapseWorkspaceId + Name string +} + +func SynapseFirewallRuleID(input string) (*SynapseFirewallRuleId, error) { + id, err := azure.ParseAzureResourceID(input) + if err != nil { + return nil, fmt.Errorf("parsing synapseWorkspace ID %q: %+v", input, err) + } + + FirewallRuleId := SynapseFirewallRuleId{ + Workspace: &SynapseWorkspaceId{ + SubscriptionID: id.SubscriptionID, + ResourceGroup: id.ResourceGroup, + }, + } + if FirewallRuleId.Workspace.Name, err = id.PopSegment("workspaces"); err != nil { + return nil, err + } + if FirewallRuleId.Name, err = id.PopSegment("firewallRules"); err != nil { + return nil, err + } + if err := id.ValidateNoEmptySegments(input); err != nil { + return nil, err + } + + return &FirewallRuleId, nil +} diff --git a/azurerm/internal/services/synapse/parse/synapse_firewall_rule_test.go b/azurerm/internal/services/synapse/parse/synapse_firewall_rule_test.go new file mode 100644 index 000000000000..f727194a2cc6 --- /dev/null +++ b/azurerm/internal/services/synapse/parse/synapse_firewall_rule_test.go @@ -0,0 +1,79 @@ +package parse + +import ( + "testing" +) + +func TestSynapseFirewallRuleID(t *testing.T) { + testData := []struct { + Name string + Input string + Expected *SynapseFirewallRuleId + }{ + { + Name: "Empty", + Input: "", + Expected: nil, + }, + { + Name: "No Resource Groups Segment", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000", + Expected: nil, + }, + { + Name: "No Resource Groups Value", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/", + Expected: nil, + }, + { + Name: "Resource Group ID", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/foo/", + Expected: nil, + }, + { + Name: "Missing Firewall Rule Value", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resourceGroup1/providers/Microsoft.Synapse/workspaces/workspace1/firewallRules", + Expected: nil, + }, + { + Name: "synapse Firewall Rule ID", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resourceGroup1/providers/Microsoft.Synapse/workspaces/workspace1/firewallRules/rule1", + Expected: &SynapseFirewallRuleId{ + Workspace: &SynapseWorkspaceId{ + ResourceGroup: "resourceGroup1", + Name: "workspace1", + }, + Name: "rule1", + }, + }, + { + Name: "Wrong Casing", + Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resourceGroup1/providers/Microsoft.Synapse/workspaces/workspace1/FirewallRules/rule1", + Expected: nil, + }, + } + + for _, v := range testData { + t.Logf("[DEBUG] Testing %q..", v.Name) + + actual, err := SynapseFirewallRuleID(v.Input) + if err != nil { + if v.Expected == nil { + continue + } + t.Fatalf("Expected a value but got an error: %s", err) + } + + if actual.Workspace.ResourceGroup != v.Expected.Workspace.ResourceGroup { + t.Fatalf("Expected %q but got %q for ResourceGroup", v.Expected.Workspace.ResourceGroup, actual.Workspace.ResourceGroup) + } + + if actual.Workspace.Name != v.Expected.Workspace.Name { + t.Fatalf("Expected %q but got %q for WorkspaceName", v.Expected.Workspace.Name, actual.Workspace.Name) + } + + if actual.Name != v.Expected.Name { + t.Fatalf("Expected %q but got %q for Name", v.Expected.Name, actual.Name) + } + } +} diff --git a/azurerm/internal/services/synapse/parse/synapse_workspace.go b/azurerm/internal/services/synapse/parse/synapse_workspace.go index 5a80a59fa4c0..27b71b8b33db 100644 --- a/azurerm/internal/services/synapse/parse/synapse_workspace.go +++ b/azurerm/internal/services/synapse/parse/synapse_workspace.go @@ -7,8 +7,9 @@ import ( ) type SynapseWorkspaceId struct { - ResourceGroup string - Name string + SubscriptionID string + ResourceGroup string + Name string } func SynapseWorkspaceID(input string) (*SynapseWorkspaceId, error) { @@ -29,3 +30,7 @@ func SynapseWorkspaceID(input string) (*SynapseWorkspaceId, error) { return &synapseWorkspace, nil } + +func (id *SynapseWorkspaceId) String() string { + return fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Synapse/workspaces/%s", id.SubscriptionID, id.ResourceGroup, id.Name) +} diff --git a/azurerm/internal/services/synapse/registration.go b/azurerm/internal/services/synapse/registration.go index 25010959a758..7818c77145cc 100644 --- a/azurerm/internal/services/synapse/registration.go +++ b/azurerm/internal/services/synapse/registration.go @@ -26,6 +26,7 @@ func (r Registration) SupportedDataSources() map[string]*schema.Resource { // SupportedResources returns the supported Resources supported by this Service func (r Registration) SupportedResources() map[string]*schema.Resource { return map[string]*schema.Resource{ - "azurerm_synapse_workspace": resourceArmSynapseWorkspace(), + "azurerm_synapse_firewall_rule": resourceArmSynapseFirewallRule(), + "azurerm_synapse_workspace": resourceArmSynapseWorkspace(), } } diff --git a/azurerm/internal/services/synapse/synapse_firewall_rule_resource.go b/azurerm/internal/services/synapse/synapse_firewall_rule_resource.go new file mode 100644 index 000000000000..be11766fc8fe --- /dev/null +++ b/azurerm/internal/services/synapse/synapse_firewall_rule_resource.go @@ -0,0 +1,166 @@ +package synapse + +import ( + "fmt" + "log" + "time" + + "github.com/Azure/azure-sdk-for-go/services/preview/synapse/mgmt/2019-06-01-preview/synapse" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/synapse/parse" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/synapse/validate" + azSchema "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/schema" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func resourceArmSynapseFirewallRule() *schema.Resource { + return &schema.Resource{ + Create: resourceArmSynapseFirewallRuleCreateUpdate, + Read: resourceArmSynapseFirewallRuleRead, + Update: resourceArmSynapseFirewallRuleCreateUpdate, + Delete: resourceArmSynapseFirewallRuleDelete, + + Importer: azSchema.ValidateResourceIDPriorToImport(func(id string) error { + _, err := parse.SynapseFirewallRuleID(id) + return err + }), + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(30 * time.Minute), + Read: schema.DefaultTimeout(5 * time.Minute), + Update: schema.DefaultTimeout(30 * time.Minute), + Delete: schema.DefaultTimeout(30 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "synapse_workspace_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.SynapseWorkspaceID, + }, + + "start_ip_address": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.IsIPv4Address, + }, + + "end_ip_address": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.IsIPv4Address, + }, + }, + } +} + +func resourceArmSynapseFirewallRuleCreateUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Synapse.FirewallRulesClient + ctx, cancel := timeouts.ForCreateUpdate(meta.(*clients.Client).StopContext, d) + defer cancel() + + name := d.Get("name").(string) + workspaceId, _ := parse.SynapseWorkspaceID(d.Get("synapse_workspace_id").(string)) + + if d.IsNewResource() { + existing, err := client.Get(ctx, workspaceId.ResourceGroup, workspaceId.Name, name) + if err != nil { + if !utils.ResponseWasNotFound(existing.Response) { + return fmt.Errorf("checking for presence of existing Synapse Firewall Rule %q (Resource Group %q / Workspace %q): %+v", name, workspaceId.ResourceGroup, workspaceId.Name, err) + } + } + + if existing.ID != nil && *existing.ID != "" { + return tf.ImportAsExistsError("azurerm_synapse_firewall_rule", *existing.ID) + } + } + + parameters := synapse.IPFirewallRuleInfo{ + IPFirewallRuleProperties: &synapse.IPFirewallRuleProperties{ + StartIPAddress: utils.String(d.Get("start_ip_address").(string)), + EndIPAddress: utils.String(d.Get("end_ip_address").(string)), + }, + } + + future, err := client.CreateOrUpdate(ctx, workspaceId.ResourceGroup, workspaceId.Name, name, parameters) + if err != nil { + return fmt.Errorf("creating/updating Synapse Firewall Rule %q (Resource Group %q / Workspace %q): %+v", name, workspaceId.ResourceGroup, workspaceId.Name, err) + } + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("waiting on creation/updation for Synapse Firewall Rule %q (Resource Group %q / Workspace %q): %+v", name, workspaceId.ResourceGroup, workspaceId.Name, err) + } + + resp, err := client.Get(ctx, workspaceId.ResourceGroup, workspaceId.Name, name) + if err != nil { + return fmt.Errorf("retrieving Synapse Firewall Rule %q (Resource Group %q / Workspace %q): %+v", name, workspaceId.ResourceGroup, workspaceId.Name, err) + } + + d.SetId(*resp.ID) + + return resourceArmSynapseFirewallRuleRead(d, meta) +} + +func resourceArmSynapseFirewallRuleRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Synapse.FirewallRulesClient + ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := parse.SynapseFirewallRuleID(d.Id()) + if err != nil { + return err + } + + resp, err := client.Get(ctx, id.Workspace.ResourceGroup, id.Workspace.Name, id.Name) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + log.Printf("[INFO] Error reading Synapse Firewall Rule %q - removing from state", d.Id()) + d.SetId("") + return nil + } + + return fmt.Errorf("reading Synapse Firewall Rule %q (Resource Group %q / Workspace %q): %+v", id.Name, id.Workspace.ResourceGroup, id.Workspace.Name, err) + } + + d.Set("name", id.Name) + d.Set("synapse_workspace_id", id.Workspace.String()) + if resp.IPFirewallRuleProperties != nil { + d.Set("start_ip_address", resp.IPFirewallRuleProperties.StartIPAddress) + d.Set("end_ip_address", resp.IPFirewallRuleProperties.EndIPAddress) + } + + return nil +} + +func resourceArmSynapseFirewallRuleDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Synapse.FirewallRulesClient + ctx, cancel := timeouts.ForDelete(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := parse.SynapseFirewallRuleID(d.Id()) + if err != nil { + return err + } + + future, err := client.Delete(ctx, id.Workspace.ResourceGroup, id.Workspace.Name, id.Name) + if err != nil { + return fmt.Errorf("deleting Synapse Firewall Rule %q (Resource Group %q / Workspace %q): %+v", id.Name, id.Workspace.ResourceGroup, id.Workspace.Name, err) + } + + // sometimes the waitForCompletion rest api will return 404 + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("waiting for deleting Synapse Firewall Rule %q (Resource Group %q / Workspace %q): %+v", id.Name, id.Workspace.ResourceGroup, id.Workspace.Name, err) + } + + return nil +} diff --git a/azurerm/internal/services/synapse/validate/synapse_workspace.go b/azurerm/internal/services/synapse/validate/synapse_workspace.go index 9f510117d4cc..0513316ef47a 100644 --- a/azurerm/internal/services/synapse/validate/synapse_workspace.go +++ b/azurerm/internal/services/synapse/validate/synapse_workspace.go @@ -4,6 +4,8 @@ import ( "fmt" "regexp" "strings" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/synapse/parse" ) func SynapseWorkspaceName(i interface{}, k string) (warnings []string, errors []error) { @@ -49,3 +51,18 @@ func SqlAdministratorLoginName(i interface{}, k string) (warnings []string, erro return warnings, errors } + +func SynapseWorkspaceID(i interface{}, k string) (warnings []string, errors []error) { + v, ok := i.(string) + if !ok { + errors = append(errors, fmt.Errorf("expected type of %q to be string", k)) + return + } + + if _, err := parse.SynapseWorkspaceID(v); err != nil { + errors = append(errors, fmt.Errorf("can not parse %q as a synapse workspace resource id: %v", k, err)) + return + } + + return warnings, errors +} From 931f90ca0db541826beeb5e50536f7fbed377b4c Mon Sep 17 00:00:00 2001 From: njucz <740360112@qq.com> Date: Mon, 27 Jul 2020 11:18:18 +0800 Subject: [PATCH 2/6] add test and doc --- .../synapse_firewall_rule_resource_test.go | 197 ++++++++++++++++++ .../synapse/validate/synapse_firewall_rule.go | 26 +++ .../validate/synapse_firewall_rule_test.go | 58 ++++++ .../r/synapse_firewall_rule.html.markdown | 87 ++++++++ 4 files changed, 368 insertions(+) create mode 100644 azurerm/internal/services/synapse/tests/synapse_firewall_rule_resource_test.go create mode 100644 azurerm/internal/services/synapse/validate/synapse_firewall_rule.go create mode 100644 azurerm/internal/services/synapse/validate/synapse_firewall_rule_test.go create mode 100644 website/docs/r/synapse_firewall_rule.html.markdown diff --git a/azurerm/internal/services/synapse/tests/synapse_firewall_rule_resource_test.go b/azurerm/internal/services/synapse/tests/synapse_firewall_rule_resource_test.go new file mode 100644 index 000000000000..ffbd51fd88a7 --- /dev/null +++ b/azurerm/internal/services/synapse/tests/synapse_firewall_rule_resource_test.go @@ -0,0 +1,197 @@ +package tests + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/acceptance" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/synapse/parse" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func TestAccAzureRMSynapseFirewallRule_basic(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_synapse_firewall_rule", "test") + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMSynapseFirewallRuleDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMSynapseFirewallRule_basic(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMSynapseFirewallRuleExists(data.ResourceName), + ), + }, + data.ImportStep(), + }, + }) +} + +func TestAccAzureRMSynapseFirewallRule_requiresImport(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_synapse_firewall_rule", "test") + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMSynapseFirewallRuleDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMSynapseFirewallRule_basic(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMSynapseFirewallRuleExists(data.ResourceName), + ), + }, + data.RequiresImportErrorStep(testAccAzureRMSynapseFirewallRule_requiresImport), + }, + }) +} + +func TestAccAzureRMSynapseFirewallRule_update(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_synapse_firewall_rule", "test") + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMSynapseFirewallRuleDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMSynapseFirewallRule_basic(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMSynapseFirewallRuleExists(data.ResourceName), + ), + }, + data.ImportStep(), + { + Config: testAccAzureRMSynapseFirewallRule_withUpdates(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMSynapseFirewallRuleExists(data.ResourceName), + ), + }, + data.ImportStep(), + }, + }) +} + +func testCheckAzureRMSynapseFirewallRuleExists(resourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + client := acceptance.AzureProvider.Meta().(*clients.Client).Synapse.FirewallRulesClient + ctx := acceptance.AzureProvider.Meta().(*clients.Client).StopContext + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("synapse Firewall Rule not found: %s", resourceName) + } + id, err := parse.SynapseFirewallRuleID(rs.Primary.ID) + if err != nil { + return err + } + if resp, err := client.Get(ctx, id.Workspace.ResourceGroup, id.Workspace.Name, id.Name); err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("bad: Synapse Firewall Rule %q does not exist", id.Name) + } + return fmt.Errorf("bad: Get on Synapse.FirewallRulesClient: %+v", err) + } + return nil + } +} + +func testCheckAzureRMSynapseFirewallRuleDestroy(s *terraform.State) error { + client := acceptance.AzureProvider.Meta().(*clients.Client).Synapse.FirewallRulesClient + ctx := acceptance.AzureProvider.Meta().(*clients.Client).StopContext + + for _, rs := range s.RootModule().Resources { + if rs.Type != "azurerm_synapse_firewall_rule" { + continue + } + id, err := parse.SynapseFirewallRuleID(rs.Primary.ID) + if err != nil { + return err + } + if resp, err := client.Get(ctx, id.Workspace.ResourceGroup, id.Workspace.Name, id.Name); err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("bad: Get on Synapse.FirewallRulesClient: %+v", err) + } + return nil + } + return fmt.Errorf("bad: Get on Synapse.FirewallRulesClient: %+v", err) + } + return nil +} + +func testAccAzureRMSynapseFirewallRule_basic(data acceptance.TestData) string { + template := testAccAzureRMSynapseFirewallRule_template(data) + return fmt.Sprintf(` +%s + +resource "azurerm_synapse_firewall_rule" "test" { + name = "FirewallRule%d" + synapse_workspace_id = azurerm_synapse_workspace.test.id + start_ip_address = "0.0.0.0" + end_ip_address = "255.255.255.255" +} +`, template, data.RandomInteger) +} + +func testAccAzureRMSynapseFirewallRule_requiresImport(data acceptance.TestData) string { + config := testAccAzureRMSynapseFirewallRule_basic(data) + return fmt.Sprintf(` +%s + +resource "azurerm_synapse_firewall_rule" "import" { + name = azurerm_synapse_firewall_rule.test.name + synapse_workspace_id = azurerm_synapse_firewall_rule.test.synapse_workspace_id + start_ip_address = azurerm_synapse_firewall_rule.test.start_ip_address + end_ip_address = azurerm_synapse_firewall_rule.test.end_ip_address +} +`, config) +} + +func testAccAzureRMSynapseFirewallRule_withUpdates(data acceptance.TestData) string { + template := testAccAzureRMSynapseFirewallRule_template(data) + return fmt.Sprintf(` +%s + +resource "azurerm_synapse_firewall_rule" "test" { + name = "FirewallRule%d" + synapse_workspace_id = azurerm_synapse_workspace.test.id + start_ip_address = "10.0.17.62" + end_ip_address = "10.0.17.62" +} +`, template, data.RandomInteger) +} + +func testAccAzureRMSynapseFirewallRule_template(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctest-Synapse-%d" + location = "%s" +} + +resource "azurerm_storage_account" "test" { + name = "acctestacc%s" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + account_kind = "BlobStorage" + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_storage_data_lake_gen2_filesystem" "test" { + name = "acctest-%d" + storage_account_id = azurerm_storage_account.test.id +} + +resource "azurerm_synapse_workspace" "test" { + name = "acctestsw%d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + storage_data_lake_gen2_filesystem_id = azurerm_storage_data_lake_gen2_filesystem.test.id + sql_administrator_login = "sqladminuser" + sql_administrator_login_password = "H@Sh1CoR3!" +} +`, data.RandomInteger, data.Locations.Primary, data.RandomString, data.RandomInteger, data.RandomInteger) +} diff --git a/azurerm/internal/services/synapse/validate/synapse_firewall_rule.go b/azurerm/internal/services/synapse/validate/synapse_firewall_rule.go new file mode 100644 index 000000000000..c5daad098639 --- /dev/null +++ b/azurerm/internal/services/synapse/validate/synapse_firewall_rule.go @@ -0,0 +1,26 @@ +package validate + +import ( + "fmt" + "regexp" +) + +func SynapseFirewallRuleName(i interface{}, k string) (warnings []string, errors []error) { + v, ok := i.(string) + if !ok { + errors = append(errors, fmt.Errorf("expected type of %s to be string", k)) + return + } + + // The name attribute rules are : + // 1. can contain only letters, numbers, underscore and hythen. + // 2. must not end with the string 'ondemand' + // 3. The value must be between 1 and 128 characters long + + if !regexp.MustCompile(`^[a-zA-Z\d-_]{1,128}$`).MatchString(v) { + errors = append(errors, fmt.Errorf("%s can contain only letters, numbers, underscore and hythen, and be between 1 and 128 characters long", k)) + return + } + + return warnings, errors +} diff --git a/azurerm/internal/services/synapse/validate/synapse_firewall_rule_test.go b/azurerm/internal/services/synapse/validate/synapse_firewall_rule_test.go new file mode 100644 index 000000000000..95d67e7c3fe9 --- /dev/null +++ b/azurerm/internal/services/synapse/validate/synapse_firewall_rule_test.go @@ -0,0 +1,58 @@ +package validate + +import ( + "testing" +) + +func TestSynapseFirewallRuleName(t *testing.T) { + testData := []struct { + input string + expected bool + }{ + { + // empty + input: "", + expected: false, + }, + { + // basic example + input: "abc123", + expected: true, + }, + { + // can contain underscore + input: "aBc_123", + expected: true, + }, + { + // can contain hyphen + input: "ab-c", + expected: true, + }, + { + // can't contain `*` + input: "abcon*demand", + expected: false, + }, + { + // 128 chars + input: "abcdefghijklmnopqrstuvwxyzabcdefabcdefghijklmnopqrstuvwxyzabcdefabcdefghijklmnopqrstuvwxyzabcdefabcdefghijklmnopqrstuvwxyzabcdef", + expected: true, + }, + { + // 129 chars + input: "abcdefghijklmnopqrstuvwxyzabcdefabcdefghijklmnopqrstuvwxyzabcdefabcdefghijklmnopqrstuvwxyzabcdefabcdefghijklmnopqrstuvwxyzabcdefg", + expected: false, + }, + } + + for _, v := range testData { + t.Logf("[DEBUG] Testing %q..", v.input) + + _, errors := SynapseFirewallRuleName(v.input, "name") + actual := len(errors) == 0 + if v.expected != actual { + t.Fatalf("Expected %t but got %t", v.expected, actual) + } + } +} diff --git a/website/docs/r/synapse_firewall_rule.html.markdown b/website/docs/r/synapse_firewall_rule.html.markdown new file mode 100644 index 000000000000..b9af17ace6af --- /dev/null +++ b/website/docs/r/synapse_firewall_rule.html.markdown @@ -0,0 +1,87 @@ +--- +subcategory: "Synapse" +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_synapse_firewall_rule" +description: |- + Manages a Synapse Firewall Rule. +--- + +# azurerm_synapse_firewall_rule + +Allows you to Manages a Synapse Firewall Rule. + +## Example Usage + +```hcl +resource "azurerm_resource_group" "example" { + name = "example-resources" + location = "West Europe" +} + +resource "azurerm_storage_account" "example" { + name = "examplestorageacc" + resource_group_name = azurerm_resource_group.example.name + location = azurerm_resource_group.example.location + account_tier = "Standard" + account_replication_type = "LRS" + account_kind = "StorageV2" + is_hns_enabled = "true" +} + +resource "azurerm_storage_data_lake_gen2_filesystem" "example" { + name = "example" + storage_account_id = azurerm_storage_account.example.id +} + +resource "azurerm_synapse_workspace" "example" { + name = "example" + resource_group_name = azurerm_resource_group.example.name + location = azurerm_resource_group.example.location + storage_data_lake_gen2_filesystem_id = azurerm_storage_data_lake_gen2_filesystem.example.id + sql_administrator_login = "sqladminuser" + sql_administrator_login_password = "H@Sh1CoR3!" +} + +resource "azurerm_synapse_firewall_rule" "example" { + name = "AllowAll" + synapse_workspace_id = azurerm_synapse_workspace.test.id + start_ip_address = "0.0.0.0" + end_ip_address = "255.255.255.255" +} +``` +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name of the firewall rule. + +* `synapse_workspace_id` - (Required) The ID of the Synapse Workspace on which to create the Firewall Rule. + +* `start_ip_address` - (Required) The starting IP address to allow through the firewall for this rule. + +* `end_ip_address` - (Required) The ending IP address to allow through the firewall for this rule. + +-> **NOTE:** The Azure feature `Allow access to Azure services` can be enabled by setting `start_ip_address` and `end_ip_address` to `0.0.0.0`. + +## Attributes Reference + +The following attributes are exported: + +* `id` - The Synapse Firewall Rule ID. + +## Timeouts + +The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/docs/configuration/resources.html#timeouts) for certain actions: + +* `create` - (Defaults to 30 minutes) Used when creating the Synapse Firewall Rule. +* `update` - (Defaults to 30 minutes) Used when updating the Synapse Firewall Rule. +* `read` - (Defaults to 5 minutes) Used when retrieving the Synapse Firewall Rule. +* `delete` - (Defaults to 30 minutes) Used when deleting the Synapse Firewall Rule. + +## Import + +Synapse Firewall Rules can be imported using the `resource id`, e.g. + +```shell +terraform import azurerm_synapse_firewall_rule.rule1 /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resourcegroup1/providers/Microsoft.Synapse/workspaces/workspace1/firewallRules/rule1 +``` From 343ed206e3f70324c38c10a2291fc22edbc6caa3 Mon Sep 17 00:00:00 2001 From: njucz <740360112@qq.com> Date: Mon, 27 Jul 2020 11:39:33 +0800 Subject: [PATCH 3/6] update --- .../services/synapse/synapse_firewall_rule_resource.go | 8 ++++---- .../services/synapse/validate/synapse_firewall_rule.go | 3 +-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/azurerm/internal/services/synapse/synapse_firewall_rule_resource.go b/azurerm/internal/services/synapse/synapse_firewall_rule_resource.go index be11766fc8fe..1431031f3a5e 100644 --- a/azurerm/internal/services/synapse/synapse_firewall_rule_resource.go +++ b/azurerm/internal/services/synapse/synapse_firewall_rule_resource.go @@ -38,9 +38,10 @@ func resourceArmSynapseFirewallRule() *schema.Resource { Schema: map[string]*schema.Schema{ "name": { - Type: schema.TypeString, - Required: true, - ForceNew: true, + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.SynapseFirewallRuleName, }, "synapse_workspace_id": { @@ -157,7 +158,6 @@ func resourceArmSynapseFirewallRuleDelete(d *schema.ResourceData, meta interface return fmt.Errorf("deleting Synapse Firewall Rule %q (Resource Group %q / Workspace %q): %+v", id.Name, id.Workspace.ResourceGroup, id.Workspace.Name, err) } - // sometimes the waitForCompletion rest api will return 404 if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { return fmt.Errorf("waiting for deleting Synapse Firewall Rule %q (Resource Group %q / Workspace %q): %+v", id.Name, id.Workspace.ResourceGroup, id.Workspace.Name, err) } diff --git a/azurerm/internal/services/synapse/validate/synapse_firewall_rule.go b/azurerm/internal/services/synapse/validate/synapse_firewall_rule.go index c5daad098639..7baebb60d5bc 100644 --- a/azurerm/internal/services/synapse/validate/synapse_firewall_rule.go +++ b/azurerm/internal/services/synapse/validate/synapse_firewall_rule.go @@ -14,8 +14,7 @@ func SynapseFirewallRuleName(i interface{}, k string) (warnings []string, errors // The name attribute rules are : // 1. can contain only letters, numbers, underscore and hythen. - // 2. must not end with the string 'ondemand' - // 3. The value must be between 1 and 128 characters long + // 2. The value must be between 1 and 128 characters long if !regexp.MustCompile(`^[a-zA-Z\d-_]{1,128}$`).MatchString(v) { errors = append(errors, fmt.Errorf("%s can contain only letters, numbers, underscore and hythen, and be between 1 and 128 characters long", k)) From 172b727339267905ca5a6a67e52af00b078a7972 Mon Sep 17 00:00:00 2001 From: njucz <740360112@qq.com> Date: Mon, 27 Jul 2020 13:15:58 +0800 Subject: [PATCH 4/6] add doc lint --- website/azurerm.erb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/website/azurerm.erb b/website/azurerm.erb index b9420f547fed..52024da0fa5a 100644 --- a/website/azurerm.erb +++ b/website/azurerm.erb @@ -2676,6 +2676,10 @@
  • azurerm_synapse_workspace
  • + +
  • + azurerm_synapse_firewall_rule +
  • From d522d33d0eee567e986bcb26e07d8eb7f34941dc Mon Sep 17 00:00:00 2001 From: njucz <740360112@qq.com> Date: Mon, 27 Jul 2020 14:37:14 +0800 Subject: [PATCH 5/6] update --- .../synapse/tests/synapse_firewall_rule_resource_test.go | 5 +++-- website/docs/r/synapse_firewall_rule.html.markdown | 8 ++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/azurerm/internal/services/synapse/tests/synapse_firewall_rule_resource_test.go b/azurerm/internal/services/synapse/tests/synapse_firewall_rule_resource_test.go index ffbd51fd88a7..bcee3cdf1e2f 100644 --- a/azurerm/internal/services/synapse/tests/synapse_firewall_rule_resource_test.go +++ b/azurerm/internal/services/synapse/tests/synapse_firewall_rule_resource_test.go @@ -107,13 +107,14 @@ func testCheckAzureRMSynapseFirewallRuleDestroy(s *terraform.State) error { if err != nil { return err } - if resp, err := client.Get(ctx, id.Workspace.ResourceGroup, id.Workspace.Name, id.Name); err != nil { + resp, err := client.Get(ctx, id.Workspace.ResourceGroup, id.Workspace.Name, id.Name) + if err != nil { if !utils.ResponseWasNotFound(resp.Response) { return fmt.Errorf("bad: Get on Synapse.FirewallRulesClient: %+v", err) } return nil } - return fmt.Errorf("bad: Get on Synapse.FirewallRulesClient: %+v", err) + return fmt.Errorf("expected no Firewall Rule but found %+v", resp) } return nil } diff --git a/website/docs/r/synapse_firewall_rule.html.markdown b/website/docs/r/synapse_firewall_rule.html.markdown index b9af17ace6af..779308e55b57 100644 --- a/website/docs/r/synapse_firewall_rule.html.markdown +++ b/website/docs/r/synapse_firewall_rule.html.markdown @@ -53,9 +53,9 @@ resource "azurerm_synapse_firewall_rule" "example" { The following arguments are supported: -* `name` - (Required) The name of the firewall rule. +* `name` - (Required) The Name of the firewall rule. Changing this forces a new resource to be created. -* `synapse_workspace_id` - (Required) The ID of the Synapse Workspace on which to create the Firewall Rule. +* `synapse_workspace_id` - (Required) The ID of the Synapse Workspace on which to create the Firewall Rule. Changing this forces a new resource to be created. * `start_ip_address` - (Required) The starting IP address to allow through the firewall for this rule. @@ -80,8 +80,8 @@ The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/d ## Import -Synapse Firewall Rules can be imported using the `resource id`, e.g. +Synapse Firewall Rule can be imported using the `resource id`, e.g. ```shell -terraform import azurerm_synapse_firewall_rule.rule1 /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resourcegroup1/providers/Microsoft.Synapse/workspaces/workspace1/firewallRules/rule1 +terraform import azurerm_synapse_firewall_rule.example /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resourcegroup1/providers/Microsoft.Synapse/workspaces/workspace1/firewallRules/rule1 ``` From c11245a227dc41897319db5f05d65b719b97c5c9 Mon Sep 17 00:00:00 2001 From: njucz <740360112@qq.com> Date: Mon, 27 Jul 2020 16:22:18 +0800 Subject: [PATCH 6/6] update --- azurerm/internal/services/synapse/parse/synapse_workspace.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/azurerm/internal/services/synapse/parse/synapse_workspace.go b/azurerm/internal/services/synapse/parse/synapse_workspace.go index 27b71b8b33db..43360d2d47bb 100644 --- a/azurerm/internal/services/synapse/parse/synapse_workspace.go +++ b/azurerm/internal/services/synapse/parse/synapse_workspace.go @@ -19,7 +19,8 @@ func SynapseWorkspaceID(input string) (*SynapseWorkspaceId, error) { } synapseWorkspace := SynapseWorkspaceId{ - ResourceGroup: id.ResourceGroup, + ResourceGroup: id.ResourceGroup, + SubscriptionID: id.SubscriptionID, } if synapseWorkspace.Name, err = id.PopSegment("workspaces"); err != nil { return nil, err