Skip to content

Commit

Permalink
azurerm_app_service - support for MSI (#1130)
Browse files Browse the repository at this point in the history
* Add MSI support for App service

* Fix formatting

* Clean up debug log

* Handle client error

* Add MSI documentation

* Refactoring the separate method calls into the update block

* Updating the documentation for consistency purposes

* Fixing a bug where we returned incorrectly
  • Loading branch information
sabbox authored and tombuildsstuff committed May 1, 2018
1 parent adce0ea commit 4caf62b
Show file tree
Hide file tree
Showing 3 changed files with 210 additions and 9 deletions.
86 changes: 86 additions & 0 deletions azurerm/resource_arm_app_service.go
Expand Up @@ -29,6 +29,33 @@ 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{
"SystemAssigned",
}, true),
},
"principal_id": {
Type: schema.TypeString,
Computed: true,
},
"tenant_id": {
Type: schema.TypeString,
Computed: true,
},
},
},
},

"resource_group_name": resourceGroupNameSchema(),

"location": locationSchema(),
Expand Down Expand Up @@ -326,6 +353,11 @@ func resourceArmAppServiceCreate(d *schema.ResourceData, meta interface{}) error
},
}

if _, ok := d.GetOk("identity"); ok {
appServiceIdentity := expandAzureRmAppServiceIdentity(d)
siteEnvelope.Identity = appServiceIdentity
}

if v, ok := d.GetOkExists("client_affinity_enabled"); ok {
enabled := v.(bool)
siteEnvelope.SiteProperties.ClientAffinityEnabled = utils.Bool(enabled)
Expand Down Expand Up @@ -449,6 +481,28 @@ func resourceArmAppServiceUpdate(d *schema.ResourceData, meta interface{}) error
}
}

if d.HasChange("identity") {
site, err := client.Get(ctx, resGroup, name)
if err != nil {
return fmt.Errorf("Error getting configuration for App Service %q: %+v", name, err)
}

appServiceIdentity := expandAzureRmAppServiceIdentity(d)
site.Identity = appServiceIdentity

future, err := client.CreateOrUpdate(ctx, resGroup, name, site)

if err != nil {
return fmt.Errorf("Error updating Managed Service Identity for App Service %q: %+v", name, err)
}

err = future.WaitForCompletion(ctx, client.Client)

if err != nil {
return fmt.Errorf("Error updating Managed Service Identity for App Service %q: %+v", name, err)
}
}

return resourceArmAppServiceRead(d, meta)
}

Expand Down Expand Up @@ -546,6 +600,11 @@ func resourceArmAppServiceRead(d *schema.ResourceData, meta interface{}) error {

flattenAndSetTags(d, resp.Tags)

identity := flattenAzureRmAppServiceMachineIdentity(resp.Identity)
if err := d.Set("identity", identity); err != nil {
return err
}

return nil
}

Expand Down Expand Up @@ -802,6 +861,33 @@ func flattenAppServiceAppSettings(input map[string]*string) map[string]string {
return output
}

func expandAzureRmAppServiceIdentity(d *schema.ResourceData) *web.ManagedServiceIdentity {
identities := d.Get("identity").([]interface{})
identity := identities[0].(map[string]interface{})
identityType := identity["type"].(string)
return &web.ManagedServiceIdentity{
Type: web.ManagedServiceIdentityType(identityType),
}
}

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
}

return []interface{}{result}
}

func validateAppServiceName(v interface{}, k string) (ws []string, es []error) {
value := v.(string)

Expand Down
92 changes: 92 additions & 0 deletions azurerm/resource_arm_app_service_test.go
Expand Up @@ -2,6 +2,7 @@ package azurerm

import (
"fmt"
"regexp"
"testing"

"github.com/hashicorp/terraform/helper/acctest"
Expand Down Expand Up @@ -236,6 +237,67 @@ func TestAccAzureRMAppService_clientAffinityDisabled(t *testing.T) {
})
}

func TestAccAzureRMAppService_enableManageServiceIdentity(t *testing.T) {

resourceName := "azurerm_app_service.test"
ri := acctest.RandInt()
config := testAccAzureRMAppService_mangedServiceIdentity(ri, testLocation())

uuidMatch := regexp.MustCompile("^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-4[a-fA-F0-9]{3}-[8|9|aA|bB][a-fA-F0-9]{3}-[a-fA-F0-9]{12}$")

resource.Test(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"),
resource.TestMatchResourceAttr(resourceName, "identity.0.principal_id", uuidMatch),
resource.TestMatchResourceAttr(resourceName, "identity.0.tenant_id", uuidMatch),
),
},
},
})
}

func TestAccAzureRMAppService_updateResourceByEnablingManageServiceIdentity(t *testing.T) {

resourceName := "azurerm_app_service.test"
ri := acctest.RandInt()

basicResourceNoManagedIdentity := testAccAzureRMAppService_basic(ri, testLocation())
managedIdentityEnabled := testAccAzureRMAppService_mangedServiceIdentity(ri, testLocation())

uuidMatch := regexp.MustCompile("^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-4[a-fA-F0-9]{3}-[8|9|aA|bB][a-fA-F0-9]{3}-[a-fA-F0-9]{12}$")

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testCheckAzureRMAppServiceDestroy,
Steps: []resource.TestStep{
{
Config: basicResourceNoManagedIdentity,
Check: resource.ComposeTestCheckFunc(
testCheckAzureRMAppServiceExists(resourceName),
resource.TestCheckResourceAttr(resourceName, "identity.#", "0"),
),
},
{
Config: managedIdentityEnabled,
Check: resource.ComposeTestCheckFunc(
testCheckAzureRMAppServiceExists(resourceName),
resource.TestCheckResourceAttr(resourceName, "identity.0.type", "SystemAssigned"),
resource.TestMatchResourceAttr(resourceName, "identity.0.principal_id", uuidMatch),
resource.TestMatchResourceAttr(resourceName, "identity.0.tenant_id", uuidMatch),
),
},
},
})
}

func TestAccAzureRMAppService_clientAffinityUpdate(t *testing.T) {
resourceName := "azurerm_app_service.test"
ri := acctest.RandInt()
Expand Down Expand Up @@ -976,6 +1038,36 @@ resource "azurerm_app_service" "test" {
`, rInt, location, rInt, rInt, clientAffinity)
}

func testAccAzureRMAppService_mangedServiceIdentity(rInt int, location string) string {
return fmt.Sprintf(`
resource "azurerm_resource_group" "test" {
name = "acctestRG-%d"
location = "%s"
}
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"
}
}
`, rInt, location, rInt, rInt)
}

func testAccAzureRMAppService_connectionStrings(rInt int, location string) string {
return fmt.Sprintf(`
resource "azurerm_resource_group" "test" {
Expand Down
41 changes: 32 additions & 9 deletions website/docs/r/app_service.html.markdown
Expand Up @@ -131,6 +131,8 @@ The following arguments are supported:

* `tags` - (Optional) A mapping of tags to assign to the resource.

* `identity` - (Optional) A Managed Service Identity block as defined below.

---

`connection_string` supports the following:
Expand All @@ -141,6 +143,14 @@ The following arguments are supported:

---

`identity` supports the following:

* `type` - (Required) Specifies the identity type of the App Service. At this time the only allowed value is `SystemAssigned`.

~> The assigned `principal_id` and `tenant_id` can be retrieved after the App Service has been created. More details are available below.

---

`site_config` supports the following:

* `always_on` - (Optional) Should the app be loaded at all times? Defaults to `false`.
Expand All @@ -167,7 +177,6 @@ The following arguments are supported:

* `websockets_enabled` - (Optional) Should WebSockets be enabled?


~> **NOTE:** Additional Source Control types will be added in the future, once support for them has been added in the Azure SDK for Go.

## Attributes Reference
Expand All @@ -180,21 +189,35 @@ The following attributes are exported:

* `outbound_ip_addresses` - A comma separated list of outbound IP addresses - such as `52.23.25.3,52.143.43.12`

* `source_control` - (Optional) The default local Git source control information if deployment option is set to `LocalGit`.
* `source_control` - A `source_control` block as defined below, which contains the Source Control information when `scm_type` is set to `LocalGit`.

* `site_credential` - A `site_credential` block as defined below, which contains the site-level credentials used to publish to this App Service.

* `site_credential` - (Optional) The site-level credential used to publish files to Azure Web App.
* `identity` - An `identity` block as defined below, which contains the Managed Service Identity information for this App Service.

---

`source_control` supports the following:
`identity` exports the following:

* `repo_url` - URL of the Git repository for this App Service.
* `branch` - Branch name of the Git repository for this App Service.
* `principal_id` - The Principal ID for the Service Principal associated with the Managed Service Identity of this App Service.

`site_credential` supports the following:
* `tenant_id` - The Tenant ID for the Service Principal associated with the Managed Service Identity of this App Service.

* `username` - If your site is named 'MySite', the user name will be '$MySite'.
* `password` - Some long random string.
---

`site_credential` exports the following:

* `username` - The username which can be used to publish to this App Service
* `password` - The password associated with the username, which can be used to publish to this App Service.

~> **NOTE:** both `username` and `password` for the `site_credential` block are only exported when `scm_type` is set to `LocalGit`

---

`source_control` exports the following:

* `repo_url` - URL of the Git repository for this App Service.
* `branch` - Branch name of the Git repository for this App Service.

## Import

Expand Down

0 comments on commit 4caf62b

Please sign in to comment.