From 7e8bdfdc6275677dd76f372eb4a9201a28993744 Mon Sep 17 00:00:00 2001 From: njucz Date: Wed, 20 Nov 2019 17:18:07 +0800 Subject: [PATCH] implement resource and data_source of Azure Spring Cloud --- azurerm/config.go | 15 + azurerm/data_source_spring_cloud.go | 206 +++++++ azurerm/data_source_spring_cloud_test.go | 49 ++ .../internal/services/appplatform/client.go | 19 + .../internal/services/appplatform/validate.go | 47 ++ .../services/appplatform/validate_test.go | 61 +++ azurerm/provider.go | 2 + azurerm/resource_arm_spring_cloud.go | 514 ++++++++++++++++++ azurerm/resource_arm_spring_cloud_test.go | 219 ++++++++ website/azurerm.erb | 13 + website/docs/d/spring_cloud.html.markdown | 111 ++++ website/docs/r/spring_cloud.html.markdown | 99 ++++ 12 files changed, 1355 insertions(+) create mode 100644 azurerm/data_source_spring_cloud.go create mode 100644 azurerm/data_source_spring_cloud_test.go create mode 100644 azurerm/internal/services/appplatform/client.go create mode 100644 azurerm/internal/services/appplatform/validate.go create mode 100644 azurerm/internal/services/appplatform/validate_test.go create mode 100644 azurerm/resource_arm_spring_cloud.go create mode 100644 azurerm/resource_arm_spring_cloud_test.go create mode 100644 website/docs/d/spring_cloud.html.markdown create mode 100644 website/docs/r/spring_cloud.html.markdown diff --git a/azurerm/config.go b/azurerm/config.go index 3b7c38ca0ba9d..29cb723bbe1c3 100644 --- a/azurerm/config.go +++ b/azurerm/config.go @@ -13,6 +13,7 @@ import ( "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/analysisservices" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/apimanagement" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/applicationinsights" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/appplatform" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/authorization" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/automation" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/batch" @@ -90,6 +91,19 @@ type ArmClient struct { // Services // NOTE: all new services should be Public as they're going to be relocated in the near-future + AnalysisServices *analysisservices.Client + ApiManagement *apimanagement.Client + AppInsights *applicationinsights.Client + AppPlatform *appplatform.Client + Automation *automation.Client + Authorization *authorization.Client + Batch *batch.Client + Bot *bot.Client + Cdn *cdn.Client + Cognitive *cognitive.Client + Compute *clients.ComputeClient + Containers *containers.Client + Cosmos *cosmos.Client DataBricks *databricks.Client DataFactory *datafactory.Client Datalake *datalake.Client @@ -218,6 +232,7 @@ func getArmClient(authConfig *authentication.Config, skipProviderRegistration bo client.AnalysisServices = analysisservices.BuildClient(o) client.ApiManagement = apimanagement.BuildClient(o) client.AppInsights = applicationinsights.BuildClient(o) + client.AppPlatform = appplatform.BuildClient(o) client.Automation = automation.BuildClient(o) client.Authorization = authorization.BuildClient(o) client.Batch = batch.BuildClient(o) diff --git a/azurerm/data_source_spring_cloud.go b/azurerm/data_source_spring_cloud.go new file mode 100644 index 0000000000000..1a431847f8574 --- /dev/null +++ b/azurerm/data_source_spring_cloud.go @@ -0,0 +1,206 @@ +package azurerm + +import ( + "fmt" + + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" + azappplatform "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/appplatform" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tags" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func dataSourceArmSpringCloud() *schema.Resource { + return &schema.Resource{ + Read: dataSourceArmSpringCloudRead, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: azappplatform.ValidateSpringCloudName, + }, + + "location": azure.SchemaLocationForDataSource(), + + "resource_group": azure.SchemaResourceGroupNameForDataSource(), + + "config_server": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "uri": { + Type: schema.TypeString, + Computed: true, + }, + "host_key": { + Type: schema.TypeString, + Computed: true, + }, + "host_key_algorithm": { + Type: schema.TypeString, + Computed: true, + }, + "label": { + Type: schema.TypeString, + Computed: true, + }, + "password": { + Type: schema.TypeString, + Computed: true, + }, + "private_key": { + Type: schema.TypeString, + Computed: true, + }, + "repositories": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Computed: true, + }, + "uri": { + Type: schema.TypeString, + Computed: true, + }, + "host_key": { + Type: schema.TypeString, + Computed: true, + }, + "host_key_algorithm": { + Type: schema.TypeString, + Computed: true, + }, + "label": { + Type: schema.TypeString, + Computed: true, + }, + "password": { + Type: schema.TypeString, + Computed: true, + }, + "pattern": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "private_key": { + Type: schema.TypeString, + Computed: true, + }, + "search_paths": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "strict_host_key_checking": { + Type: schema.TypeBool, + Computed: true, + }, + "username": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "search_paths": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "strict_host_key_checking": { + Type: schema.TypeBool, + Computed: true, + }, + "username": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + + "config_server_error": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "code": { + Type: schema.TypeString, + Computed: true, + }, + "message": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + + "service_id": { + Type: schema.TypeString, + Computed: true, + }, + + "version": { + Type: schema.TypeInt, + Computed: true, + }, + + "tags": tags.SchemaDataSource(), + }, + } +} + +func dataSourceArmSpringCloudRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).AppPlatform.ServicesClient + ctx := meta.(*ArmClient).StopContext + + name := d.Get("name").(string) + resourceGroup := d.Get("resource_group").(string) + + resp, err := client.Get(ctx, resourceGroup, name) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error: Spring Cloud %q (Resource Group %q) was not found", name, resourceGroup) + } + return fmt.Errorf("Error reading Spring Cloud %q (Resource Group %q): %+v", name, resourceGroup, err) + } + + d.SetId(*resp.ID) + + d.Set("name", resp.Name) + d.Set("resource_group", resourceGroup) + if location := resp.Location; location != nil { + d.Set("location", azure.NormalizeLocation(*location)) + } + if clusterResourceProperties := resp.Properties; clusterResourceProperties != nil { + if clusterResourceProperties.ConfigServerProperties != nil { + if err := d.Set("config_server_error", flattenArmSpringCloudError(clusterResourceProperties.ConfigServerProperties.Error)); err != nil { + return fmt.Errorf("Error setting `error`: %+v", err) + } + + if clusterResourceProperties.ConfigServerProperties.ConfigServer != nil { + if err := d.Set("config_server", flattenArmSpringCloudConfigServerGitProperty(clusterResourceProperties.ConfigServerProperties.ConfigServer.GitProperty)); err != nil { + return fmt.Errorf("Error setting `config_server`: %+v", err) + } + } + } + + d.Set("service_id", clusterResourceProperties.ServiceID) + d.Set("version", int(*clusterResourceProperties.Version)) + } + + return nil +} diff --git a/azurerm/data_source_spring_cloud_test.go b/azurerm/data_source_spring_cloud_test.go new file mode 100644 index 0000000000000..355661a3fbbb1 --- /dev/null +++ b/azurerm/data_source_spring_cloud_test.go @@ -0,0 +1,49 @@ +package azurerm + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" +) + +func TestAccDataSourceAzureRMSpringCloud_complete(t *testing.T) { + dataSourceName := "data.azurerm_spring_cloud.test" + ri := tf.AccRandTimeInt() + location := testLocation() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccDataSourcePrivateLinkService_complete(ri, location), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet(dataSourceName, "id"), + resource.TestCheckResourceAttrSet(dataSourceName, "service_id"), + resource.TestCheckResourceAttrSet(dataSourceName, "version"), + resource.TestCheckResourceAttr(dataSourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(dataSourceName, "tags.env", "test"), + resource.TestCheckResourceAttr(dataSourceName, "tags.version", "1"), + resource.TestCheckResourceAttr(dataSourceName, "config_server_error.#", "0"), + resource.TestCheckResourceAttrSet(dataSourceName, "config_server"), + resource.TestCheckResourceAttr(dataSourceName, "config_server.uri", "https://github.com/Azure-Samples/piggymetrics"), + resource.TestCheckResourceAttr(dataSourceName, "config_server.label", "config"), + ), + }, + }, + }) +} + +func testAccDataSourcePrivateLinkService_complete(rInt int, location string) string { + config := testAccAzureRMSpringCloud_complete(rInt, location) + return fmt.Sprintf(` +%s + +data "azurerm_spring_cloud" "test" { + name = azurerm_spring_cloud.test.name + resource_group = azurerm_spring_cloud.test.resource_group +} +`, config) +} diff --git a/azurerm/internal/services/appplatform/client.go b/azurerm/internal/services/appplatform/client.go new file mode 100644 index 0000000000000..8bec5c239857f --- /dev/null +++ b/azurerm/internal/services/appplatform/client.go @@ -0,0 +1,19 @@ +package appplatform + +import ( + "github.com/Azure/azure-sdk-for-go/services/preview/appplatform/mgmt/2019-05-01-preview/appplatform" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/common" +) + +type Client struct { + ServicesClient *appplatform.ServicesClient +} + +func BuildClient(o *common.ClientOptions) *Client { + ServicesClient := appplatform.NewServicesClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) + o.ConfigureClient(&ServicesClient.Client, o.ResourceManagerAuthorizer) + + return &Client{ + ServicesClient: &ServicesClient, + } +} diff --git a/azurerm/internal/services/appplatform/validate.go b/azurerm/internal/services/appplatform/validate.go new file mode 100644 index 0000000000000..88a0dfa3f563b --- /dev/null +++ b/azurerm/internal/services/appplatform/validate.go @@ -0,0 +1,47 @@ +package appplatform + +import ( + "fmt" + "strings" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/validate" +) + +func ValidateSpringCloudName(i interface{}, k string) (_ []string, errors []error) { + v, ok := i.(string) + if !ok { + return nil, append(errors, fmt.Errorf("expected type of %s to be string", k)) + } + + // The name attribute rules are : + // 1. can contain only lowercase letters, numbers and hyphens. + // 2. The first character must be a letter. + // 3. The last character must be a letter or number + // 3. The value must be between 4 and 32 characters long + + if len(v) < 4 || len(v) > 32 { + errors = append(errors, fmt.Errorf("%s must be between 4 and 32 characters long", k)) + } else { + if m, _ := validate.RegExHelper(i, k, `^([a-z])([a-z\d-]{0,30})([a-z\d])$`); !m { + errors = append(errors, fmt.Errorf("%s can contain only lowercase letters, numbers and hyphens, begin with a letter, end with a letter or number", k)) + } + } + + return nil, errors +} + +func ValidateConfigServerURI(i interface{}, k string) (_ []string, errors []error) { + v, ok := i.(string) + if !ok { + return nil, append(errors, fmt.Errorf("expected type of %s to be string", k)) + } + + // the config server URI should be started with http://, https://, git@, or ssh:// + if !strings.HasPrefix(v, "http://") && + !strings.HasPrefix(v, "https://") && + !strings.HasPrefix(v, "git@") && + !strings.HasPrefix(v, "ssh://") { + errors = append(errors, fmt.Errorf("%s should be started with http://, https://, git@, or ssh://", k)) + } + return nil, errors +} diff --git a/azurerm/internal/services/appplatform/validate_test.go b/azurerm/internal/services/appplatform/validate_test.go new file mode 100644 index 0000000000000..3bf1e6ce674f7 --- /dev/null +++ b/azurerm/internal/services/appplatform/validate_test.go @@ -0,0 +1,61 @@ +package appplatform + +import "testing" + +func TestValidateSpringCloudName(t *testing.T) { + testData := []struct { + input string + expected bool + }{ + { + // empty + input: "", + expected: false, + }, + { + // basic example + input: "ab-c", + expected: true, + }, + { + // can't start with a number + input: "1abc", + expected: false, + }, + { + // can't contain underscore + input: "ab_c", + expected: false, + }, + { + // can't end with hyphen + input: "abc-", + expected: false, + }, + { + // can not short than 4 characters + input: "abc", + expected: false, + }, + { + // 32 chars + input: "abcdefghijklmnopqrstuvwxyzabcdef", + expected: true, + }, + { + // 33 chars + input: "abcdefghijklmnopqrstuvwxyzabcdefg", + expected: false, + }, + } + + for _, v := range testData { + t.Logf("[DEBUG] Testing %q..", v.input) + + _, errors := ValidateSpringCloudName(v.input, "name") + actual := len(errors) == 0 + if v.expected != actual { + t.Fatalf("Expected %t but got %t", v.expected, actual) + } + } +} diff --git a/azurerm/provider.go b/azurerm/provider.go index f8af169432806..3ab6bd9f73954 100644 --- a/azurerm/provider.go +++ b/azurerm/provider.go @@ -136,6 +136,7 @@ func Provider() terraform.ResourceProvider { "azurerm_shared_image_version": dataSourceArmSharedImageVersion(), "azurerm_shared_image": dataSourceArmSharedImage(), "azurerm_snapshot": dataSourceArmSnapshot(), + "azurerm_spring_cloud": dataSourceArmSpringCloud(), "azurerm_sql_server": dataSourceSqlServer(), "azurerm_sql_database": dataSourceSqlDatabase(), "azurerm_stream_analytics_job": dataSourceArmStreamAnalyticsJob(), @@ -433,6 +434,7 @@ func Provider() terraform.ResourceProvider { "azurerm_shared_image": resourceArmSharedImage(), "azurerm_signalr_service": resourceArmSignalRService(), "azurerm_snapshot": resourceArmSnapshot(), + "azurerm_spring_cloud": resourceArmSpringCloud(), "azurerm_sql_active_directory_administrator": resourceArmSqlAdministrator(), "azurerm_sql_database": resourceArmSqlDatabase(), "azurerm_sql_elasticpool": resourceArmSqlElasticPool(), diff --git a/azurerm/resource_arm_spring_cloud.go b/azurerm/resource_arm_spring_cloud.go new file mode 100644 index 0000000000000..7d64d03142f17 --- /dev/null +++ b/azurerm/resource_arm_spring_cloud.go @@ -0,0 +1,514 @@ +package azurerm + +import ( + "fmt" + "log" + + "github.com/Azure/azure-sdk-for-go/profiles/preview/preview/appplatform/mgmt/appplatform" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/response" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/validate" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/features" + azappplatform "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/appplatform" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tags" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func resourceArmSpringCloud() *schema.Resource { + return &schema.Resource{ + Create: resourceArmSpringCloudCreate, + Read: resourceArmSpringCloudRead, + Update: resourceArmSpringCloudUpdate, + Delete: resourceArmSpringCloudDelete, + + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: azappplatform.ValidateSpringCloudName, + }, + + // Spring Cloud Service only supports following locations, we are still supporting more locations (Wednesday, November 20, 2019 4:20 PM): + // `East US`, `Southeast Asia`, `West Europe`, `West US 2` + "location": azure.SchemaLocation(), + + "resource_group": azure.SchemaResourceGroupNameDiffSuppress(), + + "config_server": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "uri": { + Type: schema.TypeString, + Required: true, + ValidateFunc: azappplatform.ValidateConfigServerURI, + }, + "host_key": { + Type: schema.TypeString, + Optional: true, + }, + "host_key_algorithm": { + Type: schema.TypeString, + Optional: true, + }, + "label": { + Type: schema.TypeString, + Optional: true, + }, + "password": { + Type: schema.TypeString, + Optional: true, + }, + "private_key": { + Type: schema.TypeString, + Optional: true, + }, + "repositories": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validate.NoEmptyStrings, + }, + "uri": { + Type: schema.TypeString, + Required: true, + ValidateFunc: azappplatform.ValidateConfigServerURI, + }, + "host_key": { + Type: schema.TypeString, + Optional: true, + }, + "host_key_algorithm": { + Type: schema.TypeString, + Optional: true, + }, + "label": { + Type: schema.TypeString, + Optional: true, + }, + "password": { + Type: schema.TypeString, + Optional: true, + }, + "pattern": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "private_key": { + Type: schema.TypeString, + Optional: true, + }, + "search_paths": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "strict_host_key_checking": { + Type: schema.TypeBool, + Optional: true, + }, + "username": { + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + "search_paths": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "strict_host_key_checking": { + Type: schema.TypeBool, + Optional: true, + }, + "username": { + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + + "config_server_error": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "code": { + Type: schema.TypeString, + Computed: true, + }, + "message": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + + "service_id": { + Type: schema.TypeString, + Computed: true, + }, + + "version": { + Type: schema.TypeInt, + Computed: true, + }, + + "tags": tags.Schema(), + }, + } +} + +func resourceArmSpringCloudCreate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).AppPlatform.ServicesClient + ctx := meta.(*ArmClient).StopContext + + name := d.Get("name").(string) + resourceGroup := d.Get("resource_group").(string) + + if features.ShouldResourcesBeImported() && d.IsNewResource() { + existing, err := client.Get(ctx, resourceGroup, name) + if err != nil { + if !utils.ResponseWasNotFound(existing.Response) { + return fmt.Errorf("Error checking for present of existing Spring Cloud %q (Resource Group %q): %+v", name, resourceGroup, err) + } + } + if existing.ID != nil && *existing.ID != "" { + return tf.ImportAsExistsError("azurerm_spring_cloud", *existing.ID) + } + } + + location := azure.NormalizeLocation(d.Get("location").(string)) + t := d.Get("tags").(map[string]interface{}) + + resource := appplatform.ServiceResource{ + Location: utils.String(location), + Properties: &appplatform.ClusterResourceProperties{ + ConfigServerProperties: &appplatform.ConfigServerProperties{ + ConfigServer: &appplatform.ConfigServerSettings{}, + }, + }, + Tags: tags.Expand(t), + } + + if v, ok := d.GetOk("config_server"); ok { + configServer := v.([]interface{}) + resource.Properties.ConfigServerProperties.ConfigServer.GitProperty = expandArmSpringCloudConfigServerGitProperty(configServer) + } + + future, err := client.CreateOrUpdate(ctx, resourceGroup, name, &resource) + if err != nil { + return fmt.Errorf("Error creating Spring Cloud %q (Resource Group %q): %+v", name, resourceGroup, err) + } + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("Error waiting for creation of Spring Cloud %q (Resource Group %q): %+v", name, resourceGroup, err) + } + + resp, err := client.Get(ctx, resourceGroup, name) + if err != nil { + return fmt.Errorf("Error retrieving Spring Cloud %q (Resource Group %q): %+v", name, resourceGroup, err) + } + if resp.ID == nil { + return fmt.Errorf("Cannot read Spring Cloud %q (Resource Group %q) ID", name, resourceGroup) + } + d.SetId(*resp.ID) + + return resourceArmSpringCloudRead(d, meta) +} + +func resourceArmSpringCloudRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).AppPlatform.ServicesClient + ctx := meta.(*ArmClient).StopContext + + id, err := azure.ParseAzureResourceID(d.Id()) + if err != nil { + return err + } + resourceGroup := id.ResourceGroup + name := id.Path["Spring"] + + resp, err := client.Get(ctx, resourceGroup, name) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + log.Printf("[INFO] Spring Cloud %q does not exist - removing from state", d.Id()) + d.SetId("") + return nil + } + return fmt.Errorf("Error reading Spring Cloud %q (Resource Group %q): %+v", name, resourceGroup, err) + } + + d.Set("name", resp.Name) + d.Set("resource_group", resourceGroup) + if location := resp.Location; location != nil { + d.Set("location", azure.NormalizeLocation(*location)) + } + if clusterResourceProperties := resp.Properties; clusterResourceProperties != nil { + if clusterResourceProperties.ConfigServerProperties != nil { + if err := d.Set("config_server_error", flattenArmSpringCloudError(clusterResourceProperties.ConfigServerProperties.Error)); err != nil { + return fmt.Errorf("Error setting `error`: %+v", err) + } + + if clusterResourceProperties.ConfigServerProperties.ConfigServer != nil { + if err := d.Set("config_server", flattenArmSpringCloudConfigServerGitProperty(clusterResourceProperties.ConfigServerProperties.ConfigServer.GitProperty)); err != nil { + return fmt.Errorf("Error setting `config_server`: %+v", err) + } + } + } + + d.Set("service_id", clusterResourceProperties.ServiceID) + d.Set("version", int(*clusterResourceProperties.Version)) + } + + return tags.FlattenAndSet(d, resp.Tags) +} + +func resourceArmSpringCloudUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).AppPlatform.ServicesClient + ctx := meta.(*ArmClient).StopContext + + name := d.Get("name").(string) + resourceGroup := d.Get("resource_group").(string) + t := d.Get("tags").(map[string]interface{}) + + resource := appplatform.ServiceResource{ + Properties: &appplatform.ClusterResourceProperties{ + ConfigServerProperties: &appplatform.ConfigServerProperties{ + ConfigServer: &appplatform.ConfigServerSettings{}, + }, + }, + Tags: tags.Expand(t), + } + + if v, ok := d.GetOk("config_server"); ok { + configServer := v.([]interface{}) + resource.Properties.ConfigServerProperties.ConfigServer.GitProperty = expandArmSpringCloudConfigServerGitProperty(configServer) + } + + future, err := client.Update(ctx, resourceGroup, name, &resource) + if err != nil { + return fmt.Errorf("Error updating Spring Cloud %q (Resource Group %q): %+v", name, resourceGroup, err) + } + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("Error waiting for update of Spring Cloud %q (Resource Group %q): %+v", name, resourceGroup, err) + } + + return resourceArmSpringCloudRead(d, meta) +} + +func resourceArmSpringCloudDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).AppPlatform.ServicesClient + ctx := meta.(*ArmClient).StopContext + + id, err := azure.ParseAzureResourceID(d.Id()) + if err != nil { + return err + } + resourceGroup := id.ResourceGroup + name := id.Path["Spring"] + + future, err := client.Delete(ctx, resourceGroup, name) + if err != nil { + if response.WasNotFound(future.Response()) { + return nil + } + return fmt.Errorf("Error deleting Spring Cloud %q (Resource Group %q): %+v", name, resourceGroup, err) + } + + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + if !response.WasNotFound(future.Response()) { + return fmt.Errorf("Error waiting for deleting Spring Cloud %q (Resource Group %q): %+v", name, resourceGroup, err) + } + } + + return nil +} + +func expandArmSpringCloudConfigServerGitProperty(input []interface{}) *appplatform.ConfigServerGitProperty { + if len(input) == 0 { + return nil + } + v := input[0].(map[string]interface{}) + + repositories := v["repositories"].([]interface{}) + uri := v["uri"].(string) + label := v["label"].(string) + searchPaths := v["search_paths"].([]interface{}) + username := v["username"].(string) + password := v["password"].(string) + hostKey := v["host_key"].(string) + hostKeyAlgorithm := v["host_key_algorithm"].(string) + privateKey := v["private_key"].(string) + strictHostKeyChecking := v["strict_host_key_checking"].(bool) + + result := appplatform.ConfigServerGitProperty{ + HostKey: utils.String(hostKey), + HostKeyAlgorithm: utils.String(hostKeyAlgorithm), + Label: utils.String(label), + Password: utils.String(password), + PrivateKey: utils.String(privateKey), + Repositories: expandArmSpringCloudGitPatternRepository(repositories), + SearchPaths: utils.ExpandStringSlice(searchPaths), + StrictHostKeyChecking: utils.Bool(strictHostKeyChecking), + URI: utils.String(uri), + Username: utils.String(username), + } + return &result +} + +func expandArmSpringCloudGitPatternRepository(input []interface{}) *[]appplatform.GitPatternRepository { + results := make([]appplatform.GitPatternRepository, 0) + for _, item := range input { + v := item.(map[string]interface{}) + name := v["name"].(string) + pattern := v["pattern"].([]interface{}) + uri := v["uri"].(string) + label := v["label"].(string) + searchPaths := v["search_paths"].([]interface{}) + username := v["username"].(string) + password := v["password"].(string) + hostKey := v["host_key"].(string) + hostKeyAlgorithm := v["host_key_algorithm"].(string) + privateKey := v["private_key"].(string) + strictHostKeyChecking := v["strict_host_key_checking"].(bool) + + result := appplatform.GitPatternRepository{ + HostKey: utils.String(hostKey), + HostKeyAlgorithm: utils.String(hostKeyAlgorithm), + Label: utils.String(label), + Name: utils.String(name), + Password: utils.String(password), + Pattern: utils.ExpandStringSlice(pattern), + PrivateKey: utils.String(privateKey), + SearchPaths: utils.ExpandStringSlice(searchPaths), + StrictHostKeyChecking: utils.Bool(strictHostKeyChecking), + URI: utils.String(uri), + Username: utils.String(username), + } + + results = append(results, result) + } + return &results +} + +func flattenArmSpringCloudError(input *appplatform.Error) []interface{} { + if input == nil { + return make([]interface{}, 0) + } + + result := make(map[string]interface{}) + + if code := input.Code; code != nil { + result["code"] = *code + } + if message := input.Message; message != nil { + result["message"] = *message + } + + return []interface{}{result} +} + +func flattenArmSpringCloudConfigServerGitProperty(input *appplatform.ConfigServerGitProperty) []interface{} { + if input == nil { + return make([]interface{}, 0) + } + + result := make(map[string]interface{}) + + if hostKey := input.HostKey; hostKey != nil { + result["host_key"] = *hostKey + } + if hostKeyAlgorithm := input.HostKeyAlgorithm; hostKeyAlgorithm != nil { + result["host_key_algorithm"] = *hostKeyAlgorithm + } + if label := input.Label; label != nil { + result["label"] = *label + } + if password := input.Password; password != nil { + result["password"] = *password + } + if privateKey := input.PrivateKey; privateKey != nil { + result["private_key"] = *privateKey + } + result["repositories"] = flattenArmSpringCloudGitPatternRepository(input.Repositories) + result["search_paths"] = utils.FlattenStringSlice(input.SearchPaths) + if strictHostKeyChecking := input.StrictHostKeyChecking; strictHostKeyChecking != nil { + result["strict_host_key_checking"] = *strictHostKeyChecking + } + if uri := input.URI; uri != nil { + result["uri"] = *uri + } + if username := input.Username; username != nil { + result["username"] = *username + } + + return []interface{}{result} +} + +func flattenArmSpringCloudGitPatternRepository(input *[]appplatform.GitPatternRepository) []interface{} { + results := make([]interface{}, 0) + if input == nil { + return results + } + + for _, item := range *input { + v := make(map[string]interface{}) + + if name := item.Name; name != nil { + v["name"] = *name + } + if hostKey := item.HostKey; hostKey != nil { + v["host_key"] = *hostKey + } + if hostKeyAlgorithm := item.HostKeyAlgorithm; hostKeyAlgorithm != nil { + v["host_key_algorithm"] = *hostKeyAlgorithm + } + if label := item.Label; label != nil { + v["label"] = *label + } + if password := item.Password; password != nil { + v["password"] = *password + } + v["pattern"] = utils.FlattenStringSlice(item.Pattern) + if privateKey := item.PrivateKey; privateKey != nil { + v["private_key"] = *privateKey + } + v["search_paths"] = utils.FlattenStringSlice(item.SearchPaths) + if strictHostKeyChecking := item.StrictHostKeyChecking; strictHostKeyChecking != nil { + v["strict_host_key_checking"] = *strictHostKeyChecking + } + if uri := item.URI; uri != nil { + v["uri"] = *uri + } + if username := item.Username; username != nil { + v["username"] = *username + } + + results = append(results, v) + } + + return results +} diff --git a/azurerm/resource_arm_spring_cloud_test.go b/azurerm/resource_arm_spring_cloud_test.go new file mode 100644 index 0000000000000..df4467f7a676f --- /dev/null +++ b/azurerm/resource_arm_spring_cloud_test.go @@ -0,0 +1,219 @@ +package azurerm + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func TestAccAzureRMSpringCloud_basic(t *testing.T) { + resourceName := "azurerm_spring_cloud.test" + ri := tf.AccRandTimeInt() + location := testLocation() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMSpringCloudDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMSpringCloud_basic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMSpringCloudExists(resourceName), + resource.TestCheckResourceAttrSet(resourceName, "id"), + resource.TestCheckResourceAttrSet(resourceName, "service_id"), + resource.TestCheckResourceAttrSet(resourceName, "version"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.env", "test"), + resource.TestCheckResourceAttr(resourceName, "config_server.%", "0"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAzureRMSpringCloud_update(t *testing.T) { + resourceName := "azurerm_spring_cloud.test" + ri := tf.AccRandTimeInt() + location := testLocation() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMSpringCloudDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMSpringCloud_basic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMSpringCloudExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.env", "test"), + resource.TestCheckResourceAttr(resourceName, "config_server.%", "0"), + ), + }, + { + Config: testAccAzureRMSpringCloud_complete(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMSpringCloudExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.env", "test"), + resource.TestCheckResourceAttr(resourceName, "tags.version", "1"), + resource.TestCheckResourceAttrSet(resourceName, "config_server"), + resource.TestCheckResourceAttr(resourceName, "config_server.uri", "https://github.com/Azure-Samples/piggymetrics"), + resource.TestCheckResourceAttr(resourceName, "config_server.label", "config"), + ), + }, + { + Config: testAccAzureRMSpringCloud_basic(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMSpringCloudExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.env", "test"), + resource.TestCheckResourceAttr(resourceName, "config_server.%", "0"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAzureRMPrivateLinkService_complete(t *testing.T) { + resourceName := "azurerm_spring_cloud.test" + ri := tf.AccRandTimeInt() + location := testLocation() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMSpringCloudDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMSpringCloud_complete(ri, location), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMSpringCloudExists(resourceName), + resource.TestCheckResourceAttrSet(resourceName, "id"), + resource.TestCheckResourceAttrSet(resourceName, "service_id"), + resource.TestCheckResourceAttrSet(resourceName, "version"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.env", "test"), + resource.TestCheckResourceAttr(resourceName, "tags.version", "1"), + resource.TestCheckResourceAttr(resourceName, "config_server_error.#", "0"), + resource.TestCheckResourceAttrSet(resourceName, "config_server"), + resource.TestCheckResourceAttr(resourceName, "config_server.uri", "https://github.com/Azure-Samples/piggymetrics"), + resource.TestCheckResourceAttr(resourceName, "config_server.label", "config"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testCheckAzureRMSpringCloudExists(resourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Spring Cloud not found: %s", resourceName) + } + + name := rs.Primary.Attributes["name"] + resourceGroup := rs.Primary.Attributes["resource_group"] + + client := testAccProvider.Meta().(*ArmClient).AppPlatform.ServicesClient + ctx := testAccProvider.Meta().(*ArmClient).StopContext + + if resp, err := client.Get(ctx, resourceGroup, name); err != nil { + if utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Bad: Spring Cloud Service %q (Resource Group %q) does not exist", name, resourceGroup) + } + return fmt.Errorf("Bad: Get on AppPlatform.ServicesClient: %+v", err) + } + + return nil + } +} + +func testCheckAzureRMSpringCloudDestroy(s *terraform.State) error { + client := testAccProvider.Meta().(*ArmClient).AppPlatform.ServicesClient + ctx := testAccProvider.Meta().(*ArmClient).StopContext + + for _, rs := range s.RootModule().Resources { + if rs.Type != "azurerm_spring_cloud" { + continue + } + + name := rs.Primary.Attributes["name"] + resourceGroup := rs.Primary.Attributes["resource_group"] + + if resp, err := client.Get(ctx, resourceGroup, name); err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Bad: Get on AppPlatform.ServicesClient: %+v", err) + } + } + + return nil + } + + return nil +} + +func testAccAzureRMSpringCloud_basic(rInt int, location string) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_spring_cloud" "test" { + name = "acctestsc-%d" + location = azurerm_resource_group.test.location + resource_group = azurerm_resource_group.test.name + + tags = { + env = "test" + } +} + +`, rInt, location, rInt) +} + +func testAccAzureRMSpringCloud_complete(rInt int, location string) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_spring_cloud" "test" { + name = "acctestsc-%d" + location = azurerm_resource_group.test.location + resource_group = azurerm_resource_group.test.name + + config_server { + uri = "https://github.com/Azure-Samples/piggymetrics" + label = "config" + } + + tags = { + env = "test" + version = "1" + } +} +`, rInt, location, rInt) +} diff --git a/website/azurerm.erb b/website/azurerm.erb index b1db55962d9f8..134244b68031e 100644 --- a/website/azurerm.erb +++ b/website/azurerm.erb @@ -386,6 +386,10 @@ azurerm_snapshot +
  • + azurerm_spring_cloud +
  • +
  • azurerm_sql_database
  • @@ -622,6 +626,15 @@ +
  • + App Platform + +
  • +
  • Authorization Resources