Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

azurerm_app_service - support for MSI #1130

Merged
merged 8 commits into from
May 1, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 86 additions & 0 deletions azurerm/resource_arm_app_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,33 @@ func resourceArmAppService() *schema.Resource {
ValidateFunc: validateAppServiceName,
},

"identity": {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we document these properties in the documentation? That's stored in this file, it'll want the type property documented in the Argument Reference block and the principal_id and tenant_id fields documented in the Attributes Reference block

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

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 @@ -336,6 +363,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 @@ -431,6 +463,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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we handle this error? this is returned for instance when the request is invalid (vs the polling method when the long running request/modifications fail)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, sorry my bad


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 @@ -528,6 +582,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 @@ -784,6 +843,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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,8 @@ The following arguments are supported:

* `tags` - (Optional) A mapping of tags to assign to the resource. Changing this forces a new resource to be created.

* `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