From 76d63e19730df7a9dffc49f5c9e0eb6a398e4693 Mon Sep 17 00:00:00 2001 From: njucz Date: Wed, 12 Aug 2020 07:20:41 +0800 Subject: [PATCH] new resource for `azurerm_synapse_firewall_rule` (#7904) another resource for #7406 --- .../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 | 12 +- .../internal/services/synapse/registration.go | 3 +- .../synapse/synapse_firewall_rule_resource.go | 166 +++++++++++++++ .../synapse_firewall_rule_resource_test.go | 198 ++++++++++++++++++ .../synapse/validate/synapse_firewall_rule.go | 25 +++ .../validate/synapse_firewall_rule_test.go | 58 +++++ .../synapse/validate/synapse_workspace.go | 17 ++ website/azurerm.erb | 4 + .../r/synapse_firewall_rule.html.markdown | 87 ++++++++ 12 files changed, 687 insertions(+), 4 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 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/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..43360d2d47bb 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) { @@ -18,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 @@ -29,3 +31,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..1431031f3a5e --- /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, + ValidateFunc: validate.SynapseFirewallRuleName, + }, + + "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) + } + + 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/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..bcee3cdf1e2f --- /dev/null +++ b/azurerm/internal/services/synapse/tests/synapse_firewall_rule_resource_test.go @@ -0,0 +1,198 @@ +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 + } + 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("expected no Firewall Rule but found %+v", resp) + } + 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..7baebb60d5bc --- /dev/null +++ b/azurerm/internal/services/synapse/validate/synapse_firewall_rule.go @@ -0,0 +1,25 @@ +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. 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/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 +} diff --git a/website/azurerm.erb b/website/azurerm.erb index 7cc96ba219aa..310cba14a937 100644 --- a/website/azurerm.erb +++ b/website/azurerm.erb @@ -2693,6 +2693,10 @@
  • azurerm_synapse_workspace
  • + +
  • + azurerm_synapse_firewall_rule +
  • 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..779308e55b57 --- /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. 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. 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. + +* `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 Rule can be imported using the `resource id`, e.g. + +```shell +terraform import azurerm_synapse_firewall_rule.example /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resourcegroup1/providers/Microsoft.Synapse/workspaces/workspace1/firewallRules/rule1 +```