From 6f4db98b44a756764b90d78bec2b5a44cb29cb4e Mon Sep 17 00:00:00 2001 From: njucz Date: Thu, 3 Sep 2020 11:37:46 +0800 Subject: [PATCH 1/4] support managed identity for "azurerm_spring_cloud_app" --- .../appplatform/spring_cloud_app_resource.go | 108 ++++++++++++++++-- .../tests/spring_cloud_app_resource_test.go | 71 ++++++++++++ website/docs/r/spring_cloud_app.html.markdown | 25 +++- 3 files changed, 192 insertions(+), 12 deletions(-) diff --git a/azurerm/internal/services/appplatform/spring_cloud_app_resource.go b/azurerm/internal/services/appplatform/spring_cloud_app_resource.go index 1790cfe9fb68..31ce88a86761 100644 --- a/azurerm/internal/services/appplatform/spring_cloud_app_resource.go +++ b/azurerm/internal/services/appplatform/spring_cloud_app_resource.go @@ -7,7 +7,9 @@ import ( "github.com/Azure/azure-sdk-for-go/services/preview/appplatform/mgmt/2019-05-01-preview/appplatform" "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/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/internal/clients" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/appplatform/parse" @@ -19,8 +21,9 @@ import ( func resourceArmSpringCloudApp() *schema.Resource { return &schema.Resource{ - Create: resourceArmSpringCloudAppCreate, + Create: resourceArmSpringCloudAppCreateUpdate, Read: resourceArmSpringCloudAppRead, + Update: resourceArmSpringCloudAppCreateUpdate, Delete: resourceArmSpringCloudAppDelete, Importer: azSchema.ValidateResourceIDPriorToImport(func(id string) error { @@ -31,6 +34,7 @@ func resourceArmSpringCloudApp() *schema.Resource { Timeouts: &schema.ResourceTimeout{ Create: schema.DefaultTimeout(30 * time.Minute), Read: schema.DefaultTimeout(5 * time.Minute), + Update: schema.DefaultTimeout(30 * time.Minute), Delete: schema.DefaultTimeout(30 * time.Minute), }, @@ -50,12 +54,43 @@ func resourceArmSpringCloudApp() *schema.Resource { ForceNew: true, ValidateFunc: validate.SpringCloudServiceName, }, + + "identity": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "type": { + Type: schema.TypeString, + Required: true, + // other two enum: 'UserAssigned', 'SystemAssignedUserAssigned' are not supported for now + ValidateFunc: validation.StringInSlice([]string{ + string(appplatform.None), + string(appplatform.SystemAssigned), + }, true), + DiffSuppressFunc: suppress.CaseDifference, + }, + + "principal_id": { + Type: schema.TypeString, + Computed: true, + }, + + "tenant_id": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, }, } } -func resourceArmSpringCloudAppCreate(d *schema.ResourceData, meta interface{}) error { +func resourceArmSpringCloudAppCreateUpdate(d *schema.ResourceData, meta interface{}) error { client := meta.(*clients.Client).AppPlatform.AppsClient + servicesClient := meta.(*clients.Client).AppPlatform.ServicesClient ctx, cancel := timeouts.ForCreate(meta.(*clients.Client).StopContext, d) defer cancel() @@ -63,22 +98,33 @@ func resourceArmSpringCloudAppCreate(d *schema.ResourceData, meta interface{}) e resourceGroup := d.Get("resource_group_name").(string) serviceName := d.Get("service_name").(string) - existing, err := client.Get(ctx, resourceGroup, serviceName, name, "") + serviceResp, err := servicesClient.Get(ctx, resourceGroup, serviceName) if err != nil { - if !utils.ResponseWasNotFound(existing.Response) { - return fmt.Errorf("checking for presence of existing Spring Cloud App %q (Spring Cloud Service %q / Resource Group %q): %+v", name, serviceName, resourceGroup, err) - } + return fmt.Errorf("unable to retrieve Spring Cloud Service %q (Resource Group %q): %+v", name, resourceGroup, err) } - if existing.ID != nil && *existing.ID != "" { - return tf.ImportAsExistsError("azurerm_spring_cloud_app", *existing.ID) + + if d.IsNewResource() { + existing, err := client.Get(ctx, resourceGroup, serviceName, name, "") + if err != nil { + if !utils.ResponseWasNotFound(existing.Response) { + return fmt.Errorf("checking for presence of existing Spring Cloud App %q (Spring Cloud Service %q / Resource Group %q): %+v", name, serviceName, resourceGroup, err) + } + } + if existing.ID != nil && *existing.ID != "" { + return tf.ImportAsExistsError("azurerm_spring_cloud_app", *existing.ID) + } } - future, err := client.CreateOrUpdate(ctx, resourceGroup, serviceName, name, appplatform.AppResource{}) + app := appplatform.AppResource{ + Location: serviceResp.Location, + Identity: expandSpringCloudAppIdentity(d.Get("identity").([]interface{})), + } + future, err := client.CreateOrUpdate(ctx, resourceGroup, serviceName, name, app) if err != nil { - return fmt.Errorf("creating Spring Cloud App %q (Spring Cloud Service %q / Resource Group %q): %+v", name, serviceName, resourceGroup, err) + return fmt.Errorf("creating/updating Spring Cloud App %q (Spring Cloud Service %q / Resource Group %q): %+v", name, serviceName, resourceGroup, err) } if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { - return fmt.Errorf("waiting for creation of Spring Cloud App %q (Spring Cloud Service %q / Resource Group %q): %+v", name, serviceName, resourceGroup, err) + return fmt.Errorf("waiting for creation/updation of Spring Cloud App %q (Spring Cloud Service %q / Resource Group %q): %+v", name, serviceName, resourceGroup, err) } resp, err := client.Get(ctx, resourceGroup, serviceName, name, "") @@ -117,6 +163,10 @@ func resourceArmSpringCloudAppRead(d *schema.ResourceData, meta interface{}) err d.Set("resource_group_name", id.ResourceGroup) d.Set("service_name", id.ServiceName) + if err := d.Set("identity", flattenSpringCloudAppIdentity(resp.Identity)); err != nil { + return fmt.Errorf("setting `identity`: %s", err) + } + return nil } @@ -136,3 +186,39 @@ func resourceArmSpringCloudAppDelete(d *schema.ResourceData, meta interface{}) e return nil } + +func expandSpringCloudAppIdentity(input []interface{}) *appplatform.ManagedIdentityProperties { + if len(input) == 0 || input[0] == nil { + return &appplatform.ManagedIdentityProperties{ + Type: appplatform.None, + } + } + identity := input[0].(map[string]interface{}) + return &appplatform.ManagedIdentityProperties{ + Type: appplatform.ManagedIdentityType(identity["type"].(string)), + } +} + +func flattenSpringCloudAppIdentity(identity *appplatform.ManagedIdentityProperties) []interface{} { + if identity == nil { + return make([]interface{}, 0) + } + + principalId := "" + if identity.PrincipalID != nil { + principalId = *identity.PrincipalID + } + + tenantId := "" + if identity.TenantID != nil { + tenantId = *identity.TenantID + } + + return []interface{}{ + map[string]interface{}{ + "principal_id": principalId, + "tenant_id": tenantId, + "type": string(identity.Type), + }, + } +} diff --git a/azurerm/internal/services/appplatform/tests/spring_cloud_app_resource_test.go b/azurerm/internal/services/appplatform/tests/spring_cloud_app_resource_test.go index 65d7b28b571f..4f25acf30c64 100644 --- a/azurerm/internal/services/appplatform/tests/spring_cloud_app_resource_test.go +++ b/azurerm/internal/services/appplatform/tests/spring_cloud_app_resource_test.go @@ -49,6 +49,60 @@ func TestAccAzureRMSpringCloudApp_requiresImport(t *testing.T) { }) } +func TestAccAzureRMSpringCloudApp_complete(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_spring_cloud_app", "test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMSpringCloudAppDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMSpringCloudApp_complete(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMSpringCloudAppExists(data.ResourceName), + resource.TestCheckResourceAttrSet(data.ResourceName, "identity.0.principal_id"), + resource.TestCheckResourceAttrSet(data.ResourceName, "identity.0.tenant_id"), + ), + }, + data.ImportStep(), + }, + }) +} + +func TestAccAzureRMSpringCloudApp_update(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_spring_cloud_app", "test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMSpringCloudAppDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMSpringCloudApp_basic(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMSpringCloudAppExists(data.ResourceName), + ), + }, + data.ImportStep(), + { + Config: testAccAzureRMSpringCloudApp_complete(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMSpringCloudAppExists(data.ResourceName), + ), + }, + data.ImportStep(), + { + Config: testAccAzureRMSpringCloudApp_basic(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMSpringCloudAppExists(data.ResourceName), + ), + }, + data.ImportStep(), + }, + }) +} + func testCheckAzureRMSpringCloudAppExists(resourceName string) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[resourceName] @@ -125,6 +179,23 @@ resource "azurerm_spring_cloud_app" "import" { `, template) } +func testAccAzureRMSpringCloudApp_complete(data acceptance.TestData) string { + template := testAccAzureRMSpringCloudApp_template(data) + return fmt.Sprintf(` +%s + +resource "azurerm_spring_cloud_app" "test" { + name = "acctest-sca-%d" + resource_group_name = azurerm_spring_cloud_service.test.resource_group_name + service_name = azurerm_spring_cloud_service.test.name + + identity { + type = "SystemAssigned" + } +} +`, template, data.RandomInteger) +} + func testAccAzureRMSpringCloudApp_template(data acceptance.TestData) string { return fmt.Sprintf(` provider "azurerm" { diff --git a/website/docs/r/spring_cloud_app.html.markdown b/website/docs/r/spring_cloud_app.html.markdown index c5f724202ca0..617f82c719df 100644 --- a/website/docs/r/spring_cloud_app.html.markdown +++ b/website/docs/r/spring_cloud_app.html.markdown @@ -32,6 +32,10 @@ resource "azurerm_spring_cloud_app" "example" { name = "example-springcloudapp" resource_group_name = azurerm_resource_group.example.name service_name = azurerm_spring_cloud_service.example.name + + identity { + type = "SystemAssigned" + } } ``` @@ -43,7 +47,15 @@ The following arguments are supported: * `resource_group_name` - (Required) Specifies the name of the resource group in which to create the Spring Cloud Application. Changing this forces a new resource to be created. -* `service_name` - (Required) Specifies the name of the Spring Cloud Service resource. Changing this forces a new resource to be created. +* `service_name` - (Required) Specifies the name of the Spring Cloud Service resource. Changing this forces a new resource to be created. + +* `identity` - (Optional) A Managed Service Identity block as defined below. + +--- + +A `identity` block supports the following: + +* `type` - (Required) Specifies the identity type of the Spring Cloud Application. Possible values are `None` or `SystemAssigned`. ## Attributes Reference @@ -51,12 +63,23 @@ The following attributes are exported: * `id` - The ID of the Spring Cloud Application. +* `identity` - An `identity` block as defined below, which contains the Managed Service Identity information for this Spring Cloud Application. + +--- + +A `identity` block exports the following: + +* `principal_id` - The Principal ID for the Service Principal associated with the Managed Service Identity of this Spring Cloud Application. + +* `tenant_id` - The Tenant ID for the Service Principal associated with the Managed Service Identity of this Spring Cloud Application. + ## Timeouts The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/docs/configuration/resources.html#timeouts) for certain actions: * `create` - (Defaults to 30 minutes) Used when creating the Spring Cloud Application. * `read` - (Defaults to 5 minutes) Used when retrieving the Spring Cloud Application. +* `update` - (Defaults to 30 minutes) Used when updating the Spring Cloud Application. * `delete` - (Defaults to 30 minutes) Used when deleting the Spring Cloud Application. ## Import From 65d0a92ef02ed552897767f83ce782a0db3028a2 Mon Sep 17 00:00:00 2001 From: njucz Date: Thu, 3 Sep 2020 12:55:36 +0800 Subject: [PATCH 2/4] update --- .../internal/services/appplatform/spring_cloud_app_resource.go | 1 - website/docs/r/spring_cloud_app.html.markdown | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/azurerm/internal/services/appplatform/spring_cloud_app_resource.go b/azurerm/internal/services/appplatform/spring_cloud_app_resource.go index 31ce88a86761..cca76779217c 100644 --- a/azurerm/internal/services/appplatform/spring_cloud_app_resource.go +++ b/azurerm/internal/services/appplatform/spring_cloud_app_resource.go @@ -66,7 +66,6 @@ func resourceArmSpringCloudApp() *schema.Resource { Required: true, // other two enum: 'UserAssigned', 'SystemAssignedUserAssigned' are not supported for now ValidateFunc: validation.StringInSlice([]string{ - string(appplatform.None), string(appplatform.SystemAssigned), }, true), DiffSuppressFunc: suppress.CaseDifference, diff --git a/website/docs/r/spring_cloud_app.html.markdown b/website/docs/r/spring_cloud_app.html.markdown index 617f82c719df..a232f62f5944 100644 --- a/website/docs/r/spring_cloud_app.html.markdown +++ b/website/docs/r/spring_cloud_app.html.markdown @@ -55,7 +55,7 @@ The following arguments are supported: A `identity` block supports the following: -* `type` - (Required) Specifies the identity type of the Spring Cloud Application. Possible values are `None` or `SystemAssigned`. +* `type` - (Required) Specifies the identity type of the Spring Cloud Application. Possible value is `SystemAssigned`. ## Attributes Reference From 7ce48401396be993291c67e93ed1d9ee91247aa1 Mon Sep 17 00:00:00 2001 From: njucz Date: Thu, 3 Sep 2020 13:30:12 +0800 Subject: [PATCH 3/4] update --- .../internal/services/appplatform/spring_cloud_app_resource.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azurerm/internal/services/appplatform/spring_cloud_app_resource.go b/azurerm/internal/services/appplatform/spring_cloud_app_resource.go index cca76779217c..765d05e51ef0 100644 --- a/azurerm/internal/services/appplatform/spring_cloud_app_resource.go +++ b/azurerm/internal/services/appplatform/spring_cloud_app_resource.go @@ -99,7 +99,7 @@ func resourceArmSpringCloudAppCreateUpdate(d *schema.ResourceData, meta interfac serviceResp, err := servicesClient.Get(ctx, resourceGroup, serviceName) if err != nil { - return fmt.Errorf("unable to retrieve Spring Cloud Service %q (Resource Group %q): %+v", name, resourceGroup, err) + return fmt.Errorf("unable to retrieve Spring Cloud Service %q (Resource Group %q): %+v", serviceName, resourceGroup, err) } if d.IsNewResource() { From 9413785999dda0d22df37bfb5d0336b71fc43ea2 Mon Sep 17 00:00:00 2001 From: njucz Date: Thu, 3 Sep 2020 15:49:36 +0800 Subject: [PATCH 4/4] update --- .../services/appplatform/spring_cloud_app_resource.go | 8 +++----- website/docs/r/spring_cloud_app.html.markdown | 8 +++----- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/azurerm/internal/services/appplatform/spring_cloud_app_resource.go b/azurerm/internal/services/appplatform/spring_cloud_app_resource.go index 765d05e51ef0..9959be2efacc 100644 --- a/azurerm/internal/services/appplatform/spring_cloud_app_resource.go +++ b/azurerm/internal/services/appplatform/spring_cloud_app_resource.go @@ -9,7 +9,6 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/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/internal/clients" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/appplatform/parse" @@ -67,8 +66,7 @@ func resourceArmSpringCloudApp() *schema.Resource { // other two enum: 'UserAssigned', 'SystemAssignedUserAssigned' are not supported for now ValidateFunc: validation.StringInSlice([]string{ string(appplatform.SystemAssigned), - }, true), - DiffSuppressFunc: suppress.CaseDifference, + }, false), }, "principal_id": { @@ -120,10 +118,10 @@ func resourceArmSpringCloudAppCreateUpdate(d *schema.ResourceData, meta interfac } future, err := client.CreateOrUpdate(ctx, resourceGroup, serviceName, name, app) if err != nil { - return fmt.Errorf("creating/updating Spring Cloud App %q (Spring Cloud Service %q / Resource Group %q): %+v", name, serviceName, resourceGroup, err) + return fmt.Errorf("creating/update Spring Cloud App %q (Spring Cloud Service %q / Resource Group %q): %+v", name, serviceName, resourceGroup, err) } if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { - return fmt.Errorf("waiting for creation/updation of Spring Cloud App %q (Spring Cloud Service %q / Resource Group %q): %+v", name, serviceName, resourceGroup, err) + return fmt.Errorf("waiting for creation/update of Spring Cloud App %q (Spring Cloud Service %q / Resource Group %q): %+v", name, serviceName, resourceGroup, err) } resp, err := client.Get(ctx, resourceGroup, serviceName, name, "") diff --git a/website/docs/r/spring_cloud_app.html.markdown b/website/docs/r/spring_cloud_app.html.markdown index a232f62f5944..16862e8aac62 100644 --- a/website/docs/r/spring_cloud_app.html.markdown +++ b/website/docs/r/spring_cloud_app.html.markdown @@ -49,11 +49,11 @@ The following arguments are supported: * `service_name` - (Required) Specifies the name of the Spring Cloud Service resource. Changing this forces a new resource to be created. -* `identity` - (Optional) A Managed Service Identity block as defined below. +* `identity` - (Optional) An `identity` block as defined below. --- -A `identity` block supports the following: +An `identity` block supports the following: * `type` - (Required) Specifies the identity type of the Spring Cloud Application. Possible value is `SystemAssigned`. @@ -63,11 +63,9 @@ The following attributes are exported: * `id` - The ID of the Spring Cloud Application. -* `identity` - An `identity` block as defined below, which contains the Managed Service Identity information for this Spring Cloud Application. - --- -A `identity` block exports the following: +An `identity` block exports the following: * `principal_id` - The Principal ID for the Service Principal associated with the Managed Service Identity of this Spring Cloud Application.