From a52a332b00d842465f4169f61fde855bdca6d4ff Mon Sep 17 00:00:00 2001 From: Josh Lane Date: Sun, 14 Apr 2019 18:53:56 -0400 Subject: [PATCH 1/7] added support for identity attribute on container groups --- azurerm/resource_arm_container_group.go | 91 ++++++++ azurerm/resource_arm_container_group_test.go | 206 +++++++++++++++++++ website/docs/r/container_group.html.markdown | 12 ++ 3 files changed, 309 insertions(+) mode change 100644 => 100755 azurerm/resource_arm_container_group.go diff --git a/azurerm/resource_arm_container_group.go b/azurerm/resource_arm_container_group.go old mode 100644 new mode 100755 index 69fa0e810e60..5dabd1be1487 --- a/azurerm/resource_arm_container_group.go +++ b/azurerm/resource_arm_container_group.go @@ -92,6 +92,40 @@ 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, + DiffSuppressFunc: ignoreCaseDiffSuppressFunc, + ValidateFunc: validation.StringInSlice([]string{ + "SystemAssigned", + "UserAssigned", + "SystemAssignedUserAssigned", + }, 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 +450,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 +521,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 +748,28 @@ func expandContainerEnvironmentVariables(input interface{}, secure bool) *[]cont return &output } +func expandContainerGroupIdentity(d *schema.ResourceData) *containerinstance.ContainerGroupIdentity { + v := d.Get("identity") + identities := v.([]interface{}) + 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 +895,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 index 4eb117868703..b3728289b939 100644 --- a/azurerm/resource_arm_container_group_test.go +++ b/azurerm/resource_arm_container_group_test.go @@ -5,12 +5,103 @@ import ( "net/http" "testing" + "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 := testAccAzureRMContainerGroupSystemAssignedIdentity(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 := testAccAzureRMContainerGroupUserAssignedIdentity(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_multipleAssignedIdentity(t *testing.T) { + resourceName := "azurerm_container_group.test" + ri := tf.AccRandTimeInt() + rs := acctest.RandString(14) + config := testAccAzureRMContainerGroupMultipleAssignedIdentity(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 +455,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. From dbd4e534f08c5161f9f84fa83c8ce83c0118f5fb Mon Sep 17 00:00:00 2001 From: Josh Lane Date: Sun, 14 Apr 2019 21:57:49 -0400 Subject: [PATCH 2/7] fixed broken method refs --- azurerm/resource_arm_container_group_test.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) mode change 100644 => 100755 azurerm/resource_arm_container_group_test.go 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 b3728289b939..0f836a55c830 --- a/azurerm/resource_arm_container_group_test.go +++ b/azurerm/resource_arm_container_group_test.go @@ -5,6 +5,7 @@ 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" @@ -16,7 +17,7 @@ import ( func TestAccAzureRMContainerGroup_SystemAssignedIdentity(t *testing.T) { resourceName := "azurerm_container_group.test" ri := tf.AccRandTimeInt() - config := testAccAzureRMContainerGroupSystemAssignedIdentity(ri, testLocation()) + config := testAccAzureRMContainerGroup_SystemAssignedIdentity(ri, testLocation()) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, @@ -47,7 +48,7 @@ func TestAccAzureRMContainerGroup_UserAssignedIdentity(t *testing.T) { resourceName := "azurerm_container_group.test" ri := tf.AccRandTimeInt() rs := acctest.RandString(14) - config := testAccAzureRMContainerGroupUserAssignedIdentity(ri, testLocation(), rs) + config := testAccAzureRMContainerGroup_UserAssignedIdentity(ri, testLocation(), rs) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, @@ -71,11 +72,11 @@ func TestAccAzureRMContainerGroup_UserAssignedIdentity(t *testing.T) { }) } -func TestAccAzureRMContainerGroup_multipleAssignedIdentity(t *testing.T) { +func TestAccAzureRMContainerGroup_multipleAssignedIdentities(t *testing.T) { resourceName := "azurerm_container_group.test" ri := tf.AccRandTimeInt() rs := acctest.RandString(14) - config := testAccAzureRMContainerGroupMultipleAssignedIdentity(ri, testLocation(), rs) + config := testAccAzureRMContainerGroup_MultipleAssignedIdentities(ri, testLocation(), rs) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, From fd1502c3ada877045d9f2ad3f741ef1ea2861fcc Mon Sep 17 00:00:00 2001 From: Tom Harvey Date: Tue, 16 Apr 2019 07:23:26 -0400 Subject: [PATCH 3/7] enable case sensitivity Co-Authored-By: jplane --- azurerm/resource_arm_container_group.go | 1 - 1 file changed, 1 deletion(-) diff --git a/azurerm/resource_arm_container_group.go b/azurerm/resource_arm_container_group.go index 5dabd1be1487..2eae9e7c28de 100755 --- a/azurerm/resource_arm_container_group.go +++ b/azurerm/resource_arm_container_group.go @@ -102,7 +102,6 @@ func resourceArmContainerGroup() *schema.Resource { "type": { Type: schema.TypeString, Required: true, - DiffSuppressFunc: ignoreCaseDiffSuppressFunc, ValidateFunc: validation.StringInSlice([]string{ "SystemAssigned", "UserAssigned", From cc2a317859c4000b66fa083781c93f97da6671cb Mon Sep 17 00:00:00 2001 From: Josh Lane Date: Tue, 16 Apr 2019 07:38:21 -0400 Subject: [PATCH 4/7] fixed incorrect validation value for identity --- azurerm/resource_arm_container_group.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azurerm/resource_arm_container_group.go b/azurerm/resource_arm_container_group.go index 5dabd1be1487..43e6e6fc02a8 100755 --- a/azurerm/resource_arm_container_group.go +++ b/azurerm/resource_arm_container_group.go @@ -106,7 +106,7 @@ func resourceArmContainerGroup() *schema.Resource { ValidateFunc: validation.StringInSlice([]string{ "SystemAssigned", "UserAssigned", - "SystemAssignedUserAssigned", + "SystemAssigned, UserAssigned", }, false), }, "principal_id": { From c890ca2571acba75cd8d0f34e80508a995b7e548 Mon Sep 17 00:00:00 2001 From: Josh Lane Date: Tue, 16 Apr 2019 07:51:05 -0400 Subject: [PATCH 5/7] fixing format issues (gofmt) --- azurerm/resource_arm_container_group.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/azurerm/resource_arm_container_group.go b/azurerm/resource_arm_container_group.go index 6f6653a494cd..c322b7cdd265 100755 --- a/azurerm/resource_arm_container_group.go +++ b/azurerm/resource_arm_container_group.go @@ -100,8 +100,8 @@ func resourceArmContainerGroup() *schema.Resource { Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "type": { - Type: schema.TypeString, - Required: true, + Type: schema.TypeString, + Required: true, ValidateFunc: validation.StringInSlice([]string{ "SystemAssigned", "UserAssigned", From 4d53cbc99a0a05e97075250a819d5b1d68ad02e3 Mon Sep 17 00:00:00 2001 From: Tom Harvey Date: Tue, 16 Apr 2019 16:36:30 -0400 Subject: [PATCH 6/7] add nil check for when user doesn't specify identity element Good catch, sorry for that... in case its not obvious, I'm a bit of a Go newb. :-) Co-Authored-By: jplane --- azurerm/resource_arm_container_group.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/azurerm/resource_arm_container_group.go b/azurerm/resource_arm_container_group.go index c322b7cdd265..11e6dc680772 100755 --- a/azurerm/resource_arm_container_group.go +++ b/azurerm/resource_arm_container_group.go @@ -750,6 +750,9 @@ func expandContainerEnvironmentVariables(input interface{}, secure bool) *[]cont 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)) From 25d54b92b92450e88a32f09e2673bde6da78a089 Mon Sep 17 00:00:00 2001 From: Josh Lane Date: Tue, 16 Apr 2019 16:56:13 -0400 Subject: [PATCH 7/7] gofmt fix --- azurerm/resource_arm_container_group.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azurerm/resource_arm_container_group.go b/azurerm/resource_arm_container_group.go index 11e6dc680772..691461980b5c 100755 --- a/azurerm/resource_arm_container_group.go +++ b/azurerm/resource_arm_container_group.go @@ -751,7 +751,7 @@ func expandContainerGroupIdentity(d *schema.ResourceData) *containerinstance.Con v := d.Get("identity") identities := v.([]interface{}) if len(identities) == 0 { - return nil + return nil } identity := identities[0].(map[string]interface{}) identityType := containerinstance.ResourceIdentityType(identity["type"].(string))