diff --git a/azurerm/resource_arm_container_group.go b/azurerm/resource_arm_container_group.go old mode 100644 new mode 100755 index 69fa0e810e60..691461980b5c --- a/azurerm/resource_arm_container_group.go +++ b/azurerm/resource_arm_container_group.go @@ -92,6 +92,39 @@ func resourceArmContainerGroup() *schema.Resource { }, }, + "identity": { + 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{ + "SystemAssigned", + "UserAssigned", + "SystemAssigned, UserAssigned", + }, false), + }, + "principal_id": { + Type: schema.TypeString, + Computed: true, + }, + "identity_ids": { + Type: schema.TypeList, + Optional: true, + MinItems: 1, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.NoZeroValues, + }, + }, + }, + }, + }, + "tags": tagsForceNewSchema(), "restart_policy": { @@ -416,6 +449,7 @@ func resourceArmContainerGroupCreate(d *schema.ResourceData, meta interface{}) e Name: &name, Location: &location, Tags: expandTags(tags), + Identity: expandContainerGroupIdentity(d), ContainerGroupProperties: &containerinstance.ContainerGroupProperties{ Containers: containers, Diagnostics: diagnostics, @@ -486,6 +520,10 @@ func resourceArmContainerGroupRead(d *schema.ResourceData, meta interface{}) err d.Set("location", azureRMNormalizeLocation(*location)) } + if err := d.Set("identity", flattenContainerGroupIdentity(d, resp.Identity)); err != nil { + return fmt.Errorf("Error setting `identity`: %+v", err) + } + if props := resp.ContainerGroupProperties; props != nil { containerConfigs := flattenContainerGroupContainers(d, resp.Containers, props.IPAddress.Ports, props.Volumes) if err := d.Set("container", containerConfigs); err != nil { @@ -709,6 +747,31 @@ func expandContainerEnvironmentVariables(input interface{}, secure bool) *[]cont return &output } +func expandContainerGroupIdentity(d *schema.ResourceData) *containerinstance.ContainerGroupIdentity { + v := d.Get("identity") + identities := v.([]interface{}) + if len(identities) == 0 { + return nil + } + identity := identities[0].(map[string]interface{}) + identityType := containerinstance.ResourceIdentityType(identity["type"].(string)) + + identityIds := make(map[string]*containerinstance.ContainerGroupIdentityUserAssignedIdentitiesValue) + for _, id := range identity["identity_ids"].([]interface{}) { + identityIds[id.(string)] = &containerinstance.ContainerGroupIdentityUserAssignedIdentitiesValue{} + } + + cgIdentity := containerinstance.ContainerGroupIdentity{ + Type: identityType, + } + + if cgIdentity.Type == containerinstance.UserAssigned || cgIdentity.Type == containerinstance.SystemAssignedUserAssigned { + cgIdentity.UserAssignedIdentities = identityIds + } + + return &cgIdentity +} + func expandContainerImageRegistryCredentials(d *schema.ResourceData) *[]containerinstance.ImageRegistryCredential { credsRaw := d.Get("image_registry_credential").([]interface{}) if len(credsRaw) == 0 { @@ -834,6 +897,36 @@ func expandContainerProbe(input interface{}) *containerinstance.ContainerProbe { return &probe } +func flattenContainerGroupIdentity(d *schema.ResourceData, identity *containerinstance.ContainerGroupIdentity) []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 + } + + identityIds := make([]string, 0) + if identity.UserAssignedIdentities != nil { + /* + "userAssignedIdentities": { + "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/tomdevidentity/providers/Microsoft.ManagedIdentity/userAssignedIdentities/tom123": { + "principalId": "00000000-0000-0000-0000-000000000000", + "clientId": "00000000-0000-0000-0000-000000000000" + } + } + */ + for key := range identity.UserAssignedIdentities { + identityIds = append(identityIds, key) + } + } + result["identity_ids"] = identityIds + + return []interface{}{result} +} + func flattenContainerImageRegistryCredentials(d *schema.ResourceData, input *[]containerinstance.ImageRegistryCredential) []interface{} { if input == nil { return nil diff --git a/azurerm/resource_arm_container_group_test.go b/azurerm/resource_arm_container_group_test.go old mode 100644 new mode 100755 index 4eb117868703..0f836a55c830 --- a/azurerm/resource_arm_container_group_test.go +++ b/azurerm/resource_arm_container_group_test.go @@ -5,12 +5,104 @@ import ( "net/http" "testing" + "github.com/hashicorp/terraform/helper/acctest" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/validate" + "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/terraform" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) +func TestAccAzureRMContainerGroup_SystemAssignedIdentity(t *testing.T) { + resourceName := "azurerm_container_group.test" + ri := tf.AccRandTimeInt() + config := testAccAzureRMContainerGroup_SystemAssignedIdentity(ri, testLocation()) + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMContainerGroupDestroy, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMContainerGroupExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "identity.0.type", "SystemAssigned"), + resource.TestCheckResourceAttr(resourceName, "identity.0.identity_ids.#", "0"), + resource.TestMatchResourceAttr(resourceName, "identity.0.principal_id", validate.UUIDRegExp), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{ + "identity.0.principal_id", + }, + }, + }, + }) +} + +func TestAccAzureRMContainerGroup_UserAssignedIdentity(t *testing.T) { + resourceName := "azurerm_container_group.test" + ri := tf.AccRandTimeInt() + rs := acctest.RandString(14) + config := testAccAzureRMContainerGroup_UserAssignedIdentity(ri, testLocation(), rs) + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMContainerGroupDestroy, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMContainerGroupExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "identity.0.type", "UserAssigned"), + resource.TestCheckResourceAttr(resourceName, "identity.0.identity_ids.#", "1"), + resource.TestCheckResourceAttr(resourceName, "identity.0.principal_id", ""), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAzureRMContainerGroup_multipleAssignedIdentities(t *testing.T) { + resourceName := "azurerm_container_group.test" + ri := tf.AccRandTimeInt() + rs := acctest.RandString(14) + config := testAccAzureRMContainerGroup_MultipleAssignedIdentities(ri, testLocation(), rs) + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMContainerGroupDestroy, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMContainerGroupExists(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), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{ + "identity.0.principal_id", + }, + }, + }, + }) +} + func TestAccAzureRMContainerGroup_imageRegistryCredentials(t *testing.T) { resourceName := "azurerm_container_group.test" ri := tf.AccRandTimeInt() @@ -364,6 +456,121 @@ func TestAccAzureRMContainerGroup_windowsComplete(t *testing.T) { }) } +func testAccAzureRMContainerGroup_SystemAssignedIdentity(ri int, location string) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_container_group" "test" { + name = "acctestcontainergroup-%d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + ip_address_type = "public" + os_type = "Linux" + + container { + name = "hw" + image = "microsoft/aci-helloworld:latest" + cpu = "0.5" + memory = "0.5" + port = 80 + } + + identity { + type = "SystemAssigned" + } + + tags = { + environment = "Testing" + } +} +`, ri, location, ri) +} + +func testAccAzureRMContainerGroup_UserAssignedIdentity(ri int, location string, rString string) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_user_assigned_identity" "test" { + resource_group_name = "${azurerm_resource_group.test.name}" + location = "${azurerm_resource_group.test.location}" + + name = "acctest%s" +} + +resource "azurerm_container_group" "test" { + name = "acctestcontainergroup-%d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + ip_address_type = "public" + os_type = "Linux" + + container { + name = "hw" + image = "microsoft/aci-helloworld:latest" + cpu = "0.5" + memory = "0.5" + port = 80 + } + + identity { + type = "UserAssigned" + identity_ids = ["${azurerm_user_assigned_identity.test.id}"] + } + + tags = { + environment = "Testing" + } +} +`, ri, location, rString, ri) +} + +func testAccAzureRMContainerGroup_MultipleAssignedIdentities(ri int, location string, rString string) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_user_assigned_identity" "test" { + resource_group_name = "${azurerm_resource_group.test.name}" + location = "${azurerm_resource_group.test.location}" + + name = "acctest%s" +} + +resource "azurerm_container_group" "test" { + name = "acctestcontainergroup-%d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + ip_address_type = "public" + os_type = "Linux" + + container { + name = "hw" + image = "microsoft/aci-helloworld:latest" + cpu = "0.5" + memory = "0.5" + port = 80 + } + + identity { + type = "SystemAssigned, UserAssigned" + identity_ids = ["${azurerm_user_assigned_identity.test.id}"] + } + + tags = { + environment = "Testing" + } +} +`, ri, location, rString, ri) +} + func testAccAzureRMContainerGroup_linuxBasic(ri int, location string) string { return fmt.Sprintf(` resource "azurerm_resource_group" "test" { diff --git a/website/docs/r/container_group.html.markdown b/website/docs/r/container_group.html.markdown index 32b3a8157cc4..57a2db8db135 100644 --- a/website/docs/r/container_group.html.markdown +++ b/website/docs/r/container_group.html.markdown @@ -110,6 +110,8 @@ The following arguments are supported: * `location` - (Required) Specifies the supported Azure location where the resource exists. Changing this forces a new resource to be created. +* `identity` - (Optional) An `identity` block. + * `container` - (Required) The definition of a container that is part of the group as documented in the `container` block below. Changing this forces a new resource to be created. ~> **Note:** if `os_type` is set to `Windows` currently only a single `container` block is supported. @@ -132,6 +134,16 @@ The following arguments are supported: --- +An `identity` block supports the following: + +* `type` - (Required) The Managed Service Identity Type of this container group. 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`, identity the Principal ID can be retrieved after the container group has been created. See [documentation](https://docs.microsoft.com/en-us/azure/active-directory/managed-service-identity/overview) for more information. + +* `identity_ids` - (Optional) Specifies a list of user managed identity ids to be assigned. Required if `type` is `UserAssigned`. + +--- + A `container` block supports: * `name` - (Required) Specifies the name of the Container. Changing this forces a new resource to be created.