diff --git a/internal/services/springcloud/registration.go b/internal/services/springcloud/registration.go index fdb6cc476ccf..352986daffb6 100644 --- a/internal/services/springcloud/registration.go +++ b/internal/services/springcloud/registration.go @@ -43,6 +43,7 @@ func (r Registration) SupportedResources() map[string]*pluginsdk.Resource { "azurerm_spring_cloud_app_mysql_association": resourceSpringCloudAppMysqlAssociation(), "azurerm_spring_cloud_app_redis_association": resourceSpringCloudAppRedisAssociation(), "azurerm_spring_cloud_builder": resourceSpringCloudBuildServiceBuilder(), + "azurerm_spring_cloud_build_deployment": resourceSpringCloudBuildDeployment(), "azurerm_spring_cloud_build_pack_binding": resourceSpringCloudBuildPackBinding(), "azurerm_spring_cloud_certificate": resourceSpringCloudCertificate(), "azurerm_spring_cloud_configuration_service": resourceSpringCloudConfigurationService(), diff --git a/internal/services/springcloud/spring_cloud_build_deployment_resource.go b/internal/services/springcloud/spring_cloud_build_deployment_resource.go new file mode 100644 index 000000000000..a36712e5233d --- /dev/null +++ b/internal/services/springcloud/spring_cloud_build_deployment_resource.go @@ -0,0 +1,266 @@ +package springcloud + +import ( + "fmt" + "log" + "time" + + "github.com/Azure/azure-sdk-for-go/services/preview/appplatform/mgmt/2022-03-01-preview/appplatform" + "github.com/hashicorp/terraform-provider-azurerm/helpers/tf" + "github.com/hashicorp/terraform-provider-azurerm/internal/clients" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/springcloud/parse" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/springcloud/validate" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/validation" + "github.com/hashicorp/terraform-provider-azurerm/internal/timeouts" + "github.com/hashicorp/terraform-provider-azurerm/utils" +) + +func resourceSpringCloudBuildDeployment() *pluginsdk.Resource { + return &pluginsdk.Resource{ + Create: resourceSpringCloudBuildDeploymentCreateUpdate, + Read: resourceSpringCloudBuildDeploymentRead, + Update: resourceSpringCloudBuildDeploymentCreateUpdate, + Delete: resourceSpringCloudBuildDeploymentDelete, + + Importer: pluginsdk.ImporterValidatingResourceId(func(id string) error { + _, err := parse.SpringCloudDeploymentID(id) + return err + }), + + Timeouts: &pluginsdk.ResourceTimeout{ + Create: pluginsdk.DefaultTimeout(30 * time.Minute), + Read: pluginsdk.DefaultTimeout(5 * time.Minute), + Update: pluginsdk.DefaultTimeout(30 * time.Minute), + Delete: pluginsdk.DefaultTimeout(30 * time.Minute), + }, + + Schema: map[string]*pluginsdk.Schema{ + "name": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.SpringCloudDeploymentName, + }, + + "spring_cloud_app_id": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.SpringCloudAppID, + }, + + "build_result_id": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "environment_variables": { + Type: pluginsdk.TypeMap, + Optional: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + ValidateFunc: validation.StringIsNotEmpty, + }, + }, + + "instance_count": { + Type: pluginsdk.TypeInt, + Optional: true, + Default: 1, + ValidateFunc: validation.IntBetween(1, 500), + }, + + "quota": { + Type: pluginsdk.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "cpu": { + Type: pluginsdk.TypeString, + Optional: true, + Computed: true, + ValidateFunc: validation.StringInSlice([]string{ + "500m", + "1", + "2", + "3", + "4", + }, false), + }, + + "memory": { + Type: pluginsdk.TypeString, + Optional: true, + Computed: true, + ValidateFunc: validation.StringInSlice([]string{ + "512Mi", + "1Gi", + "2Gi", + "3Gi", + "4Gi", + "5Gi", + "6Gi", + "7Gi", + "8Gi", + }, false), + }, + }, + }, + }, + }, + } +} + +func resourceSpringCloudBuildDeploymentCreateUpdate(d *pluginsdk.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).AppPlatform.DeploymentsClient + servicesClient := meta.(*clients.Client).AppPlatform.ServicesClient + subscriptionId := meta.(*clients.Client).Account.SubscriptionId + ctx, cancel := timeouts.ForCreate(meta.(*clients.Client).StopContext, d) + defer cancel() + + appId, err := parse.SpringCloudAppID(d.Get("spring_cloud_app_id").(string)) + if err != nil { + return err + } + + id := parse.NewSpringCloudDeploymentID(subscriptionId, appId.ResourceGroup, appId.SpringName, appId.AppName, d.Get("name").(string)) + + if d.IsNewResource() { + existing, err := client.Get(ctx, id.ResourceGroup, id.SpringName, id.AppName, id.DeploymentName) + if err != nil { + if !utils.ResponseWasNotFound(existing.Response) { + return fmt.Errorf("checking for presence of existing %s: %+v", id, err) + } + } + if !utils.ResponseWasNotFound(existing.Response) { + return tf.ImportAsExistsError("azurerm_spring_cloud_build_deployment", id.ID()) + } + } + + service, err := servicesClient.Get(ctx, appId.ResourceGroup, appId.SpringName) + if err != nil { + return fmt.Errorf("checking for presence of existing %s: %+v", appId, err) + } + if service.Sku == nil || service.Sku.Name == nil || service.Sku.Tier == nil { + return fmt.Errorf("invalid `sku` for Spring Cloud Service %q (Resource Group %q)", appId.SpringName, appId.ResourceGroup) + } + + deployment := appplatform.DeploymentResource{ + Sku: &appplatform.Sku{ + Name: service.Sku.Name, + Tier: service.Sku.Tier, + Capacity: utils.Int32(int32(d.Get("instance_count").(int))), + }, + Properties: &appplatform.DeploymentResourceProperties{ + Source: appplatform.BuildResultUserSourceInfo{ + BuildResultID: utils.String(d.Get("build_result_id").(string)), + }, + DeploymentSettings: &appplatform.DeploymentSettings{ + EnvironmentVariables: expandSpringCloudDeploymentEnvironmentVariables(d.Get("environment_variables").(map[string]interface{})), + ResourceRequests: expandSpringCloudBuildDeploymentResourceRequests(d.Get("quota").([]interface{})), + }, + }, + } + + future, err := client.CreateOrUpdate(ctx, id.ResourceGroup, id.SpringName, id.AppName, id.DeploymentName, deployment) + if err != nil { + return fmt.Errorf("creating %s: %+v", id, err) + } + + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("waiting for creation of %s: %+v", id, err) + } + + d.SetId(id.ID()) + + return resourceSpringCloudBuildDeploymentRead(d, meta) +} + +func resourceSpringCloudBuildDeploymentRead(d *pluginsdk.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).AppPlatform.DeploymentsClient + ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := parse.SpringCloudDeploymentID(d.Id()) + if err != nil { + return err + } + + resp, err := client.Get(ctx, id.ResourceGroup, id.SpringName, id.AppName, id.DeploymentName) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + log.Printf("[INFO] Spring Cloud deployment %q does not exist - removing from state", d.Id()) + d.SetId("") + return nil + } + return fmt.Errorf("reading %s: %+v", id, err) + } + + d.Set("name", id.DeploymentName) + d.Set("spring_cloud_app_id", parse.NewSpringCloudAppID(id.SubscriptionId, id.ResourceGroup, id.SpringName, id.AppName).ID()) + if resp.Sku != nil { + d.Set("instance_count", resp.Sku.Capacity) + } + if resp.Properties != nil { + if settings := resp.Properties.DeploymentSettings; settings != nil { + d.Set("environment_variables", flattenSpringCloudDeploymentEnvironmentVariables(settings.EnvironmentVariables)) + if err := d.Set("quota", flattenSpringCloudDeploymentResourceRequests(settings.ResourceRequests)); err != nil { + return fmt.Errorf("setting `quota`: %+v", err) + } + } + if source, ok := resp.Properties.Source.AsBuildResultUserSourceInfo(); ok && source != nil { + d.Set("build_result_id", source.BuildResultID) + } + } + + return nil +} + +func resourceSpringCloudBuildDeploymentDelete(d *pluginsdk.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).AppPlatform.DeploymentsClient + ctx, cancel := timeouts.ForDelete(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := parse.SpringCloudDeploymentID(d.Id()) + if err != nil { + return err + } + + future, err := client.Delete(ctx, id.ResourceGroup, id.SpringName, id.AppName, id.DeploymentName) + if err != nil { + return fmt.Errorf("deleting %s: %+v", id, err) + } + if err := future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("waiting for deletion of %s: %+v", id, err) + } + + return nil +} + +func expandSpringCloudBuildDeploymentResourceRequests(input []interface{}) *appplatform.ResourceRequests { + if len(input) == 0 || input[0] == nil { + return nil + } + cpuResult := "1" + memResult := "1Gi" + + v := input[0].(map[string]interface{}) + if cpuNew := v["cpu"].(string); cpuNew != "" { + cpuResult = cpuNew + } + + if memoryNew := v["memory"].(string); memoryNew != "" { + memResult = memoryNew + } + + result := appplatform.ResourceRequests{ + CPU: utils.String(cpuResult), + Memory: utils.String(memResult), + } + + return &result +} diff --git a/internal/services/springcloud/spring_cloud_build_deployment_resource_test.go b/internal/services/springcloud/spring_cloud_build_deployment_resource_test.go new file mode 100644 index 000000000000..aef116efedfb --- /dev/null +++ b/internal/services/springcloud/spring_cloud_build_deployment_resource_test.go @@ -0,0 +1,176 @@ +package springcloud_test + +import ( + "context" + "fmt" + "testing" + + "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance" + "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance/check" + "github.com/hashicorp/terraform-provider-azurerm/internal/clients" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/springcloud/parse" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" + "github.com/hashicorp/terraform-provider-azurerm/utils" +) + +type SpringCloudBuildDeploymentResource struct{} + +func TestAccSpringCloudBuildDeployment_basic(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_spring_cloud_build_deployment", "test") + r := SpringCloudBuildDeploymentResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccSpringCloudBuildDeployment_requiresImport(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_spring_cloud_build_deployment", "test") + r := SpringCloudBuildDeploymentResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.RequiresImportErrorStep(r.requiresImport), + }) +} + +func TestAccSpringCloudBuildDeployment_complete(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_spring_cloud_build_deployment", "test") + r := SpringCloudBuildDeploymentResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.complete(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccSpringCloudBuildDeployment_update(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_spring_cloud_build_deployment", "test") + r := SpringCloudBuildDeploymentResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.complete(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func (r SpringCloudBuildDeploymentResource) Exists(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { + id, err := parse.SpringCloudDeploymentID(state.ID) + if err != nil { + return nil, err + } + + resp, err := clients.AppPlatform.DeploymentsClient.Get(ctx, id.ResourceGroup, id.SpringName, id.AppName, id.DeploymentName) + if err != nil { + return nil, fmt.Errorf("reading Spring Cloud Deployment %q (Spring Cloud Service %q / App %q / Resource Group %q): %+v", id.DeploymentName, id.SpringName, id.AppName, id.ResourceGroup, err) + } + + return utils.Bool(resp.Properties != nil), nil +} + +func (r SpringCloudBuildDeploymentResource) basic(data acceptance.TestData) string { + return fmt.Sprintf(` +%s + +resource "azurerm_spring_cloud_build_deployment" "test" { + name = "acctest-scjd%s" + spring_cloud_app_id = azurerm_spring_cloud_app.test.id + build_result_id = "" +} +`, r.template(data), data.RandomString) +} + +func (r SpringCloudBuildDeploymentResource) requiresImport(data acceptance.TestData) string { + return fmt.Sprintf(` +%s + +resource "azurerm_spring_cloud_build_deployment" "import" { + name = azurerm_spring_cloud_build_deployment.test.name + spring_cloud_app_id = azurerm_spring_cloud_build_deployment.test.spring_cloud_app_id + build_result_id = "" +} +`, r.basic(data)) +} + +func (r SpringCloudBuildDeploymentResource) complete(data acceptance.TestData) string { + return fmt.Sprintf(` +%s + +resource "azurerm_spring_cloud_build_deployment" "test" { + name = "acctest-scjd%s" + spring_cloud_app_id = azurerm_spring_cloud_app.test.id + build_result_id = "" + instance_count = 2 + + environment_variables = { + "Foo" : "Bar" + "Env" : "Staging" + } + quota { + cpu = "2" + memory = "2Gi" + } +} +`, r.template(data), data.RandomString) +} + +func (SpringCloudBuildDeploymentResource) template(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-spring-%d" + location = "%s" +} + +resource "azurerm_spring_cloud_service" "test" { + name = "acctest-sc-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + sku_name = "E0" +} + +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 +} +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomInteger) +} diff --git a/website/docs/r/spring_cloud_build_deployment.html.markdown b/website/docs/r/spring_cloud_build_deployment.html.markdown new file mode 100644 index 000000000000..18cd81e2d483 --- /dev/null +++ b/website/docs/r/spring_cloud_build_deployment.html.markdown @@ -0,0 +1,106 @@ +--- +subcategory: "Spring Cloud" +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_spring_cloud_build_deployment" +description: |- + Manages a Spring Cloud Build Deployment. +--- + +# azurerm_spring_cloud_build_deployment + +Manages a Spring Cloud Build Deployment. + +## Example Usage + +```hcl +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "example" { + name = "example" + location = "West Europe" +} + +resource "azurerm_spring_cloud_service" "example" { + name = "example" + location = azurerm_resource_group.example.location + resource_group_name = azurerm_resource_group.example.name + sku_name = "E0" +} + +resource "azurerm_spring_cloud_app" "example" { + name = "example" + resource_group_name = azurerm_spring_cloud_service.example.resource_group_name + service_name = azurerm_spring_cloud_service.example.name +} + + +resource "azurerm_spring_cloud_build_deployment" "example" { + name = "example" + spring_cloud_app_id = azurerm_spring_cloud_app.example.id + build_result_id = "" + instance_count = 2 + environment_variables = { + "Foo" : "Bar" + "Env" : "Staging" + } + quota { + cpu = "2" + memory = "4Gi" + } +} +``` + +## Arguments Reference + +The following arguments are supported: + +* `name` - (Required) The name which should be used for this Spring Cloud Build Deployment. Changing this forces a new Spring Cloud Build Deployment to be created. + +* `spring_cloud_app_id` - (Required) The ID of the Spring Cloud Service. Changing this forces a new Spring Cloud Build Deployment to be created. + +* `build_result_id` - (Required) The ID of the Spring Cloud Build Result. + +--- + +* `environment_variables` - (Optional) Specifies the environment variables of the Spring Cloud Deployment as a map of key-value pairs. + +* `instance_count` - (Optional) Specifies the required instance count of the Spring Cloud Deployment. Possible Values are between `1` and `500`. Defaults to `1` if not specified. + +* `quota` - (Optional) A `quota` block as defined below. + +--- + +A `quota` block supports the following: + +* `cpu` - (Optional) Specifies the required cpu of the Spring Cloud Deployment. Possible Values are `500m`, `1`, `2`, `3` and `4`. Defaults to `1` if not specified. + +-> **Note:** `cpu` supports `500m` and `1` for Basic tier, `500m`, `1`, `2`, `3` and `4` for Standard tier. + +* `memory` - (Optional) Specifies the required memory size of the Spring Cloud Deployment. Possible Values are `512Mi`, `1Gi`, `2Gi`, `3Gi`, `4Gi`, `5Gi`, `6Gi`, `7Gi`, and `8Gi`. Defaults to `1Gi` if not specified. + +-> **Note:** `memory` supports `512Mi`, `1Gi` and `2Gi` for Basic tier, `512Mi`, `1Gi`, `2Gi`, `3Gi`, `4Gi`, `5Gi`, `6Gi`, `7Gi`, and `8Gi` for Standard tier. + +## Attributes Reference + +In addition to the Arguments listed above - the following Attributes are exported: + +* `id` - The ID of the Spring Cloud Build Deployment. + +## 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 Build Deployment. +* `read` - (Defaults to 5 minutes) Used when retrieving the Spring Cloud Build Deployment. +* `update` - (Defaults to 30 minutes) Used when updating the Spring Cloud Build Deployment. +* `delete` - (Defaults to 30 minutes) Used when deleting the Spring Cloud Build Deployment. + +## Import + +Spring Cloud Build Deployments can be imported using the `resource id`, e.g. + +```shell +terraform import azurerm_spring_cloud_build_deployment.example /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.AppPlatform/Spring/spring1/apps/app1/deployments/deploy1 +```