From 49c42811373ac24652b1bd0fb6eb558c43470c49 Mon Sep 17 00:00:00 2001 From: Owen Farrell Date: Thu, 15 Jul 2021 09:57:08 -0400 Subject: [PATCH] new resource "azurerm_synapse_private_link_hub" (#12495) * Add support for Synapse Private Link Hub resources Signed-off-by: Owen Farrell * Apply suggestions from code review Co-authored-by: Tom Bamford * Apply acceptance test suggestions from code review Co-authored-by: Tom Bamford * Autogenerate Synapse Private Link Hub ID source Signed-off-by: Owen Farrell * Test import of Synapse Private Link Hub Signed-off-by: Owen Farrell Co-authored-by: Tom Bamford --- .../services/synapse/client/client.go | 5 + .../synapse/parse/private_link_hub.go | 69 ++++++++ .../synapse/parse/private_link_hub_test.go | 112 ++++++++++++ .../internal/services/synapse/registration.go | 1 + .../internal/services/synapse/resourceids.go | 1 + .../synapse/synapse_private_link_hub.go | 165 ++++++++++++++++++ .../synapse/synapse_private_link_hub_test.go | 145 +++++++++++++++ .../synapse/validate/private_link_hub_id.go | 23 +++ .../validate/private_link_hub_id_test.go | 76 ++++++++ .../synapse/validate/private_link_hub_name.go | 24 +++ .../validate/private_link_hub_name_test.go | 63 +++++++ .../r/synapse_private_link_hub.html.markdown | 56 ++++++ 12 files changed, 740 insertions(+) create mode 100644 azurerm/internal/services/synapse/parse/private_link_hub.go create mode 100644 azurerm/internal/services/synapse/parse/private_link_hub_test.go create mode 100644 azurerm/internal/services/synapse/synapse_private_link_hub.go create mode 100644 azurerm/internal/services/synapse/synapse_private_link_hub_test.go create mode 100644 azurerm/internal/services/synapse/validate/private_link_hub_id.go create mode 100644 azurerm/internal/services/synapse/validate/private_link_hub_id_test.go create mode 100644 azurerm/internal/services/synapse/validate/private_link_hub_name.go create mode 100644 azurerm/internal/services/synapse/validate/private_link_hub_name_test.go create mode 100644 website/docs/r/synapse_private_link_hub.html.markdown diff --git a/azurerm/internal/services/synapse/client/client.go b/azurerm/internal/services/synapse/client/client.go index 15b215c2ee60..34d995fb05c3 100644 --- a/azurerm/internal/services/synapse/client/client.go +++ b/azurerm/internal/services/synapse/client/client.go @@ -12,6 +12,7 @@ import ( type Client struct { FirewallRulesClient *synapse.IPFirewallRulesClient + PrivateLinkHubsClient *synapse.PrivateLinkHubsClient SparkPoolClient *synapse.BigDataPoolsClient SqlPoolClient *synapse.SQLPoolsClient SqlPoolTransparentDataEncryptionClient *synapse.SQLPoolTransparentDataEncryptionsClient @@ -26,6 +27,9 @@ func NewClient(o *common.ClientOptions) *Client { firewallRuleClient := synapse.NewIPFirewallRulesClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) o.ConfigureClient(&firewallRuleClient.Client, o.ResourceManagerAuthorizer) + privateLinkHubsClient := synapse.NewPrivateLinkHubsClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) + o.ConfigureClient(&privateLinkHubsClient.Client, o.ResourceManagerAuthorizer) + // the service team hopes to rename it to sparkPool, so rename the sdk here sparkPoolClient := synapse.NewBigDataPoolsClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) o.ConfigureClient(&sparkPoolClient.Client, o.ResourceManagerAuthorizer) @@ -47,6 +51,7 @@ func NewClient(o *common.ClientOptions) *Client { return &Client{ FirewallRulesClient: &firewallRuleClient, + PrivateLinkHubsClient: &privateLinkHubsClient, SparkPoolClient: &sparkPoolClient, SqlPoolClient: &sqlPoolClient, SqlPoolTransparentDataEncryptionClient: &sqlPoolTransparentDataEncryptionClient, diff --git a/azurerm/internal/services/synapse/parse/private_link_hub.go b/azurerm/internal/services/synapse/parse/private_link_hub.go new file mode 100644 index 000000000000..50cadfbc5be4 --- /dev/null +++ b/azurerm/internal/services/synapse/parse/private_link_hub.go @@ -0,0 +1,69 @@ +package parse + +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + +import ( + "fmt" + "strings" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" +) + +type PrivateLinkHubId struct { + SubscriptionId string + ResourceGroup string + Name string +} + +func NewPrivateLinkHubID(subscriptionId, resourceGroup, name string) PrivateLinkHubId { + return PrivateLinkHubId{ + SubscriptionId: subscriptionId, + ResourceGroup: resourceGroup, + Name: name, + } +} + +func (id PrivateLinkHubId) String() string { + segments := []string{ + fmt.Sprintf("Name %q", id.Name), + fmt.Sprintf("Resource Group %q", id.ResourceGroup), + } + segmentsStr := strings.Join(segments, " / ") + return fmt.Sprintf("%s: (%s)", "Private Link Hub", segmentsStr) +} + +func (id PrivateLinkHubId) ID() string { + fmtString := "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Synapse/privateLinkHubs/%s" + return fmt.Sprintf(fmtString, id.SubscriptionId, id.ResourceGroup, id.Name) +} + +// PrivateLinkHubID parses a PrivateLinkHub ID into an PrivateLinkHubId struct +func PrivateLinkHubID(input string) (*PrivateLinkHubId, error) { + id, err := azure.ParseAzureResourceID(input) + if err != nil { + return nil, err + } + + resourceId := PrivateLinkHubId{ + SubscriptionId: id.SubscriptionID, + ResourceGroup: id.ResourceGroup, + } + + if resourceId.SubscriptionId == "" { + return nil, fmt.Errorf("ID was missing the 'subscriptions' element") + } + + if resourceId.ResourceGroup == "" { + return nil, fmt.Errorf("ID was missing the 'resourceGroups' element") + } + + if resourceId.Name, err = id.PopSegment("privateLinkHubs"); err != nil { + return nil, err + } + + if err := id.ValidateNoEmptySegments(input); err != nil { + return nil, err + } + + return &resourceId, nil +} diff --git a/azurerm/internal/services/synapse/parse/private_link_hub_test.go b/azurerm/internal/services/synapse/parse/private_link_hub_test.go new file mode 100644 index 000000000000..9ebac52ff3c8 --- /dev/null +++ b/azurerm/internal/services/synapse/parse/private_link_hub_test.go @@ -0,0 +1,112 @@ +package parse + +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + +import ( + "testing" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/resourceid" +) + +var _ resourceid.Formatter = PrivateLinkHubId{} + +func TestPrivateLinkHubIDFormatter(t *testing.T) { + actual := NewPrivateLinkHubID("12345678-1234-9876-4563-123456789012", "resGroup1", "privateLinkHub1").ID() + expected := "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Synapse/privateLinkHubs/privateLinkHub1" + if actual != expected { + t.Fatalf("Expected %q but got %q", expected, actual) + } +} + +func TestPrivateLinkHubID(t *testing.T) { + testData := []struct { + Input string + Error bool + Expected *PrivateLinkHubId + }{ + + { + // empty + Input: "", + Error: true, + }, + + { + // missing SubscriptionId + Input: "/", + Error: true, + }, + + { + // missing value for SubscriptionId + Input: "/subscriptions/", + Error: true, + }, + + { + // missing ResourceGroup + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/", + Error: true, + }, + + { + // missing value for ResourceGroup + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/", + Error: true, + }, + + { + // missing Name + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Synapse/", + Error: true, + }, + + { + // missing value for Name + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Synapse/privateLinkHubs/", + Error: true, + }, + + { + // valid + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Synapse/privateLinkHubs/privateLinkHub1", + Expected: &PrivateLinkHubId{ + SubscriptionId: "12345678-1234-9876-4563-123456789012", + ResourceGroup: "resGroup1", + Name: "privateLinkHub1", + }, + }, + + { + // upper-cased + Input: "/SUBSCRIPTIONS/12345678-1234-9876-4563-123456789012/RESOURCEGROUPS/RESGROUP1/PROVIDERS/MICROSOFT.SYNAPSE/PRIVATELINKHUBS/PRIVATELINKHUB1", + Error: true, + }, + } + + for _, v := range testData { + t.Logf("[DEBUG] Testing %q", v.Input) + + actual, err := PrivateLinkHubID(v.Input) + if err != nil { + if v.Error { + continue + } + + t.Fatalf("Expect a value but got an error: %s", err) + } + if v.Error { + t.Fatal("Expect an error but didn't get one") + } + + if actual.SubscriptionId != v.Expected.SubscriptionId { + t.Fatalf("Expected %q but got %q for SubscriptionId", v.Expected.SubscriptionId, actual.SubscriptionId) + } + if actual.ResourceGroup != v.Expected.ResourceGroup { + t.Fatalf("Expected %q but got %q for ResourceGroup", v.Expected.ResourceGroup, actual.ResourceGroup) + } + 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/registration.go b/azurerm/internal/services/synapse/registration.go index ad85fe3d7459..3a190bc7e0f7 100644 --- a/azurerm/internal/services/synapse/registration.go +++ b/azurerm/internal/services/synapse/registration.go @@ -27,6 +27,7 @@ func (r Registration) SupportedDataSources() map[string]*pluginsdk.Resource { func (r Registration) SupportedResources() map[string]*pluginsdk.Resource { return map[string]*pluginsdk.Resource{ "azurerm_synapse_firewall_rule": resourceSynapseFirewallRule(), + "azurerm_synapse_private_link_hub": resourceSynapsePrivateLinkHub(), "azurerm_synapse_managed_private_endpoint": resourceSynapseManagedPrivateEndpoint(), "azurerm_synapse_role_assignment": resourceSynapseRoleAssignment(), "azurerm_synapse_spark_pool": resourceSynapseSparkPool(), diff --git a/azurerm/internal/services/synapse/resourceids.go b/azurerm/internal/services/synapse/resourceids.go index 44e5b456af33..19a9765b2362 100644 --- a/azurerm/internal/services/synapse/resourceids.go +++ b/azurerm/internal/services/synapse/resourceids.go @@ -6,3 +6,4 @@ package synapse //go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=SqlPool -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Synapse/workspaces/workspace1/sqlPools/sqlPool1 //go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=Workspace -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Synapse/workspaces/workspace1 //go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=ManagedPrivateEndpoint -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Synapse/workspaces/workspace1/managedVirtualNetworks/default/managedPrivateEndpoints/endpoint1 +//go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=PrivateLinkHub -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Synapse/privateLinkHubs/privateLinkHub1 diff --git a/azurerm/internal/services/synapse/synapse_private_link_hub.go b/azurerm/internal/services/synapse/synapse_private_link_hub.go new file mode 100644 index 000000000000..7ee9cc5a7c57 --- /dev/null +++ b/azurerm/internal/services/synapse/synapse_private_link_hub.go @@ -0,0 +1,165 @@ +package synapse + +import ( + "fmt" + "log" + "time" + + "github.com/Azure/azure-sdk-for-go/services/synapse/mgmt/2021-03-01/synapse" + "github.com/hashicorp/go-azure-helpers/response" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" + "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/location" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/synapse/parse" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/synapse/validate" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tags" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/pluginsdk" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func resourceSynapsePrivateLinkHub() *pluginsdk.Resource { + return &pluginsdk.Resource{ + Create: resourceSynapsePrivateLinkHubCreate, + Read: resourceSynapsePrivateLinkHubRead, + Update: resourceSynapsePrivateLinkHubUpdate, + Delete: resourceSynapsePrivateLinkHubDelete, + + Timeouts: &pluginsdk.ResourceTimeout{ + Create: pluginsdk.DefaultTimeout(30 * time.Minute), + Read: pluginsdk.DefaultTimeout(5 * time.Minute), + Update: pluginsdk.DefaultTimeout(30 * time.Minute), + Delete: pluginsdk.DefaultTimeout(30 * time.Minute), + }, + + Importer: pluginsdk.ImporterValidatingResourceId(func(id string) error { + _, err := parse.PrivateLinkHubID(id) + return err + }), + + Schema: map[string]*pluginsdk.Schema{ + "name": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.PrivateLinkHubName, + }, + + "resource_group_name": azure.SchemaResourceGroupName(), + + "location": azure.SchemaLocation(), + + "tags": tags.Schema(), + }, + } +} + +func resourceSynapsePrivateLinkHubCreate(d *pluginsdk.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Synapse.PrivateLinkHubsClient + ctx, cancel := timeouts.ForCreate(meta.(*clients.Client).StopContext, d) + defer cancel() + + subscriptionId := meta.(*clients.Client).Account.SubscriptionId + id := parse.NewPrivateLinkHubID(subscriptionId, d.Get("resource_group_name").(string), d.Get("name").(string)) + + existing, err := client.Get(ctx, id.ResourceGroup, id.Name) + if err != nil { + if !utils.ResponseWasNotFound(existing.Response) { + return fmt.Errorf("checking for presence of %s: %+v", id, err) + } + } + if !utils.ResponseWasNotFound(existing.Response) { + return tf.ImportAsExistsError("azurerm_synapse_private_link_hub", id.ID()) + } + + privateLinkHubInfo := synapse.PrivateLinkHub{ + Location: utils.String(location.Normalize(d.Get("location").(string))), + Tags: tags.Expand(d.Get("tags").(map[string]interface{})), + } + + _, err = client.CreateOrUpdate(ctx, privateLinkHubInfo, id.ResourceGroup, id.Name) + if err != nil { + return fmt.Errorf("creating %s: %+v", id, err) + } + + d.SetId(id.ID()) + + return resourceSynapsePrivateLinkHubRead(d, meta) +} + +func resourceSynapsePrivateLinkHubRead(d *pluginsdk.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Synapse.PrivateLinkHubsClient + ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := parse.PrivateLinkHubID(d.Id()) + if err != nil { + return err + } + + resp, err := client.Get(ctx, id.ResourceGroup, id.Name) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + log.Printf("[INFO] synapse %q does not exist - removing from state", d.Id()) + d.SetId("") + return nil + } + return fmt.Errorf("retrieving %s: %+v", id, err) + } + + d.Set("name", id.Name) + d.Set("resource_group_name", id.ResourceGroup) + d.Set("location", location.NormalizeNilable(resp.Location)) + + return tags.FlattenAndSet(d, resp.Tags) +} + +func resourceSynapsePrivateLinkHubUpdate(d *pluginsdk.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Synapse.PrivateLinkHubsClient + ctx, cancel := timeouts.ForUpdate(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := parse.PrivateLinkHubID(d.Id()) + if err != nil { + return err + } + + if d.HasChange("tags") { + privateLinkHubPatchInfo := synapse.PrivateLinkHubPatchInfo{ + Tags: tags.Expand(d.Get("tags").(map[string]interface{})), + } + + _, err := client.Update(ctx, privateLinkHubPatchInfo, id.ResourceGroup, id.Name) + if err != nil { + return fmt.Errorf("updating %s: %+v", id, err) + } + } + + return resourceSynapsePrivateLinkHubRead(d, meta) +} + +func resourceSynapsePrivateLinkHubDelete(d *pluginsdk.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Synapse.PrivateLinkHubsClient + ctx, cancel := timeouts.ForDelete(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := parse.PrivateLinkHubID(d.Id()) + if err != nil { + return err + } + + future, err := client.Delete(ctx, id.ResourceGroup, id.Name) + if err != nil { + return fmt.Errorf("deleting %s: %+v", id, err) + } + + // sometimes the waitForCompletion rest api will return 404 + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + if !response.WasNotFound(future.Response()) { + return fmt.Errorf("waiting for deletion of %s: %+v", id, err) + } + } + + return nil +} diff --git a/azurerm/internal/services/synapse/synapse_private_link_hub_test.go b/azurerm/internal/services/synapse/synapse_private_link_hub_test.go new file mode 100644 index 000000000000..23e0758bfc78 --- /dev/null +++ b/azurerm/internal/services/synapse/synapse_private_link_hub_test.go @@ -0,0 +1,145 @@ +package synapse_test + +import ( + "context" + "fmt" + "testing" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/acceptance" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/acceptance/check" + "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/tf/pluginsdk" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +type SynapsePrivateLinkHubResource struct{} + +func TestAccSynapsePrivateLinkHub_basic(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_synapse_private_link_hub", "test") + r := SynapsePrivateLinkHubResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccSynapsePrivateLinkHub_requiresImport(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_synapse_private_link_hub", "test") + r := SynapsePrivateLinkHubResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.RequiresImportErrorStep(r.requiresImport), + }) +} + +func TestAccSynapsePrivateLinkHub_update(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_synapse_private_link_hub", "test") + r := SynapsePrivateLinkHubResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + { + Config: r.withUpdateFields(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("tags.%").HasValue("1"), + check.That(data.ResourceName).Key("tags.ENV").HasValue("Test2"), + ), + }, + }) +} + +func (r SynapsePrivateLinkHubResource) Exists(ctx context.Context, client *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { + id, err := parse.PrivateLinkHubID(state.ID) + if err != nil { + return nil, err + } + + resp, err := client.Synapse.PrivateLinkHubsClient.Get(ctx, id.ResourceGroup, id.Name) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + return utils.Bool(false), nil + } + return nil, fmt.Errorf("retrieving %s: %+v", id, err) + } + + return utils.Bool(true), nil +} + +func (r SynapsePrivateLinkHubResource) basic(data acceptance.TestData) string { + template := r.template(data) + return fmt.Sprintf(` +%[1]s + +resource "azurerm_synapse_private_link_hub" "test" { + name = "acctestsw%[2]d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location +} +`, template, data.RandomInteger) +} + +func (r SynapsePrivateLinkHubResource) requiresImport(data acceptance.TestData) string { + config := r.basic(data) + return fmt.Sprintf(` +%s + +resource "azurerm_synapse_private_link_hub" "import" { + name = azurerm_synapse_private_link_hub.test.name + resource_group_name = azurerm_synapse_private_link_hub.test.resource_group_name + location = azurerm_synapse_private_link_hub.test.location +} +`, config) +} + +func (r SynapsePrivateLinkHubResource) withUpdateFields(data acceptance.TestData) string { + template := r.template(data) + return fmt.Sprintf(` +%[1]s + +resource "azurerm_synapse_private_link_hub" "test" { + name = "acctestsw%[2]d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + + tags = { + ENV = "Test2" + } +} +`, template, data.RandomInteger) +} + +func (r SynapsePrivateLinkHubResource) template(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features { + key_vault { + purge_soft_delete_on_destroy = false + } + } +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-synapse-%d" + location = "%s" +} +`, data.RandomInteger, data.Locations.Primary) +} diff --git a/azurerm/internal/services/synapse/validate/private_link_hub_id.go b/azurerm/internal/services/synapse/validate/private_link_hub_id.go new file mode 100644 index 000000000000..142fd407b59c --- /dev/null +++ b/azurerm/internal/services/synapse/validate/private_link_hub_id.go @@ -0,0 +1,23 @@ +package validate + +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + +import ( + "fmt" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/synapse/parse" +) + +func PrivateLinkHubID(input interface{}, key string) (warnings []string, errors []error) { + v, ok := input.(string) + if !ok { + errors = append(errors, fmt.Errorf("expected %q to be a string", key)) + return + } + + if _, err := parse.PrivateLinkHubID(v); err != nil { + errors = append(errors, err) + } + + return +} diff --git a/azurerm/internal/services/synapse/validate/private_link_hub_id_test.go b/azurerm/internal/services/synapse/validate/private_link_hub_id_test.go new file mode 100644 index 000000000000..f714c73b41b9 --- /dev/null +++ b/azurerm/internal/services/synapse/validate/private_link_hub_id_test.go @@ -0,0 +1,76 @@ +package validate + +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + +import "testing" + +func TestPrivateLinkHubID(t *testing.T) { + cases := []struct { + Input string + Valid bool + }{ + + { + // empty + Input: "", + Valid: false, + }, + + { + // missing SubscriptionId + Input: "/", + Valid: false, + }, + + { + // missing value for SubscriptionId + Input: "/subscriptions/", + Valid: false, + }, + + { + // missing ResourceGroup + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/", + Valid: false, + }, + + { + // missing value for ResourceGroup + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/", + Valid: false, + }, + + { + // missing Name + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Synapse/", + Valid: false, + }, + + { + // missing value for Name + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Synapse/privateLinkHubs/", + Valid: false, + }, + + { + // valid + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Synapse/privateLinkHubs/privateLinkHub1", + Valid: true, + }, + + { + // upper-cased + Input: "/SUBSCRIPTIONS/12345678-1234-9876-4563-123456789012/RESOURCEGROUPS/RESGROUP1/PROVIDERS/MICROSOFT.SYNAPSE/PRIVATELINKHUBS/PRIVATELINKHUB1", + Valid: false, + }, + } + for _, tc := range cases { + t.Logf("[DEBUG] Testing Value %s", tc.Input) + _, errors := PrivateLinkHubID(tc.Input, "test") + valid := len(errors) == 0 + + if tc.Valid != valid { + t.Fatalf("Expected %t but got %t", tc.Valid, valid) + } + } +} diff --git a/azurerm/internal/services/synapse/validate/private_link_hub_name.go b/azurerm/internal/services/synapse/validate/private_link_hub_name.go new file mode 100644 index 000000000000..8c781cef5e73 --- /dev/null +++ b/azurerm/internal/services/synapse/validate/private_link_hub_name.go @@ -0,0 +1,24 @@ +package validate + +import ( + "fmt" + "regexp" +) + +func PrivateLinkHubName(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. The value must be between 1 and 45 characters long + // 2. must contain only lowercase letters or numbers. + + if !regexp.MustCompile(`^[a-z0-9]{1,45}$`).MatchString(v) { + errors = append(errors, fmt.Errorf("%s must be between 1 and 45 characters long and can contain only lowercase letters or numbers", k)) + return + } + return warnings, errors +} diff --git a/azurerm/internal/services/synapse/validate/private_link_hub_name_test.go b/azurerm/internal/services/synapse/validate/private_link_hub_name_test.go new file mode 100644 index 000000000000..0f84ce8926e6 --- /dev/null +++ b/azurerm/internal/services/synapse/validate/private_link_hub_name_test.go @@ -0,0 +1,63 @@ +package validate + +import ( + "testing" +) + +func TestPrivateLinkHubName(t *testing.T) { + testData := []struct { + input string + expected bool + }{ + { + // empty + input: "", + expected: false, + }, + { + // 1 char + input: "a", + expected: true, + }, + { + // basic example + input: "abc123", + expected: true, + }, + { + // can't contain upper case + input: "aBc123", + expected: false, + }, + { + // can't contain underscore + input: "abc_123", + expected: false, + }, + { + // can't contain hyphen + input: "abc-123", + expected: false, + }, + { + // 45 chars + input: "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrs", + expected: true, + }, + { + // 46 chars + input: "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrst", + expected: false, + }, + } + + for _, v := range testData { + t.Logf("[DEBUG] Testing %q..", v.input) + + _, errors := PrivateLinkHubName(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_private_link_hub.html.markdown b/website/docs/r/synapse_private_link_hub.html.markdown new file mode 100644 index 000000000000..b48ce88bbd8c --- /dev/null +++ b/website/docs/r/synapse_private_link_hub.html.markdown @@ -0,0 +1,56 @@ +--- +subcategory: "Synapse" +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_synapse_private_link_hub" +description: |- + Manages a Synapse Private Link Hub. +--- + +# azurerm_synapse_private_link_hub + +Manages a Synapse Private Link Hub. + +## Example Usage + +```hcl +resource "azurerm_synapse_private_link_hub" "example" { + name = "example-resource" + resource_group_name = "example-rg" + location = "West Europe" +} +``` + +## Arguments Reference + +The following arguments are supported: + +* `name` - (Required) The name which should be used for this Synapse Private Link Hub. Changing this forces a new Synapse Private Link Hub to be created. + +* `resource_group_name` - (Required) The name of the resource group in which to create the Synapse Private Link Hub. Changing this forces a new resource to be created. + +* `location` - (Required) Specifies the Azure location where the Synapse Private Link Hub exists. Changing this forces a new resource to be created. + +* `tags` - (Optional) A mapping of tags which should be assigned to the Synapse Private Link Hub. + +## Attributes Reference + +In addition to the Arguments listed above - the following Attributes are exported: + +* `id` - The ID of the Synapse Private Link Hub. + +## 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 Private Link Hub. +* `read` - (Defaults to 5 minutes) Used when retrieving the Synapse Private Link Hub. +* `update` - (Defaults to 30 minutes) Used when updating the Synapse Private Link Hub. +* `delete` - (Defaults to 30 minutes) Used when deleting the Synapse Private Link Hub. + +## Import + +Synapse Private Link Hub can be imported using the `resource id`, e.g. + +```shell +terraform import azurerm_synapse_private_link_hub.example /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/group1/providers/Microsoft.Synapse/privateLinkHubs/privateLinkHub1 +```