From c7a3d91e86f7e175adbca484bc09f8aae3a0a182 Mon Sep 17 00:00:00 2001 From: Aris van Ommeren Date: Wed, 10 Nov 2021 09:48:26 +0100 Subject: [PATCH 1/3] `sql_managed_identity_active_directory_administrator`: New resource --- internal/services/sql/client/client.go | 68 +++--- ...ce_azure_active_directory_administrator.go | 75 +++++++ ...ure_active_directory_administrator_test.go | 128 +++++++++++ internal/services/sql/registration.go | 19 +- internal/services/sql/resourceids.go | 1 + ...managed_instance_administrator_resource.go | 210 ++++++++++++++++++ ...ed_instance_administrator_resource_test.go | 151 +++++++++++++ ...azure_active_directory_administrator_id.go | 23 ++ ..._active_directory_administrator_id_test.go | 88 ++++++++ ...tive_directory_administrator.html.markdown | 80 +++++++ 10 files changed, 805 insertions(+), 38 deletions(-) create mode 100644 internal/services/sql/parse/managed_instance_azure_active_directory_administrator.go create mode 100644 internal/services/sql/parse/managed_instance_azure_active_directory_administrator_test.go create mode 100644 internal/services/sql/sql_managed_instance_administrator_resource.go create mode 100644 internal/services/sql/sql_managed_instance_administrator_resource_test.go create mode 100644 internal/services/sql/validate/managed_instance_azure_active_directory_administrator_id.go create mode 100644 internal/services/sql/validate/managed_instance_azure_active_directory_administrator_id_test.go create mode 100644 website/docs/r/sql_managed_instance_active_directory_administrator.html.markdown diff --git a/internal/services/sql/client/client.go b/internal/services/sql/client/client.go index 7287d78eb66b..d13a0072fe4a 100644 --- a/internal/services/sql/client/client.go +++ b/internal/services/sql/client/client.go @@ -8,21 +8,23 @@ import ( ) type Client struct { - DatabasesClient *sql.DatabasesClient - DatabaseThreatDetectionPoliciesClient *sql.DatabaseThreatDetectionPoliciesClient - ElasticPoolsClient *sql.ElasticPoolsClient - DatabaseExtendedBlobAuditingPoliciesClient *sql.ExtendedDatabaseBlobAuditingPoliciesClient - FirewallRulesClient *sql.FirewallRulesClient - FailoverGroupsClient *sql.FailoverGroupsClient - ManagedInstancesClient *sqlv5.ManagedInstancesClient - ManagedDatabasesClient *msi.ManagedDatabasesClient - ServersClient *sql.ServersClient - ServerExtendedBlobAuditingPoliciesClient *sql.ExtendedServerBlobAuditingPoliciesClient - ServerConnectionPoliciesClient *sql.ServerConnectionPoliciesClient - ServerAzureADAdministratorsClient *sqlv5.ServerAzureADAdministratorsClient - ServerAzureADOnlyAuthenticationsClient *sqlv5.ServerAzureADOnlyAuthenticationsClient - ServerSecurityAlertPoliciesClient *sql.ServerSecurityAlertPoliciesClient - VirtualNetworkRulesClient *sql.VirtualNetworkRulesClient + DatabasesClient *sql.DatabasesClient + DatabaseThreatDetectionPoliciesClient *sql.DatabaseThreatDetectionPoliciesClient + ElasticPoolsClient *sql.ElasticPoolsClient + DatabaseExtendedBlobAuditingPoliciesClient *sql.ExtendedDatabaseBlobAuditingPoliciesClient + FirewallRulesClient *sql.FirewallRulesClient + FailoverGroupsClient *sql.FailoverGroupsClient + ManagedInstancesClient *sqlv5.ManagedInstancesClient + ManagedInstanceAdministratorsClient *sqlv5.ManagedInstanceAdministratorsClient + ManagedInstanceAzureADOnlyAuthenticationsClient *sqlv5.ManagedInstanceAzureADOnlyAuthenticationsClient + ManagedDatabasesClient *msi.ManagedDatabasesClient + ServersClient *sql.ServersClient + ServerExtendedBlobAuditingPoliciesClient *sql.ExtendedServerBlobAuditingPoliciesClient + ServerConnectionPoliciesClient *sql.ServerConnectionPoliciesClient + ServerAzureADAdministratorsClient *sqlv5.ServerAzureADAdministratorsClient + ServerAzureADOnlyAuthenticationsClient *sqlv5.ServerAzureADOnlyAuthenticationsClient + ServerSecurityAlertPoliciesClient *sql.ServerSecurityAlertPoliciesClient + VirtualNetworkRulesClient *sql.VirtualNetworkRulesClient } func NewClient(o *common.ClientOptions) *Client { @@ -48,6 +50,12 @@ func NewClient(o *common.ClientOptions) *Client { managedInstancesClient := sqlv5.NewManagedInstancesClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) o.ConfigureClient(&managedInstancesClient.Client, o.ResourceManagerAuthorizer) + managedInstanceAdministratorsClient := sqlv5.NewManagedInstanceAdministratorsClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) + o.ConfigureClient(&managedInstanceAdministratorsClient.Client, o.ResourceManagerAuthorizer) + + managedInstanceAzureADOnlyAuthenticationsClient := sqlv5.NewManagedInstanceAzureADOnlyAuthenticationsClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) + o.ConfigureClient(&managedInstanceAzureADOnlyAuthenticationsClient.Client, o.ResourceManagerAuthorizer) + managedDatabasesClient := msi.NewManagedDatabasesClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) o.ConfigureClient(&managedDatabasesClient.Client, o.ResourceManagerAuthorizer) @@ -74,19 +82,21 @@ func NewClient(o *common.ClientOptions) *Client { return &Client{ DatabasesClient: &databasesClient, - DatabaseExtendedBlobAuditingPoliciesClient: &databaseExtendedBlobAuditingPoliciesClient, - DatabaseThreatDetectionPoliciesClient: &databaseThreatDetectionPoliciesClient, - ElasticPoolsClient: &elasticPoolsClient, - FailoverGroupsClient: &failoverGroupsClient, - FirewallRulesClient: &firewallRulesClient, - ManagedInstancesClient: &managedInstancesClient, - ManagedDatabasesClient: &managedDatabasesClient, - ServersClient: &serversClient, - ServerAzureADAdministratorsClient: &serverAzureADAdministratorsClient, - ServerAzureADOnlyAuthenticationsClient: &serverAzureADOnlyAuthenticationsClient, - ServerConnectionPoliciesClient: &serverConnectionPoliciesClient, - ServerExtendedBlobAuditingPoliciesClient: &serverExtendedBlobAuditingPoliciesClient, - ServerSecurityAlertPoliciesClient: &serverSecurityAlertPoliciesClient, - VirtualNetworkRulesClient: &virtualNetworkRulesClient, + DatabaseExtendedBlobAuditingPoliciesClient: &databaseExtendedBlobAuditingPoliciesClient, + DatabaseThreatDetectionPoliciesClient: &databaseThreatDetectionPoliciesClient, + ElasticPoolsClient: &elasticPoolsClient, + FailoverGroupsClient: &failoverGroupsClient, + FirewallRulesClient: &firewallRulesClient, + ManagedInstancesClient: &managedInstancesClient, + ManagedInstanceAdministratorsClient: &managedInstanceAdministratorsClient, + ManagedInstanceAzureADOnlyAuthenticationsClient: &managedInstanceAzureADOnlyAuthenticationsClient, + ManagedDatabasesClient: &managedDatabasesClient, + ServersClient: &serversClient, + ServerAzureADAdministratorsClient: &serverAzureADAdministratorsClient, + ServerAzureADOnlyAuthenticationsClient: &serverAzureADOnlyAuthenticationsClient, + ServerConnectionPoliciesClient: &serverConnectionPoliciesClient, + ServerExtendedBlobAuditingPoliciesClient: &serverExtendedBlobAuditingPoliciesClient, + ServerSecurityAlertPoliciesClient: &serverSecurityAlertPoliciesClient, + VirtualNetworkRulesClient: &virtualNetworkRulesClient, } } diff --git a/internal/services/sql/parse/managed_instance_azure_active_directory_administrator.go b/internal/services/sql/parse/managed_instance_azure_active_directory_administrator.go new file mode 100644 index 000000000000..496f241733bf --- /dev/null +++ b/internal/services/sql/parse/managed_instance_azure_active_directory_administrator.go @@ -0,0 +1,75 @@ +package parse + +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + +import ( + "fmt" + "strings" + + "github.com/hashicorp/terraform-provider-azurerm/helpers/azure" +) + +type ManagedInstanceAzureActiveDirectoryAdministratorId struct { + SubscriptionId string + ResourceGroup string + ManagedInstanceName string + AdministratorName string +} + +func NewManagedInstanceAzureActiveDirectoryAdministratorID(subscriptionId, resourceGroup, managedInstanceName, administratorName string) ManagedInstanceAzureActiveDirectoryAdministratorId { + return ManagedInstanceAzureActiveDirectoryAdministratorId{ + SubscriptionId: subscriptionId, + ResourceGroup: resourceGroup, + ManagedInstanceName: managedInstanceName, + AdministratorName: administratorName, + } +} + +func (id ManagedInstanceAzureActiveDirectoryAdministratorId) String() string { + segments := []string{ + fmt.Sprintf("Administrator Name %q", id.AdministratorName), + fmt.Sprintf("Managed Instance Name %q", id.ManagedInstanceName), + fmt.Sprintf("Resource Group %q", id.ResourceGroup), + } + segmentsStr := strings.Join(segments, " / ") + return fmt.Sprintf("%s: (%s)", "Managed Instance Azure Active Directory Administrator", segmentsStr) +} + +func (id ManagedInstanceAzureActiveDirectoryAdministratorId) ID() string { + fmtString := "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Sql/managedInstances/%s/administrators/%s" + return fmt.Sprintf(fmtString, id.SubscriptionId, id.ResourceGroup, id.ManagedInstanceName, id.AdministratorName) +} + +// ManagedInstanceAzureActiveDirectoryAdministratorID parses a ManagedInstanceAzureActiveDirectoryAdministrator ID into an ManagedInstanceAzureActiveDirectoryAdministratorId struct +func ManagedInstanceAzureActiveDirectoryAdministratorID(input string) (*ManagedInstanceAzureActiveDirectoryAdministratorId, error) { + id, err := azure.ParseAzureResourceID(input) + if err != nil { + return nil, err + } + + resourceId := ManagedInstanceAzureActiveDirectoryAdministratorId{ + 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.ManagedInstanceName, err = id.PopSegment("managedInstances"); err != nil { + return nil, err + } + if resourceId.AdministratorName, err = id.PopSegment("administrators"); err != nil { + return nil, err + } + + if err := id.ValidateNoEmptySegments(input); err != nil { + return nil, err + } + + return &resourceId, nil +} diff --git a/internal/services/sql/parse/managed_instance_azure_active_directory_administrator_test.go b/internal/services/sql/parse/managed_instance_azure_active_directory_administrator_test.go new file mode 100644 index 000000000000..a1d0d6a0ab38 --- /dev/null +++ b/internal/services/sql/parse/managed_instance_azure_active_directory_administrator_test.go @@ -0,0 +1,128 @@ +package parse + +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + +import ( + "testing" + + "github.com/hashicorp/terraform-provider-azurerm/internal/resourceid" +) + +var _ resourceid.Formatter = ManagedInstanceAzureActiveDirectoryAdministratorId{} + +func TestManagedInstanceAzureActiveDirectoryAdministratorIDFormatter(t *testing.T) { + actual := NewManagedInstanceAzureActiveDirectoryAdministratorID("12345678-1234-9876-4563-123456789012", "resGroup1", "instance1", "activeDirectory").ID() + expected := "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Sql/managedInstances/instance1/administrators/activeDirectory" + if actual != expected { + t.Fatalf("Expected %q but got %q", expected, actual) + } +} + +func TestManagedInstanceAzureActiveDirectoryAdministratorID(t *testing.T) { + testData := []struct { + Input string + Error bool + Expected *ManagedInstanceAzureActiveDirectoryAdministratorId + }{ + + { + // 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 ManagedInstanceName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Sql/", + Error: true, + }, + + { + // missing value for ManagedInstanceName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Sql/managedInstances/", + Error: true, + }, + + { + // missing AdministratorName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Sql/managedInstances/instance1/", + Error: true, + }, + + { + // missing value for AdministratorName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Sql/managedInstances/instance1/administrators/", + Error: true, + }, + + { + // valid + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Sql/managedInstances/instance1/administrators/activeDirectory", + Expected: &ManagedInstanceAzureActiveDirectoryAdministratorId{ + SubscriptionId: "12345678-1234-9876-4563-123456789012", + ResourceGroup: "resGroup1", + ManagedInstanceName: "instance1", + AdministratorName: "activeDirectory", + }, + }, + + { + // upper-cased + Input: "/SUBSCRIPTIONS/12345678-1234-9876-4563-123456789012/RESOURCEGROUPS/RESGROUP1/PROVIDERS/MICROSOFT.SQL/MANAGEDINSTANCES/INSTANCE1/ADMINISTRATORS/ACTIVEDIRECTORY", + Error: true, + }, + } + + for _, v := range testData { + t.Logf("[DEBUG] Testing %q", v.Input) + + actual, err := ManagedInstanceAzureActiveDirectoryAdministratorID(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.ManagedInstanceName != v.Expected.ManagedInstanceName { + t.Fatalf("Expected %q but got %q for ManagedInstanceName", v.Expected.ManagedInstanceName, actual.ManagedInstanceName) + } + if actual.AdministratorName != v.Expected.AdministratorName { + t.Fatalf("Expected %q but got %q for AdministratorName", v.Expected.AdministratorName, actual.AdministratorName) + } + } +} diff --git a/internal/services/sql/registration.go b/internal/services/sql/registration.go index bbb7497bd360..bd56c7bea565 100644 --- a/internal/services/sql/registration.go +++ b/internal/services/sql/registration.go @@ -29,14 +29,15 @@ func (r Registration) SupportedDataSources() map[string]*pluginsdk.Resource { // SupportedResources returns the supported Resources supported by this Service func (r Registration) SupportedResources() map[string]*pluginsdk.Resource { return map[string]*pluginsdk.Resource{ - "azurerm_sql_active_directory_administrator": resourceSqlAdministrator(), - "azurerm_sql_database": resourceSqlDatabase(), - "azurerm_sql_elasticpool": resourceSqlElasticPool(), - "azurerm_sql_failover_group": resourceSqlFailoverGroup(), - "azurerm_sql_firewall_rule": resourceSqlFirewallRule(), - "azurerm_sql_managed_database": resourceArmSqlManagedDatabase(), - "azurerm_sql_managed_instance": resourceArmSqlMiServer(), - "azurerm_sql_server": resourceSqlServer(), - "azurerm_sql_virtual_network_rule": resourceSqlVirtualNetworkRule(), + "azurerm_sql_active_directory_administrator": resourceSqlAdministrator(), + "azurerm_sql_database": resourceSqlDatabase(), + "azurerm_sql_elasticpool": resourceSqlElasticPool(), + "azurerm_sql_failover_group": resourceSqlFailoverGroup(), + "azurerm_sql_firewall_rule": resourceSqlFirewallRule(), + "azurerm_sql_managed_database": resourceArmSqlManagedDatabase(), + "azurerm_sql_managed_instance": resourceArmSqlMiServer(), + "azurerm_sql_managed_instance_active_directory_administrator": resourceSqlManagedInstanceAdministrator(), + "azurerm_sql_server": resourceSqlServer(), + "azurerm_sql_virtual_network_rule": resourceSqlVirtualNetworkRule(), } } diff --git a/internal/services/sql/resourceids.go b/internal/services/sql/resourceids.go index 12e541dddb82..e5a9163ff66d 100644 --- a/internal/services/sql/resourceids.go +++ b/internal/services/sql/resourceids.go @@ -7,5 +7,6 @@ package sql //go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=FirewallRule -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Sql/servers/server1/firewallRules/rule1 //go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=ManagedInstance -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Sql/managedInstances/instance1 //go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=ManagedDatabase -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Sql/managedInstances/instance1/databases/database1 +//go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=ManagedInstanceAzureActiveDirectoryAdministrator -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Sql/managedInstances/instance1/administrators/activeDirectory //go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=Server -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Sql/servers/server1 //go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=VirtualNetworkRule -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Sql/servers/server1/virtualNetworkRules/virtualNetworkRule1 diff --git a/internal/services/sql/sql_managed_instance_administrator_resource.go b/internal/services/sql/sql_managed_instance_administrator_resource.go new file mode 100644 index 000000000000..872057d35203 --- /dev/null +++ b/internal/services/sql/sql_managed_instance_administrator_resource.go @@ -0,0 +1,210 @@ +package sql + +import ( + "fmt" + "log" + "net/http" + "time" + + "github.com/Azure/azure-sdk-for-go/services/preview/sql/mgmt/v5.0/sql" + "github.com/gofrs/uuid" + "github.com/hashicorp/terraform-provider-azurerm/helpers/azure" + "github.com/hashicorp/terraform-provider-azurerm/helpers/tf" + "github.com/hashicorp/terraform-provider-azurerm/internal/clients" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/sql/parse" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/validation" + "github.com/hashicorp/terraform-provider-azurerm/internal/timeouts" + "github.com/hashicorp/terraform-provider-azurerm/utils" +) + +func resourceSqlManagedInstanceAdministrator() *pluginsdk.Resource { + return &pluginsdk.Resource{ + Create: resourceSqlManagedInstanceActiveDirectoryAdministratorCreateUpdate, + Read: resourceSqlManagedInstanceActiveDirectoryAdministratorRead, + Update: resourceSqlManagedInstanceActiveDirectoryAdministratorCreateUpdate, + Delete: resourceSqlManagedInstanceActiveDirectoryAdministratorDelete, + Importer: pluginsdk.ImporterValidatingResourceId(func(id string) error { + _, err := parse.ManagedInstanceAzureActiveDirectoryAdministratorID(id) + return err + }), + + 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), + }, + + Schema: map[string]*pluginsdk.Schema{ + "managed_instance_name": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + }, + + "resource_group_name": azure.SchemaResourceGroupName(), + + "login": { + Type: pluginsdk.TypeString, + Required: true, + }, + + "object_id": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validation.IsUUID, + }, + + "tenant_id": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validation.IsUUID, + }, + + "azuread_authentication_only": { + Type: pluginsdk.TypeBool, + Optional: true, + Computed: true, + }, + }, + } +} + +func resourceSqlManagedInstanceActiveDirectoryAdministratorCreateUpdate(d *pluginsdk.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Sql.ManagedInstanceAdministratorsClient + aadOnlyAuthentictionsClient := meta.(*clients.Client).Sql.ManagedInstanceAzureADOnlyAuthenticationsClient + subscriptionId := meta.(*clients.Client).Account.SubscriptionId + ctx, cancel := timeouts.ForCreateUpdate(meta.(*clients.Client).StopContext, d) + defer cancel() + + id := parse.NewManagedInstanceAzureActiveDirectoryAdministratorID(subscriptionId, d.Get("resource_group_name").(string), d.Get("managed_instance_name").(string), "activeDirectory") + login := d.Get("login").(string) + objectId := uuid.FromStringOrNil(d.Get("object_id").(string)) + tenantId := uuid.FromStringOrNil(d.Get("tenant_id").(string)) + + if d.IsNewResource() { + existing, err := client.Get(ctx, id.ResourceGroup, id.ManagedInstanceName) + if err != nil { + if !utils.ResponseWasNotFound(existing.Response) { + return fmt.Errorf("checking for presence of existing %q: %+v", id, err) + } + } + + if existing.ID != nil && *existing.ID != "" { + return tf.ImportAsExistsError("azurerm_sql_managed_instance_active_directory_administrator", *existing.ID) + } + } + + if !d.IsNewResource() { + aadOnlyDeleteFuture, err := aadOnlyAuthentictionsClient.Delete(ctx, id.ResourceGroup, id.ManagedInstanceName) + if err != nil { + if aadOnlyDeleteFuture.Response() == nil || aadOnlyDeleteFuture.Response().StatusCode != http.StatusBadRequest { + return fmt.Errorf("deleting AD Only Authentications %s: %+v", id.String(), err) + } + log.Printf("[INFO] AD Only Authentication is not removed as AD Admin is not set for %s: %+v", id.String(), err) + } else if err = aadOnlyDeleteFuture.WaitForCompletionRef(ctx, aadOnlyAuthentictionsClient.Client); err != nil { + return fmt.Errorf("waiting for deletion of AD Only Authentications %s: %+v", id.String(), err) + } + } + + parameters := sql.ManagedInstanceAdministrator{ + ManagedInstanceAdministratorProperties: &sql.ManagedInstanceAdministratorProperties{ + AdministratorType: utils.String("ActiveDirectory"), + Login: utils.String(login), + Sid: &objectId, + TenantID: &tenantId, + }, + } + + future, err := client.CreateOrUpdate(ctx, id.ResourceGroup, id.ManagedInstanceName, parameters) + if err != nil { + return fmt.Errorf("creating/updating %q: %+v", id, err) + } + + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("waiting for creation/update of %q: %+v", id, err) + } + + if aadOnlyAuthentictionsEnabled := d.Get("azuread_authentication_only").(bool); aadOnlyAuthentictionsEnabled { + aadOnlyAuthentictionsParams := sql.ManagedInstanceAzureADOnlyAuthentication{ + ManagedInstanceAzureADOnlyAuthProperties: &sql.ManagedInstanceAzureADOnlyAuthProperties{ + AzureADOnlyAuthentication: utils.Bool(aadOnlyAuthentictionsEnabled), + }, + } + aadOnlyEnabledFuture, err := aadOnlyAuthentictionsClient.CreateOrUpdate(ctx, id.ResourceGroup, id.ManagedInstanceName, aadOnlyAuthentictionsParams) + if err != nil { + return fmt.Errorf("setting AAD only authentication for %s: %+v", id.String(), err) + } + + if err = aadOnlyEnabledFuture.WaitForCompletionRef(ctx, aadOnlyAuthentictionsClient.Client); err != nil { + return fmt.Errorf("waiting for setting of AAD only authentication for %s: %+v", id.String(), err) + } + } + + d.SetId(id.ID()) + + return nil +} + +func resourceSqlManagedInstanceActiveDirectoryAdministratorRead(d *pluginsdk.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Sql.ManagedInstanceAdministratorsClient + aadOnlyAuthentictionsClient := meta.(*clients.Client).Sql.ManagedInstanceAzureADOnlyAuthenticationsClient + ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := parse.ManagedInstanceAzureActiveDirectoryAdministratorID(d.Id()) + if err != nil { + return err + } + + resp, err := client.Get(ctx, id.ResourceGroup, id.ManagedInstanceName) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + log.Printf("[INFO] %q was not found - removing from state", id) + d.SetId("") + return nil + } + + return fmt.Errorf("retrieving %q: %+v", id, err) + } + + d.Set("managed_instance_name", id.ManagedInstanceName) + d.Set("resource_group_name", id.ResourceGroup) + d.Set("login", resp.Login) + d.Set("object_id", resp.Sid.String()) + d.Set("tenant_id", resp.TenantID.String()) + + respAadOnly, err := aadOnlyAuthentictionsClient.Get(ctx, id.ResourceGroup, id.ManagedInstanceName) + if err != nil { + return fmt.Errorf("reading AAD only authentication for %s: %+v", id.String(), err) + } + aadOnly := false + if authProps := respAadOnly.ManagedInstanceAzureADOnlyAuthProperties; authProps != nil && authProps.AzureADOnlyAuthentication != nil { + aadOnly = *authProps.AzureADOnlyAuthentication + } + d.Set("azuread_authentication_only", aadOnly) + + return nil +} + +func resourceSqlManagedInstanceActiveDirectoryAdministratorDelete(d *pluginsdk.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Sql.ManagedInstanceAdministratorsClient + ctx, cancel := timeouts.ForDelete(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := parse.ManagedInstanceAzureActiveDirectoryAdministratorID(d.Id()) + if err != nil { + return err + } + + future, err := client.Delete(ctx, id.ResourceGroup, id.ManagedInstanceName) + if err != nil { + return fmt.Errorf("deleting %q: %+v", id, err) + } + if err := future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("waiting for deletion of %q: %+v", id, err) + } + + return nil +} diff --git a/internal/services/sql/sql_managed_instance_administrator_resource_test.go b/internal/services/sql/sql_managed_instance_administrator_resource_test.go new file mode 100644 index 000000000000..1c7be9ed9619 --- /dev/null +++ b/internal/services/sql/sql_managed_instance_administrator_resource_test.go @@ -0,0 +1,151 @@ +package sql_test + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance" + "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance/check" + "github.com/hashicorp/terraform-provider-azurerm/internal/clients" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/sql/parse" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" + "github.com/hashicorp/terraform-provider-azurerm/utils" +) + +type SqlMiAdministratorResource struct{} + +func TestAccSqlMiAdministrator_basic(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_sql_managed_instance_active_directory_administrator", "test") + r := SqlMiAdministratorResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.template(data), + }, + { + PreConfig: func() { time.Sleep(5 * time.Minute) }, + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.basicWithAadAuthOnlyEqualTo(data, true), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.basicWithAadAuthOnlyEqualTo(data, false), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccSqlMiAdministrator_requiresImport(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_sql_managed_instance_active_directory_administrator", "test") + r := SqlMiAdministratorResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.template(data), + }, + { + PreConfig: func() { time.Sleep(5 * time.Minute) }, + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.RequiresImportErrorStep(r.requiresImport), + }) +} + +func (r SqlMiAdministratorResource) Exists(ctx context.Context, client *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { + id, err := parse.ManagedInstanceAzureActiveDirectoryAdministratorID(state.ID) + if err != nil { + return nil, err + } + + resp, err := client.Sql.ManagedInstanceAdministratorsClient.Get(ctx, id.ResourceGroup, id.ManagedInstanceName) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + return utils.Bool(false), nil + } + return nil, fmt.Errorf("retrieving %q: %+v", id, err) + } + return utils.Bool(true), nil +} + +func (r SqlMiAdministratorResource) basic(data acceptance.TestData) string { + return fmt.Sprintf(` +%s + +resource "azurerm_sql_managed_instance_active_directory_administrator" "test" { + managed_instance_name = azurerm_sql_managed_instance.test.name + resource_group_name = azurerm_resource_group.test.name + login = data.azuread_service_principal.test.display_name + tenant_id = data.azurerm_client_config.current.tenant_id + object_id = data.azurerm_client_config.current.client_id + + depends_on = [azuread_directory_role_member.test] +} +`, r.template(data)) +} + +func (r SqlMiAdministratorResource) basicWithAadAuthOnlyEqualTo(data acceptance.TestData, aadAuthOnly bool) string { + return fmt.Sprintf(` +%s + +resource "azurerm_sql_managed_instance_active_directory_administrator" "test" { + managed_instance_name = azurerm_sql_managed_instance.test.name + resource_group_name = azurerm_resource_group.test.name + login = data.azuread_service_principal.test.display_name + tenant_id = data.azurerm_client_config.current.tenant_id + object_id = data.azurerm_client_config.current.client_id + azuread_authentication_only = %t +} +`, r.template(data), aadAuthOnly) +} + +func (r SqlMiAdministratorResource) requiresImport(data acceptance.TestData) string { + return fmt.Sprintf(` +%s + +resource "azurerm_sql_managed_instance_active_directory_administrator" "import" { + managed_instance_name = azurerm_sql_managed_instance_active_directory_administrator.test.managed_instance_name + resource_group_name = azurerm_sql_managed_instance_active_directory_administrator.test.resource_group_name + login = azurerm_sql_managed_instance_active_directory_administrator.test.login + tenant_id = azurerm_sql_managed_instance_active_directory_administrator.test.tenant_id + object_id = azurerm_sql_managed_instance_active_directory_administrator.test.object_id +} +`, r.basic(data)) +} + +func (r SqlMiAdministratorResource) template(data acceptance.TestData) string { + return fmt.Sprintf(` +%s + +resource "azuread_directory_role" "reader" { + display_name = "Directory Readers" +} + +data "azurerm_client_config" "current" {} + +data "azuread_service_principal" "test" { + object_id = data.azurerm_client_config.current.object_id +} + +resource "azuread_directory_role_member" "test" { + role_object_id = azuread_directory_role.reader.object_id + member_object_id = azurerm_sql_managed_instance.test.identity.0.principal_id +} +`, SqlManagedInstanceResource{}.identity(data)) +} diff --git a/internal/services/sql/validate/managed_instance_azure_active_directory_administrator_id.go b/internal/services/sql/validate/managed_instance_azure_active_directory_administrator_id.go new file mode 100644 index 000000000000..a0abc6a8cb72 --- /dev/null +++ b/internal/services/sql/validate/managed_instance_azure_active_directory_administrator_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/hashicorp/terraform-provider-azurerm/internal/services/sql/parse" +) + +func ManagedInstanceAzureActiveDirectoryAdministratorID(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.ManagedInstanceAzureActiveDirectoryAdministratorID(v); err != nil { + errors = append(errors, err) + } + + return +} diff --git a/internal/services/sql/validate/managed_instance_azure_active_directory_administrator_id_test.go b/internal/services/sql/validate/managed_instance_azure_active_directory_administrator_id_test.go new file mode 100644 index 000000000000..46cb8611c99f --- /dev/null +++ b/internal/services/sql/validate/managed_instance_azure_active_directory_administrator_id_test.go @@ -0,0 +1,88 @@ +package validate + +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + +import "testing" + +func TestManagedInstanceAzureActiveDirectoryAdministratorID(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 ManagedInstanceName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Sql/", + Valid: false, + }, + + { + // missing value for ManagedInstanceName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Sql/managedInstances/", + Valid: false, + }, + + { + // missing AdministratorName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Sql/managedInstances/instance1/", + Valid: false, + }, + + { + // missing value for AdministratorName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Sql/managedInstances/instance1/administrators/", + Valid: false, + }, + + { + // valid + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Sql/managedInstances/instance1/administrators/activeDirectory", + Valid: true, + }, + + { + // upper-cased + Input: "/SUBSCRIPTIONS/12345678-1234-9876-4563-123456789012/RESOURCEGROUPS/RESGROUP1/PROVIDERS/MICROSOFT.SQL/MANAGEDINSTANCES/INSTANCE1/ADMINISTRATORS/ACTIVEDIRECTORY", + Valid: false, + }, + } + for _, tc := range cases { + t.Logf("[DEBUG] Testing Value %s", tc.Input) + _, errors := ManagedInstanceAzureActiveDirectoryAdministratorID(tc.Input, "test") + valid := len(errors) == 0 + + if tc.Valid != valid { + t.Fatalf("Expected %t but got %t", tc.Valid, valid) + } + } +} diff --git a/website/docs/r/sql_managed_instance_active_directory_administrator.html.markdown b/website/docs/r/sql_managed_instance_active_directory_administrator.html.markdown new file mode 100644 index 000000000000..3970246c8f9b --- /dev/null +++ b/website/docs/r/sql_managed_instance_active_directory_administrator.html.markdown @@ -0,0 +1,80 @@ +--- +subcategory: "Database" +layout: "azurerm" +page_title: "Azure Resource manager: azurerm_sql_managed_instance_active_directory_administrator" +description: |- + Manages an Active Directory administrator on a SQL Managed Instance +--- + +# azurerm_sql_managed_instance_active_directory_administrator + +Allows you to set a user or group as the AD administrator for an Azure SQL Managed Instance + +## Example Usage + +```hcl +resource "azurerm_sql_managed_instance" "example" { + name = "managedsqlinstance" + resource_group_name = azurerm_resource_group.example.name + location = azurerm_resource_group.example.location + administrator_login = "mradministrator" + administrator_login_password = "thisIsDog11" + license_type = "BasePrice" + subnet_id = azurerm_subnet.example.id + sku_name = "GP_Gen5" + vcores = 4 + storage_size_in_gb = 32 + + depends_on = [ + azurerm_subnet_network_security_group_association.example, + azurerm_subnet_route_table_association.example, + ] +} + +data "azurerm_client_config" "current" {} + +resource "azurerm_sql_managed_instance_active_directory_administrator" "example" { + managed_instance_name = azurerm_sql_managed_instance.example.name + resource_group_name = azurerm_resource_group.example.name + login = "sqladmin" + tenant_id = data.azurerm_client_config.current.tenant_id + object_id = data.azurerm_client_config.current.object_id +} +``` + +## Argument Reference + +The following arguments are supported: + +* `managed_instance_name` - (Required) The name of the SQL Managed Instance on which to set the administrator. Changing this forces a new resource to be created. + +* `resource_group_name` - (Required) The name of the resource group for the SQL Managed Instance. Changing this forces a new resource to be created. + +* `login` - (Required) The login name of the principal to set as the Managed Instance administrator + +* `object_id` - (Required) The ID of the principal to set as the Managed Instance administrator + +* `tenant_id` - (Required) The Azure Tenant ID + +## Attributes Reference + +The following attributes are exported: + +* `id` - The ID of the SQL Managed Instance Active Directory Administrator. + +## 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 SQL Active Directory Administrator. +* `update` - (Defaults to 30 minutes) Used when updating the SQL Active Directory Administrator. +* `read` - (Defaults to 5 minutes) Used when retrieving the SQL Active Directory Administrator. +* `delete` - (Defaults to 30 minutes) Used when deleting the SQL Active Directory Administrator. + +## Import + +A SQL Active Directory Administrator can be imported using the `resource id`, e.g. + +```shell +terraform import azurerm_sql_managed_instance_active_directory_administrator.administrator /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myresourcegroup/providers/Microsoft.Sql/managedInstances/mymanagedinstance/administrators/activeDirectory +``` From 1b81ee3c95d24c53d8d71c3f70d6c52da505a9a3 Mon Sep 17 00:00:00 2001 From: Aris van Ommeren Date: Sun, 5 Dec 2021 13:03:50 +0100 Subject: [PATCH 2/3] Fix comments --- ...managed_instance_administrator_resource.go | 26 +++++++++---------- ...tive_directory_administrator.html.markdown | 2 ++ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/internal/services/sql/sql_managed_instance_administrator_resource.go b/internal/services/sql/sql_managed_instance_administrator_resource.go index 872057d35203..b57af9421008 100644 --- a/internal/services/sql/sql_managed_instance_administrator_resource.go +++ b/internal/services/sql/sql_managed_instance_administrator_resource.go @@ -65,7 +65,7 @@ func resourceSqlManagedInstanceAdministrator() *pluginsdk.Resource { "azuread_authentication_only": { Type: pluginsdk.TypeBool, Optional: true, - Computed: true, + Default: false, }, }, } @@ -126,20 +126,18 @@ func resourceSqlManagedInstanceActiveDirectoryAdministratorCreateUpdate(d *plugi return fmt.Errorf("waiting for creation/update of %q: %+v", id, err) } - if aadOnlyAuthentictionsEnabled := d.Get("azuread_authentication_only").(bool); aadOnlyAuthentictionsEnabled { - aadOnlyAuthentictionsParams := sql.ManagedInstanceAzureADOnlyAuthentication{ - ManagedInstanceAzureADOnlyAuthProperties: &sql.ManagedInstanceAzureADOnlyAuthProperties{ - AzureADOnlyAuthentication: utils.Bool(aadOnlyAuthentictionsEnabled), - }, - } - aadOnlyEnabledFuture, err := aadOnlyAuthentictionsClient.CreateOrUpdate(ctx, id.ResourceGroup, id.ManagedInstanceName, aadOnlyAuthentictionsParams) - if err != nil { - return fmt.Errorf("setting AAD only authentication for %s: %+v", id.String(), err) - } + aadOnlyAuthentictionsParams := sql.ManagedInstanceAzureADOnlyAuthentication{ + ManagedInstanceAzureADOnlyAuthProperties: &sql.ManagedInstanceAzureADOnlyAuthProperties{ + AzureADOnlyAuthentication: utils.Bool(d.Get("azuread_authentication_only").(bool)), + }, + } + aadOnlyEnabledFuture, err := aadOnlyAuthentictionsClient.CreateOrUpdate(ctx, id.ResourceGroup, id.ManagedInstanceName, aadOnlyAuthentictionsParams) + if err != nil { + return fmt.Errorf("setting AAD only authentication for %s: %+v", id.String(), err) + } - if err = aadOnlyEnabledFuture.WaitForCompletionRef(ctx, aadOnlyAuthentictionsClient.Client); err != nil { - return fmt.Errorf("waiting for setting of AAD only authentication for %s: %+v", id.String(), err) - } + if err = aadOnlyEnabledFuture.WaitForCompletionRef(ctx, aadOnlyAuthentictionsClient.Client); err != nil { + return fmt.Errorf("waiting for setting of AAD only authentication for %s: %+v", id.String(), err) } d.SetId(id.ID()) diff --git a/website/docs/r/sql_managed_instance_active_directory_administrator.html.markdown b/website/docs/r/sql_managed_instance_active_directory_administrator.html.markdown index 3970246c8f9b..68cffa0b7c77 100644 --- a/website/docs/r/sql_managed_instance_active_directory_administrator.html.markdown +++ b/website/docs/r/sql_managed_instance_active_directory_administrator.html.markdown @@ -56,6 +56,8 @@ The following arguments are supported: * `tenant_id` - (Required) The Azure Tenant ID +* `azuread_authentication_only` - (Optional) Specifies whether only AD Users and administrators can be used to login (`true`) or also local database users (`false`). Defaults to `false`. + ## Attributes Reference The following attributes are exported: From 4da3b80cd50ca136624e666292ffa80ce1e221be Mon Sep 17 00:00:00 2001 From: Aris van Ommeren Date: Sun, 5 Dec 2021 13:05:09 +0100 Subject: [PATCH 3/3] Fix resource IDs --- .../managed_instance_azure_active_directory_administrator.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/services/sql/parse/managed_instance_azure_active_directory_administrator.go b/internal/services/sql/parse/managed_instance_azure_active_directory_administrator.go index 496f241733bf..f189fff9d52c 100644 --- a/internal/services/sql/parse/managed_instance_azure_active_directory_administrator.go +++ b/internal/services/sql/parse/managed_instance_azure_active_directory_administrator.go @@ -6,7 +6,7 @@ import ( "fmt" "strings" - "github.com/hashicorp/terraform-provider-azurerm/helpers/azure" + "github.com/hashicorp/go-azure-helpers/resourcemanager/resourceids" ) type ManagedInstanceAzureActiveDirectoryAdministratorId struct { @@ -42,7 +42,7 @@ func (id ManagedInstanceAzureActiveDirectoryAdministratorId) ID() string { // ManagedInstanceAzureActiveDirectoryAdministratorID parses a ManagedInstanceAzureActiveDirectoryAdministrator ID into an ManagedInstanceAzureActiveDirectoryAdministratorId struct func ManagedInstanceAzureActiveDirectoryAdministratorID(input string) (*ManagedInstanceAzureActiveDirectoryAdministratorId, error) { - id, err := azure.ParseAzureResourceID(input) + id, err := resourceids.ParseAzureResourceID(input) if err != nil { return nil, err }