From 8c42ff099e70eaccb5f32766e16b698dd7291a5c Mon Sep 17 00:00:00 2001 From: Joakim Bakke Hellum Date: Tue, 11 Jun 2019 17:21:16 +0200 Subject: [PATCH 1/9] azurerm_app_service: support for specifying user assigned identities --- azurerm/resource_arm_app_service.go | 47 ++++++++- azurerm/resource_arm_app_service_test.go | 126 +++++++++++++++++++++++ website/docs/r/app_service.html.markdown | 6 +- 3 files changed, 172 insertions(+), 7 deletions(-) diff --git a/azurerm/resource_arm_app_service.go b/azurerm/resource_arm_app_service.go index fe0edf48692f..7231eddb2d7f 100644 --- a/azurerm/resource_arm_app_service.go +++ b/azurerm/resource_arm_app_service.go @@ -43,7 +43,10 @@ func resourceArmAppService() *schema.Resource { Required: true, DiffSuppressFunc: ignoreCaseDiffSuppressFunc, ValidateFunc: validation.StringInSlice([]string{ - "SystemAssigned", + string(web.ManagedServiceIdentityTypeNone), + string(web.ManagedServiceIdentityTypeSystemAssigned), + string(web.ManagedServiceIdentityTypeSystemAssignedUserAssigned), + string(web.ManagedServiceIdentityTypeUserAssigned), }, true), }, "principal_id": { @@ -54,6 +57,16 @@ func resourceArmAppService() *schema.Resource { Type: schema.TypeString, Computed: true, }, + "identity_ids": { + Type: schema.TypeList, + Optional: true, + MinItems: 1, + ForceNew: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.NoZeroValues, + }, + }, }, }, }, @@ -719,12 +732,28 @@ func flattenAppServiceAppSettings(input map[string]*string) map[string]string { } func expandAzureRmAppServiceIdentity(d *schema.ResourceData) *web.ManagedServiceIdentity { - identities := d.Get("identity").([]interface{}) + v := d.Get("identity") + identities := v.([]interface{}) + if len(identities) == 0 { + return nil + } identity := identities[0].(map[string]interface{}) - identityType := identity["type"].(string) - return &web.ManagedServiceIdentity{ - Type: web.ManagedServiceIdentityType(identityType), + identityType := web.ManagedServiceIdentityType(identity["type"].(string)) + + identityIds := make(map[string]*web.ManagedServiceIdentityUserAssignedIdentitiesValue) + for _, id := range identity["identity_ids"].([]interface{}) { + identityIds[id.(string)] = &web.ManagedServiceIdentityUserAssignedIdentitiesValue{} } + + managedServiceIdentity := web.ManagedServiceIdentity{ + Type: identityType, + } + + if managedServiceIdentity.Type == web.ManagedServiceIdentityTypeUserAssigned || managedServiceIdentity.Type == web.ManagedServiceIdentityTypeSystemAssignedUserAssigned { + managedServiceIdentity.UserAssignedIdentities = identityIds + } + + return &managedServiceIdentity } func flattenAzureRmAppServiceMachineIdentity(identity *web.ManagedServiceIdentity) []interface{} { @@ -742,6 +771,14 @@ func flattenAzureRmAppServiceMachineIdentity(identity *web.ManagedServiceIdentit result["tenant_id"] = *identity.TenantID } + identityIds := make([]string, 0) + if identity.UserAssignedIdentities != nil { + for key := range identity.UserAssignedIdentities { + identityIds = append(identityIds, key) + } + } + result["identity_ids"] = identityIds + return []interface{}{result} } diff --git a/azurerm/resource_arm_app_service_test.go b/azurerm/resource_arm_app_service_test.go index 6ff0da0099d1..aefa03346375 100644 --- a/azurerm/resource_arm_app_service_test.go +++ b/azurerm/resource_arm_app_service_test.go @@ -518,6 +518,56 @@ func TestAccAzureRMAppService_updateResourceByEnablingManageServiceIdentity(t *t }) } +func TestAccAzureRMAppService_userAssignedIdentity(t *testing.T) { + + resourceName := "azurerm_app_service.test" + ri := tf.AccRandTimeInt() + config := testAccAzureRMAppService_userAssignedIdentity(ri, testLocation()) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMAppServiceDestroy, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMAppServiceExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "identity.0.type", "UserAssigned"), + resource.TestCheckResourceAttr(resourceName, "identity.0.identity_ids.#", "1"), + resource.TestCheckResourceAttr(resourceName, "identity.0.principal_id", ""), + resource.TestCheckResourceAttr(resourceName, "identity.0.tenant_id", ""), + ), + }, + }, + }) +} + +func TestAccAzureRMAppService_multipleAssignedIdentities(t *testing.T) { + + resourceName := "azurerm_app_service.test" + ri := tf.AccRandTimeInt() + config := testAccAzureRMAppService_multipleAssignedIdentities(ri, testLocation()) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMAppServiceDestroy, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMAppServiceExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "identity.0.type", "SystemAssigned, UserAssigned"), + resource.TestCheckResourceAttr(resourceName, "identity.0.identity_ids.#", "1"), + resource.TestMatchResourceAttr(resourceName, "identity.0.principal_id", validate.UUIDRegExp), + resource.TestMatchResourceAttr(resourceName, "identity.0.tenant_id", validate.UUIDRegExp), + ), + }, + }, + }) +} + func TestAccAzureRMAppService_clientAffinityUpdate(t *testing.T) { resourceName := "azurerm_app_service.test" ri := tf.AccRandTimeInt() @@ -2409,6 +2459,82 @@ resource "azurerm_app_service" "test" { `, rInt, location, rInt, rInt) } +func testAccAzureRMAppService_userAssignedIdentity(rInt int, location string) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_user_assigned_identity" "test" { + name = "acct-%d" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "${azurerm_resource_group.test.location}" +} + +resource "azurerm_app_service_plan" "test" { + name = "acctestASP-%d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + + sku { + tier = "Standard" + size = "S1" + } +} + +resource "azurerm_app_service" "test" { + name = "acctestAS-%d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + app_service_plan_id = "${azurerm_app_service_plan.test.id}" + + identity { + type = "UserAssigned" + identity_ids = ["${azurerm_user_assigned_identity.test.id}"] + } +} +`, rInt, location, rInt, rInt, rInt) +} + +func testAccAzureRMAppService_multipleAssignedIdentities(rInt int, location string) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_user_assigned_identity" "test" { + name = "acct-%d" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "${azurerm_resource_group.test.location}" +} + +resource "azurerm_app_service_plan" "test" { + name = "acctestASP-%d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + + sku { + tier = "Standard" + size = "S1" + } +} + +resource "azurerm_app_service" "test" { + name = "acctestAS-%d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + app_service_plan_id = "${azurerm_app_service_plan.test.id}" + + identity { + type = "SystemAssigned, UserAssigned" + identity_ids = ["${azurerm_user_assigned_identity.test.id}"] + } +} +`, rInt, location, rInt, rInt, rInt) +} + func testAccAzureRMAppService_connectionStrings(rInt int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/website/docs/r/app_service.html.markdown b/website/docs/r/app_service.html.markdown index 8ee41a678902..5406548adfaf 100644 --- a/website/docs/r/app_service.html.markdown +++ b/website/docs/r/app_service.html.markdown @@ -123,9 +123,11 @@ A `connection_string` block supports the following: A `identity` block supports the following: -* `type` - (Required) Specifies the identity type of the App Service. At this time the only allowed value is `SystemAssigned`. +* `type` - (Required) Specifies the identity type of the App Service. Possible values are `SystemAssigned` (where Azure will generate a Service Principal for you), `UserAssigned` where you can specify the Service Principal IDs in the `identity_ids` field, and `SystemAssigned, UserAssigned` which assigns both a system managed identity as well as the specified user assigned identities. -~> The assigned `principal_id` and `tenant_id` can be retrieved after the App Service has been created. More details are available below. +~> **NOTE:** When `type` is set to `SystemAssigned`, The assigned `principal_id` and `tenant_id` can be retrieved after the App Service has been created. More details are available below. + +* `identity_ids` - (Optional) Specifies a list of user managed identity ids to be assigned. Required if `type` is `UserAssigned`. --- From f0004e1e06b46322f37cd142146fcfc489fd2ddc Mon Sep 17 00:00:00 2001 From: Joakim Bakke Hellum Date: Tue, 11 Jun 2019 18:03:27 +0200 Subject: [PATCH 2/9] Fix copy-paste error in schema --- azurerm/resource_arm_app_service.go | 1 - 1 file changed, 1 deletion(-) diff --git a/azurerm/resource_arm_app_service.go b/azurerm/resource_arm_app_service.go index 7231eddb2d7f..edf37bd5b650 100644 --- a/azurerm/resource_arm_app_service.go +++ b/azurerm/resource_arm_app_service.go @@ -61,7 +61,6 @@ func resourceArmAppService() *schema.Resource { Type: schema.TypeList, Optional: true, MinItems: 1, - ForceNew: true, Elem: &schema.Schema{ Type: schema.TypeString, ValidateFunc: validation.NoZeroValues, From fb3d058dd3abad6bf7c862c48f8903d2ca62782f Mon Sep 17 00:00:00 2001 From: Joakim Bakke Hellum Date: Fri, 14 Jun 2019 00:41:57 +0200 Subject: [PATCH 3/9] Address code review comments --- azurerm/helpers/azure/app_service.go | 90 +++++++++++ azurerm/resource_arm_app_service.go | 100 +------------ azurerm/resource_arm_app_service_slot.go | 30 +--- azurerm/resource_arm_app_service_slot_test.go | 140 ++++++++++++++++++ azurerm/resource_arm_app_service_test.go | 8 +- website/docs/r/app_service_slot.html.markdown | 6 +- 6 files changed, 247 insertions(+), 127 deletions(-) diff --git a/azurerm/helpers/azure/app_service.go b/azurerm/helpers/azure/app_service.go index 44e49775a156..1e1d11ef8a46 100644 --- a/azurerm/helpers/azure/app_service.go +++ b/azurerm/helpers/azure/app_service.go @@ -208,6 +208,46 @@ func SchemaAppServiceAuthSettings() *schema.Schema { } } +func SchemaAppServiceIdentity() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "type": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + string(web.ManagedServiceIdentityTypeNone), + string(web.ManagedServiceIdentityTypeSystemAssigned), + string(web.ManagedServiceIdentityTypeSystemAssignedUserAssigned), + string(web.ManagedServiceIdentityTypeUserAssigned), + }, true), + }, + "principal_id": { + Type: schema.TypeString, + Computed: true, + }, + "tenant_id": { + Type: schema.TypeString, + Computed: true, + }, + "identity_ids": { + Type: schema.TypeList, + Optional: true, + MinItems: 1, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.NoZeroValues, + }, + }, + }, + }, + } +} + func SchemaAppServiceSiteConfig() *schema.Schema { return &schema.Schema{ Type: schema.TypeList, @@ -1193,6 +1233,56 @@ func ExpandAppServiceLogs(input interface{}) web.SiteLogsConfigProperties { return logs } +func ExpandAppServiceIdentity(d *schema.ResourceData) *web.ManagedServiceIdentity { + identities := d.Get("identity").([]interface{}) + if len(identities) == 0 { + return nil + } + identity := identities[0].(map[string]interface{}) + identityType := web.ManagedServiceIdentityType(identity["type"].(string)) + + identityIds := make(map[string]*web.ManagedServiceIdentityUserAssignedIdentitiesValue) + for _, id := range identity["identity_ids"].([]interface{}) { + identityIds[id.(string)] = &web.ManagedServiceIdentityUserAssignedIdentitiesValue{} + } + + managedServiceIdentity := web.ManagedServiceIdentity{ + Type: identityType, + } + + if managedServiceIdentity.Type == web.ManagedServiceIdentityTypeUserAssigned || managedServiceIdentity.Type == web.ManagedServiceIdentityTypeSystemAssignedUserAssigned { + managedServiceIdentity.UserAssignedIdentities = identityIds + } + + return &managedServiceIdentity +} + +func FlattenAppServiceIdentity(identity *web.ManagedServiceIdentity) []interface{} { + if identity == nil { + return make([]interface{}, 0) + } + + result := make(map[string]interface{}) + result["type"] = string(identity.Type) + + if identity.PrincipalID != nil { + result["principal_id"] = *identity.PrincipalID + } + if identity.TenantID != nil { + result["tenant_id"] = *identity.TenantID + } + + identityIds := make([]string, 0) + if identity.UserAssignedIdentities != nil { + for key := range identity.UserAssignedIdentities { + identityIds = append(identityIds, key) + } + } + result["identity_ids"] = identityIds + + return []interface{}{result} +} + func ExpandAppServiceSiteConfig(input interface{}) web.SiteConfig { configs := input.([]interface{}) siteConfig := web.SiteConfig{} diff --git a/azurerm/resource_arm_app_service.go b/azurerm/resource_arm_app_service.go index edf37bd5b650..3f1208ce079f 100644 --- a/azurerm/resource_arm_app_service.go +++ b/azurerm/resource_arm_app_service.go @@ -31,44 +31,7 @@ func resourceArmAppService() *schema.Resource { ValidateFunc: validateAppServiceName, }, - "identity": { - Type: schema.TypeList, - Optional: true, - Computed: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "type": { - Type: schema.TypeString, - Required: true, - DiffSuppressFunc: ignoreCaseDiffSuppressFunc, - ValidateFunc: validation.StringInSlice([]string{ - string(web.ManagedServiceIdentityTypeNone), - string(web.ManagedServiceIdentityTypeSystemAssigned), - string(web.ManagedServiceIdentityTypeSystemAssignedUserAssigned), - string(web.ManagedServiceIdentityTypeUserAssigned), - }, true), - }, - "principal_id": { - Type: schema.TypeString, - Computed: true, - }, - "tenant_id": { - Type: schema.TypeString, - Computed: true, - }, - "identity_ids": { - Type: schema.TypeList, - Optional: true, - MinItems: 1, - Elem: &schema.Schema{ - Type: schema.TypeString, - ValidateFunc: validation.NoZeroValues, - }, - }, - }, - }, - }, + "identity": azure.SchemaAppServiceIdentity(), "resource_group_name": azure.SchemaResourceGroupName(), @@ -263,7 +226,7 @@ func resourceArmAppServiceCreate(d *schema.ResourceData, meta interface{}) error } if _, ok := d.GetOk("identity"); ok { - appServiceIdentity := expandAzureRmAppServiceIdentity(d) + appServiceIdentity := azure.ExpandAppServiceIdentity(d) siteEnvelope.Identity = appServiceIdentity } @@ -301,7 +264,7 @@ func resourceArmAppServiceCreate(d *schema.ResourceData, meta interface{}) error authSettings := azure.ExpandAppServiceAuthSettings(authSettingsRaw) auth := web.SiteAuthSettings{ - ID: read.ID, + ID: read.ID, SiteAuthSettingsProperties: &authSettings} if _, err := client.UpdateAuthSettings(ctx, resGroup, name, auth); err != nil { @@ -382,7 +345,7 @@ func resourceArmAppServiceUpdate(d *schema.ResourceData, meta interface{}) error authSettingsProperties := azure.ExpandAppServiceAuthSettings(authSettingsRaw) id := d.Id() authSettings := web.SiteAuthSettings{ - ID: &id, + ID: &id, SiteAuthSettingsProperties: &authSettingsProperties, } @@ -461,7 +424,7 @@ func resourceArmAppServiceUpdate(d *schema.ResourceData, meta interface{}) error return fmt.Errorf("Error getting configuration for App Service %q: %+v", name, err) } - appServiceIdentity := expandAzureRmAppServiceIdentity(d) + appServiceIdentity := azure.ExpandAppServiceIdentity(d) site.Identity = appServiceIdentity future, err := client.CreateOrUpdate(ctx, resGroup, name, site) @@ -618,7 +581,7 @@ func resourceArmAppServiceRead(d *schema.ResourceData, meta interface{}) error { return err } - identity := flattenAzureRmAppServiceMachineIdentity(resp.Identity) + identity := azure.FlattenAppServiceIdentity(resp.Identity) if err := d.Set("identity", identity); err != nil { return err } @@ -730,57 +693,6 @@ func flattenAppServiceAppSettings(input map[string]*string) map[string]string { return output } -func expandAzureRmAppServiceIdentity(d *schema.ResourceData) *web.ManagedServiceIdentity { - v := d.Get("identity") - identities := v.([]interface{}) - if len(identities) == 0 { - return nil - } - identity := identities[0].(map[string]interface{}) - identityType := web.ManagedServiceIdentityType(identity["type"].(string)) - - identityIds := make(map[string]*web.ManagedServiceIdentityUserAssignedIdentitiesValue) - for _, id := range identity["identity_ids"].([]interface{}) { - identityIds[id.(string)] = &web.ManagedServiceIdentityUserAssignedIdentitiesValue{} - } - - managedServiceIdentity := web.ManagedServiceIdentity{ - Type: identityType, - } - - if managedServiceIdentity.Type == web.ManagedServiceIdentityTypeUserAssigned || managedServiceIdentity.Type == web.ManagedServiceIdentityTypeSystemAssignedUserAssigned { - managedServiceIdentity.UserAssignedIdentities = identityIds - } - - return &managedServiceIdentity -} - -func flattenAzureRmAppServiceMachineIdentity(identity *web.ManagedServiceIdentity) []interface{} { - if identity == nil { - return make([]interface{}, 0) - } - - result := make(map[string]interface{}) - result["type"] = string(identity.Type) - - if identity.PrincipalID != nil { - result["principal_id"] = *identity.PrincipalID - } - if identity.TenantID != nil { - result["tenant_id"] = *identity.TenantID - } - - identityIds := make([]string, 0) - if identity.UserAssignedIdentities != nil { - for key := range identity.UserAssignedIdentities { - identityIds = append(identityIds, key) - } - } - result["identity_ids"] = identityIds - - return []interface{}{result} -} - func validateAppServiceName(v interface{}, k string) (warnings []string, errors []error) { value := v.(string) diff --git a/azurerm/resource_arm_app_service_slot.go b/azurerm/resource_arm_app_service_slot.go index 32e6b8ee8c96..6757faea682b 100644 --- a/azurerm/resource_arm_app_service_slot.go +++ b/azurerm/resource_arm_app_service_slot.go @@ -34,31 +34,7 @@ func resourceArmAppServiceSlot() *schema.Resource { "location": azure.SchemaLocation(), - "identity": { - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "type": { - Type: schema.TypeString, - Required: true, - DiffSuppressFunc: ignoreCaseDiffSuppressFunc, - ValidateFunc: validation.StringInSlice([]string{ - "SystemAssigned", - }, true), - }, - "principal_id": { - Type: schema.TypeString, - Computed: true, - }, - "tenant_id": { - Type: schema.TypeString, - Computed: true, - }, - }, - }, - }, + "identity": azure.SchemaAppServiceIdentity(), "app_service_name": { Type: schema.TypeString, @@ -206,7 +182,7 @@ func resourceArmAppServiceSlotCreateUpdate(d *schema.ResourceData, meta interfac } if _, ok := d.GetOk("identity"); ok { - appServiceIdentity := expandAzureRmAppServiceIdentity(d) + appServiceIdentity := azure.ExpandAppServiceIdentity(d) siteEnvelope.Identity = appServiceIdentity } @@ -362,7 +338,7 @@ func resourceArmAppServiceSlotRead(d *schema.ResourceData, meta interface{}) err return err } - identity := flattenAzureRmAppServiceMachineIdentity(resp.Identity) + identity := azure.FlattenAppServiceIdentity(resp.Identity) if err := d.Set("identity", identity); err != nil { return err } diff --git a/azurerm/resource_arm_app_service_slot_test.go b/azurerm/resource_arm_app_service_slot_test.go index bca4d40fc087..81f590c756c5 100644 --- a/azurerm/resource_arm_app_service_slot_test.go +++ b/azurerm/resource_arm_app_service_slot_test.go @@ -962,6 +962,54 @@ func TestAccAzureRMAppServiceSlot_enableManageServiceIdentity(t *testing.T) { }) } +func TestAccAzureRMAppServiceSlot_userAssignedIdentity(t *testing.T) { + resourceName := "azurerm_app_service_slot.test" + ri := tf.AccRandTimeInt() + config := testAccAzureRMAppServiceSlot_userAssignedIdentity(ri, testLocation()) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMAppServiceSlotDestroy, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMAppServiceSlotExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "identity.0.type", "UserAssigned"), + resource.TestCheckResourceAttr(resourceName, "identity.0.identity_ids.#", "1"), + resource.TestCheckResourceAttr(resourceName, "identity.0.principal_id", ""), + resource.TestCheckResourceAttr(resourceName, "identity.0.tenant_id", ""), + ), + }, + }, + }) +} + +func TestAccAzureRMAppServiceSlot_multipleAssignedIdentities(t *testing.T) { + resourceName := "azurerm_app_service_slot.test" + ri := tf.AccRandTimeInt() + config := testAccAzureRMAppServiceSlot_multipleAssignedIdentities(ri, testLocation()) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMAppServiceSlotDestroy, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMAppServiceSlotExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "identity.0.type", "SystemAssigned, UserAssigned"), + resource.TestCheckResourceAttr(resourceName, "identity.0.identity_ids.#", "1"), + resource.TestMatchResourceAttr(resourceName, "identity.0.principal_id", validate.UUIDRegExp), + resource.TestMatchResourceAttr(resourceName, "identity.0.tenant_id", validate.UUIDRegExp), + ), + }, + }, + }) +} + func TestAccAzureRMAppServiceSlot_minTls(t *testing.T) { resourceName := "azurerm_app_service_slot.test" ri := tf.AccRandTimeInt() @@ -2277,6 +2325,98 @@ resource "azurerm_app_service_slot" "test" { `, rInt, location, rInt, rInt, rInt) } +func testAccAzureRMAppServiceSlot_userAssignedIdentity(rInt int, location string) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_user_assigned_identity" "test" { + name = "acct-%d" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "${azurerm_resource_group.test.location}" +} + +resource "azurerm_app_service_plan" "test" { + name = "acctestASP-%d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + + sku { + tier = "Standard" + size = "S1" + } +} + +resource "azurerm_app_service" "test" { + name = "acctestAS-%d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + app_service_plan_id = "${azurerm_app_service_plan.test.id}" +} + +resource "azurerm_app_service_slot" "test" { + name = "acctestASSlot-%d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + app_service_plan_id = "${azurerm_app_service_plan.test.id}" + app_service_name = "${azurerm_app_service.test.name}" + + identity { + type = "UserAssigned" + identity_ids = ["${azurerm_user_assigned_identity.test.id}"] + } +} +`, rInt, location, rInt, rInt, rInt, rInt) +} + +func testAccAzureRMAppServiceSlot_multipleAssignedIdentities(rInt int, location string) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_user_assigned_identity" "test" { + name = "acct-%d" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "${azurerm_resource_group.test.location}" +} + +resource "azurerm_app_service_plan" "test" { + name = "acctestASP-%d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + + sku { + tier = "Standard" + size = "S1" + } +} + +resource "azurerm_app_service" "test" { + name = "acctestAS-%d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + app_service_plan_id = "${azurerm_app_service_plan.test.id}" +} + +resource "azurerm_app_service_slot" "test" { + name = "acctestASSlot-%d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + app_service_plan_id = "${azurerm_app_service_plan.test.id}" + app_service_name = "${azurerm_app_service.test.name}" + + identity { + type = "SystemAssigned, UserAssigned" + identity_ids = ["${azurerm_user_assigned_identity.test.id}"] + } +} +`, rInt, location, rInt, rInt, rInt, rInt) +} + func testAccAzureRMAppServiceSlot_minTls(rInt int, location string, tlsVersion string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/azurerm/resource_arm_app_service_test.go b/azurerm/resource_arm_app_service_test.go index aefa03346375..e1fdc346ba38 100644 --- a/azurerm/resource_arm_app_service_test.go +++ b/azurerm/resource_arm_app_service_test.go @@ -2490,8 +2490,8 @@ resource "azurerm_app_service" "test" { app_service_plan_id = "${azurerm_app_service_plan.test.id}" identity { - type = "UserAssigned" - identity_ids = ["${azurerm_user_assigned_identity.test.id}"] + type = "UserAssigned" + identity_ids = ["${azurerm_user_assigned_identity.test.id}"] } } `, rInt, location, rInt, rInt, rInt) @@ -2528,8 +2528,8 @@ resource "azurerm_app_service" "test" { app_service_plan_id = "${azurerm_app_service_plan.test.id}" identity { - type = "SystemAssigned, UserAssigned" - identity_ids = ["${azurerm_user_assigned_identity.test.id}"] + type = "SystemAssigned, UserAssigned" + identity_ids = ["${azurerm_user_assigned_identity.test.id}"] } } `, rInt, location, rInt, rInt, rInt) diff --git a/website/docs/r/app_service_slot.html.markdown b/website/docs/r/app_service_slot.html.markdown index 0ed68b25a02a..22212cab844f 100644 --- a/website/docs/r/app_service_slot.html.markdown +++ b/website/docs/r/app_service_slot.html.markdown @@ -240,9 +240,11 @@ A `cors` block supports the following: A `identity` block supports the following: -* `type` - (Required) Specifies the identity type of the App Service. At this time the only allowed value is `SystemAssigned`. +* `type` - (Required) Specifies the identity type of the App Service. Possible values are `SystemAssigned` (where Azure will generate a Service Principal for you), `UserAssigned` where you can specify the Service Principal IDs in the `identity_ids` field, and `SystemAssigned, UserAssigned` which assigns both a system managed identity as well as the specified user assigned identities. -~> The assigned `principal_id` and `tenant_id` can be retrieved after the App Service Slot has been created. +~> **NOTE:** When `type` is set to `SystemAssigned`, The assigned `principal_id` and `tenant_id` can be retrieved after the App Service has been created. More details are available below. + +* `identity_ids` - (Optional) Specifies a list of user managed identity ids to be assigned. Required if `type` is `UserAssigned`. --- From f3e4835e66cd607f185d21958b91c9f90b5c0709 Mon Sep 17 00:00:00 2001 From: Joakim Bakke Hellum Date: Fri, 14 Jun 2019 01:05:27 +0200 Subject: [PATCH 4/9] Fix go lint error --- azurerm/resource_arm_app_service.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/azurerm/resource_arm_app_service.go b/azurerm/resource_arm_app_service.go index 3f1208ce079f..85d4429a8d1e 100644 --- a/azurerm/resource_arm_app_service.go +++ b/azurerm/resource_arm_app_service.go @@ -264,7 +264,7 @@ func resourceArmAppServiceCreate(d *schema.ResourceData, meta interface{}) error authSettings := azure.ExpandAppServiceAuthSettings(authSettingsRaw) auth := web.SiteAuthSettings{ - ID: read.ID, + ID: read.ID, SiteAuthSettingsProperties: &authSettings} if _, err := client.UpdateAuthSettings(ctx, resGroup, name, auth); err != nil { @@ -345,7 +345,7 @@ func resourceArmAppServiceUpdate(d *schema.ResourceData, meta interface{}) error authSettingsProperties := azure.ExpandAppServiceAuthSettings(authSettingsRaw) id := d.Id() authSettings := web.SiteAuthSettings{ - ID: &id, + ID: &id, SiteAuthSettingsProperties: &authSettingsProperties, } From 62d7705a2703ad07454d6fa27ec3ab79b18e89c7 Mon Sep 17 00:00:00 2001 From: Joakim Bakke Hellum Date: Fri, 14 Jun 2019 03:44:38 +0200 Subject: [PATCH 5/9] Add missing schema behavior --- azurerm/helpers/azure/app_service.go | 1 + 1 file changed, 1 insertion(+) diff --git a/azurerm/helpers/azure/app_service.go b/azurerm/helpers/azure/app_service.go index 1e1d11ef8a46..bb9f6f13be00 100644 --- a/azurerm/helpers/azure/app_service.go +++ b/azurerm/helpers/azure/app_service.go @@ -225,6 +225,7 @@ func SchemaAppServiceIdentity() *schema.Schema { string(web.ManagedServiceIdentityTypeSystemAssignedUserAssigned), string(web.ManagedServiceIdentityTypeUserAssigned), }, true), + DiffSuppressFunc: suppress.CaseDifference, }, "principal_id": { Type: schema.TypeString, From ffa3401bcfb243b6ca19cc611f657b862822760d Mon Sep 17 00:00:00 2001 From: Joakim Bakke Hellum Date: Fri, 14 Jun 2019 04:50:52 +0200 Subject: [PATCH 6/9] Fix schema for app service slot --- azurerm/resource_arm_app_service_slot.go | 40 +++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/azurerm/resource_arm_app_service_slot.go b/azurerm/resource_arm_app_service_slot.go index 6757faea682b..637c93417887 100644 --- a/azurerm/resource_arm_app_service_slot.go +++ b/azurerm/resource_arm_app_service_slot.go @@ -8,6 +8,7 @@ import ( "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/validation" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/suppress" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) @@ -34,7 +35,44 @@ func resourceArmAppServiceSlot() *schema.Resource { "location": azure.SchemaLocation(), - "identity": azure.SchemaAppServiceIdentity(), + "identity": { + Type: schema.TypeList, + Optional: true, + ForceNew: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "type": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + string(web.ManagedServiceIdentityTypeNone), + string(web.ManagedServiceIdentityTypeSystemAssigned), + string(web.ManagedServiceIdentityTypeSystemAssignedUserAssigned), + string(web.ManagedServiceIdentityTypeUserAssigned), + }, true), + DiffSuppressFunc: suppress.CaseDifference, + }, + "principal_id": { + Type: schema.TypeString, + Computed: true, + }, + "tenant_id": { + Type: schema.TypeString, + Computed: true, + }, + "identity_ids": { + Type: schema.TypeList, + Optional: true, + MinItems: 1, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.NoZeroValues, + }, + }, + }, + }, + }, "app_service_name": { Type: schema.TypeString, From 337acb1273fa07664d77d475533a0b4fcd2be6f5 Mon Sep 17 00:00:00 2001 From: Joakim Bakke Hellum Date: Fri, 14 Jun 2019 11:31:55 +0200 Subject: [PATCH 7/9] Updating identity on app service slot should force new --- azurerm/resource_arm_app_service_slot.go | 3 ++- website/docs/r/app_service_slot.html.markdown | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/azurerm/resource_arm_app_service_slot.go b/azurerm/resource_arm_app_service_slot.go index 637c93417887..7dbd365d28dc 100644 --- a/azurerm/resource_arm_app_service_slot.go +++ b/azurerm/resource_arm_app_service_slot.go @@ -38,13 +38,13 @@ func resourceArmAppServiceSlot() *schema.Resource { "identity": { Type: schema.TypeList, Optional: true, - ForceNew: true, MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "type": { Type: schema.TypeString, Required: true, + ForceNew: true, ValidateFunc: validation.StringInSlice([]string{ string(web.ManagedServiceIdentityTypeNone), string(web.ManagedServiceIdentityTypeSystemAssigned), @@ -65,6 +65,7 @@ func resourceArmAppServiceSlot() *schema.Resource { Type: schema.TypeList, Optional: true, MinItems: 1, + ForceNew: true, Elem: &schema.Schema{ Type: schema.TypeString, ValidateFunc: validation.NoZeroValues, diff --git a/website/docs/r/app_service_slot.html.markdown b/website/docs/r/app_service_slot.html.markdown index 22212cab844f..134abca36468 100644 --- a/website/docs/r/app_service_slot.html.markdown +++ b/website/docs/r/app_service_slot.html.markdown @@ -240,11 +240,11 @@ A `cors` block supports the following: A `identity` block supports the following: -* `type` - (Required) Specifies the identity type of the App Service. Possible values are `SystemAssigned` (where Azure will generate a Service Principal for you), `UserAssigned` where you can specify the Service Principal IDs in the `identity_ids` field, and `SystemAssigned, UserAssigned` which assigns both a system managed identity as well as the specified user assigned identities. +* `type` - (Required) Specifies the identity type of the App Service. Possible values are `SystemAssigned` (where Azure will generate a Service Principal for you), `UserAssigned` where you can specify the Service Principal IDs in the `identity_ids` field, and `SystemAssigned, UserAssigned` which assigns both a system managed identity as well as the specified user assigned identities. Changing this forces a new resource to be created. ~> **NOTE:** When `type` is set to `SystemAssigned`, The assigned `principal_id` and `tenant_id` can be retrieved after the App Service has been created. More details are available below. -* `identity_ids` - (Optional) Specifies a list of user managed identity ids to be assigned. Required if `type` is `UserAssigned`. +* `identity_ids` - (Optional) Specifies a list of user managed identity ids to be assigned. Required if `type` is `UserAssigned`. Changing this forces a new resource to be created. --- From ee1dc0ce2faaa1604f79024a7d8d6f9dfca8d621 Mon Sep 17 00:00:00 2001 From: Joakim Bakke Hellum Date: Fri, 14 Jun 2019 13:26:53 +0200 Subject: [PATCH 8/9] Allow identity to be updated on app service slot --- azurerm/resource_arm_app_service_slot.go | 53 +++++-------------- website/docs/r/app_service_slot.html.markdown | 4 +- 2 files changed, 15 insertions(+), 42 deletions(-) diff --git a/azurerm/resource_arm_app_service_slot.go b/azurerm/resource_arm_app_service_slot.go index 7dbd365d28dc..5b5845a4593d 100644 --- a/azurerm/resource_arm_app_service_slot.go +++ b/azurerm/resource_arm_app_service_slot.go @@ -8,7 +8,6 @@ import ( "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/validation" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" - "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/suppress" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) @@ -35,45 +34,7 @@ func resourceArmAppServiceSlot() *schema.Resource { "location": azure.SchemaLocation(), - "identity": { - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "type": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - ValidateFunc: validation.StringInSlice([]string{ - string(web.ManagedServiceIdentityTypeNone), - string(web.ManagedServiceIdentityTypeSystemAssigned), - string(web.ManagedServiceIdentityTypeSystemAssignedUserAssigned), - string(web.ManagedServiceIdentityTypeUserAssigned), - }, true), - DiffSuppressFunc: suppress.CaseDifference, - }, - "principal_id": { - Type: schema.TypeString, - Computed: true, - }, - "tenant_id": { - Type: schema.TypeString, - Computed: true, - }, - "identity_ids": { - Type: schema.TypeList, - Optional: true, - MinItems: 1, - ForceNew: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - ValidateFunc: validation.NoZeroValues, - }, - }, - }, - }, - }, + "identity": azure.SchemaAppServiceIdentity(), "app_service_name": { Type: schema.TypeString, @@ -281,6 +242,18 @@ func resourceArmAppServiceSlotCreateUpdate(d *schema.ResourceData, meta interfac } } + if d.HasChange("identity") { + identity := azure.ExpandAppServiceIdentity(d) + sitePatchResource := web.SitePatchResource{ + ID: utils.String(d.Id()), + Identity: identity, + } + _, err := client.UpdateSlot(ctx, resGroup, appServiceName, sitePatchResource, slot) + if err != nil { + return fmt.Errorf("Error updating Managed Service Identity for App Service App Service Slot %q/%q: %+v", appServiceName, slot, err) + } + } + return resourceArmAppServiceSlotRead(d, meta) } diff --git a/website/docs/r/app_service_slot.html.markdown b/website/docs/r/app_service_slot.html.markdown index 134abca36468..22212cab844f 100644 --- a/website/docs/r/app_service_slot.html.markdown +++ b/website/docs/r/app_service_slot.html.markdown @@ -240,11 +240,11 @@ A `cors` block supports the following: A `identity` block supports the following: -* `type` - (Required) Specifies the identity type of the App Service. Possible values are `SystemAssigned` (where Azure will generate a Service Principal for you), `UserAssigned` where you can specify the Service Principal IDs in the `identity_ids` field, and `SystemAssigned, UserAssigned` which assigns both a system managed identity as well as the specified user assigned identities. Changing this forces a new resource to be created. +* `type` - (Required) Specifies the identity type of the App Service. Possible values are `SystemAssigned` (where Azure will generate a Service Principal for you), `UserAssigned` where you can specify the Service Principal IDs in the `identity_ids` field, and `SystemAssigned, UserAssigned` which assigns both a system managed identity as well as the specified user assigned identities. ~> **NOTE:** When `type` is set to `SystemAssigned`, The assigned `principal_id` and `tenant_id` can be retrieved after the App Service has been created. More details are available below. -* `identity_ids` - (Optional) Specifies a list of user managed identity ids to be assigned. Required if `type` is `UserAssigned`. Changing this forces a new resource to be created. +* `identity_ids` - (Optional) Specifies a list of user managed identity ids to be assigned. Required if `type` is `UserAssigned`. --- From 2eded91d374d03b580e3dae1da53dbcf5dd47dcc Mon Sep 17 00:00:00 2001 From: Joakim Bakke Hellum Date: Fri, 14 Jun 2019 13:37:56 +0200 Subject: [PATCH 9/9] Fix a typo --- azurerm/resource_arm_app_service_slot.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azurerm/resource_arm_app_service_slot.go b/azurerm/resource_arm_app_service_slot.go index 5b5845a4593d..3704040506a3 100644 --- a/azurerm/resource_arm_app_service_slot.go +++ b/azurerm/resource_arm_app_service_slot.go @@ -250,7 +250,7 @@ func resourceArmAppServiceSlotCreateUpdate(d *schema.ResourceData, meta interfac } _, err := client.UpdateSlot(ctx, resGroup, appServiceName, sitePatchResource, slot) if err != nil { - return fmt.Errorf("Error updating Managed Service Identity for App Service App Service Slot %q/%q: %+v", appServiceName, slot, err) + return fmt.Errorf("Error updating Managed Service Identity for App Service Slot %q/%q: %+v", appServiceName, slot, err) } }