diff --git a/internal/services/containerapps/container_app_environment_resource.go b/internal/services/containerapps/container_app_environment_resource.go index d5c10f926977..a8edc920dace 100644 --- a/internal/services/containerapps/container_app_environment_resource.go +++ b/internal/services/containerapps/container_app_environment_resource.go @@ -17,6 +17,7 @@ import ( "github.com/hashicorp/go-azure-sdk/resource-manager/containerapps/2023-05-01/managedenvironments" "github.com/hashicorp/go-azure-sdk/resource-manager/operationalinsights/2020-08-01/workspaces" "github.com/hashicorp/terraform-provider-azurerm/internal/sdk" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/containerapps/helpers" "github.com/hashicorp/terraform-provider-azurerm/internal/services/containerapps/validate" "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" "github.com/hashicorp/terraform-provider-azurerm/internal/tf/validation" @@ -25,15 +26,16 @@ import ( type ContainerAppEnvironmentResource struct{} type ContainerAppEnvironmentModel struct { - Name string `tfschema:"name"` - ResourceGroup string `tfschema:"resource_group_name"` - Location string `tfschema:"location"` - DaprApplicationInsightsConnectionString string `tfschema:"dapr_application_insights_connection_string"` - LogAnalyticsWorkspaceId string `tfschema:"log_analytics_workspace_id"` - InfrastructureSubnetId string `tfschema:"infrastructure_subnet_id"` - InternalLoadBalancerEnabled bool `tfschema:"internal_load_balancer_enabled"` - ZoneRedundant bool `tfschema:"zone_redundancy_enabled"` - Tags map[string]interface{} `tfschema:"tags"` + Name string `tfschema:"name"` + ResourceGroup string `tfschema:"resource_group_name"` + Location string `tfschema:"location"` + DaprApplicationInsightsConnectionString string `tfschema:"dapr_application_insights_connection_string"` + LogAnalyticsWorkspaceId string `tfschema:"log_analytics_workspace_id"` + InfrastructureSubnetId string `tfschema:"infrastructure_subnet_id"` + InternalLoadBalancerEnabled bool `tfschema:"internal_load_balancer_enabled"` + ZoneRedundant bool `tfschema:"zone_redundancy_enabled"` + Tags map[string]interface{} `tfschema:"tags"` + WorkloadProfiles []helpers.WorkloadProfileModel `tfschema:"workload_profile"` DefaultDomain string `tfschema:"default_domain"` DockerBridgeCidr string `tfschema:"docker_bridge_cidr"` @@ -104,6 +106,8 @@ func (r ContainerAppEnvironmentResource) Arguments() map[string]*pluginsdk.Schem Description: "Should the Container Environment operate in Internal Load Balancing Mode? Defaults to `false`. **Note:** can only be set to `true` if `infrastructure_subnet_id` is specified.", }, + "workload_profile": helpers.WorkloadProfileSchema(), + "zone_redundancy_enabled": { Type: pluginsdk.TypeBool, Optional: true, @@ -231,6 +235,8 @@ func (r ContainerAppEnvironmentResource) Create() sdk.ResourceFunc { managedEnvironment.Properties.VnetConfiguration.Internal = pointer.To(containerAppEnvironment.InternalLoadBalancerEnabled) } + managedEnvironment.Properties.WorkloadProfiles = helpers.ExpandWorkloadProfiles(containerAppEnvironment.WorkloadProfiles) + if err := client.CreateOrUpdateThenPoll(ctx, id, managedEnvironment); err != nil { return fmt.Errorf("creating %s: %+v", id, err) } @@ -279,6 +285,7 @@ func (r ContainerAppEnvironmentResource) Read() sdk.ResourceFunc { state.ZoneRedundant = pointer.From(props.ZoneRedundant) state.StaticIP = pointer.From(props.StaticIP) state.DefaultDomain = pointer.From(props.DefaultDomain) + state.WorkloadProfiles = helpers.FlattenWorkloadProfiles(props.WorkloadProfiles) } } diff --git a/internal/services/containerapps/container_app_environment_resource_test.go b/internal/services/containerapps/container_app_environment_resource_test.go index ecc8b8917fc0..e304eeaf018d 100644 --- a/internal/services/containerapps/container_app_environment_resource_test.go +++ b/internal/services/containerapps/container_app_environment_resource_test.go @@ -64,6 +64,50 @@ func TestAccContainerAppEnvironment_complete(t *testing.T) { }) } +func TestAccContainerAppEnvironment_withWorkloadProfile(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_container_app_environment", "test") + r := ContainerAppEnvironmentResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.completeWithWorkloadProfile(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep("log_analytics_workspace_id"), + }) +} + +func TestAccContainerAppEnvironment_updateWorkloadProfile(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_container_app_environment", "test") + r := ContainerAppEnvironmentResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.complete(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep("log_analytics_workspace_id"), + { + Config: r.completeWithWorkloadProfile(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep("log_analytics_workspace_id"), + { + Config: r.completeUpdate(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep("log_analytics_workspace_id"), + }) +} + func TestAccContainerAppEnvironment_completeUpdate(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_container_app_environment", "test") r := ContainerAppEnvironmentResource{} @@ -189,6 +233,59 @@ resource "azurerm_container_app_environment" "test" { `, r.templateVNet(data), data.RandomInteger) } +func (r ContainerAppEnvironmentResource) completeWithWorkloadProfile(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +%[1]s + +resource "azurerm_virtual_network" "test" { + name = "acctestvirtnet%[2]d" + address_space = ["10.0.0.0/16"] + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name +} + +resource "azurerm_subnet" "control" { + name = "control-plane" + resource_group_name = azurerm_resource_group.test.name + virtual_network_name = azurerm_virtual_network.test.name + address_prefixes = ["10.0.0.0/23"] + delegation { + name = "acctestdelegation%[2]d" + service_delegation { + actions = ["Microsoft.Network/virtualNetworks/subnets/join/action"] + name = "Microsoft.App/environments" + } + } +} + +resource "azurerm_container_app_environment" "test" { + name = "acctest-CAEnv%[2]d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + infrastructure_subnet_id = azurerm_subnet.control.id + + workload_profile { + maximum_count = 2 + minimum_count = 0 + name = "My-GP-01" + workload_profile_type = "D4" + } + + zone_redundancy_enabled = true + + tags = { + Foo = "Bar" + secret = "sauce" + } +} +`, r.template(data), data.RandomInteger) + +} + func (r ContainerAppEnvironmentResource) completeUpdate(data acceptance.TestData) string { return fmt.Sprintf(` provider "azurerm" { diff --git a/internal/services/containerapps/helpers/container_app_environment.go b/internal/services/containerapps/helpers/container_app_environment.go new file mode 100644 index 000000000000..736798edb5ae --- /dev/null +++ b/internal/services/containerapps/helpers/container_app_environment.go @@ -0,0 +1,113 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package helpers + +import ( + "strings" + + "github.com/hashicorp/go-azure-helpers/lang/pointer" + "github.com/hashicorp/go-azure-sdk/resource-manager/containerapps/2023-05-01/managedenvironments" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/validation" +) + +const consumption = "Consumption" + +type WorkloadProfileModel struct { + MaximumCount int `tfschema:"maximum_count"` + MinimumCount int `tfschema:"minimum_count"` + Name string `tfschema:"name"` + WorkloadProfileType string `tfschema:"workload_profile_type"` +} + +func WorkloadProfileSchema() *pluginsdk.Schema { + return &pluginsdk.Schema{ + Type: pluginsdk.TypeSet, + Optional: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "name": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "workload_profile_type": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + "D4", + "D8", + "D16", + "D32", + "E4", + "E8", + "E16", + "E32", + }, false), + }, + + "maximum_count": { + Type: pluginsdk.TypeInt, + Required: true, + }, + + "minimum_count": { + Type: pluginsdk.TypeInt, + Required: true, + }, + }, + }, + } +} + +func ExpandWorkloadProfiles(input []WorkloadProfileModel) *[]managedenvironments.WorkloadProfile { + if len(input) == 0 { + return nil + } + + result := make([]managedenvironments.WorkloadProfile, 0) + + for _, v := range input { + r := managedenvironments.WorkloadProfile{ + Name: v.Name, + WorkloadProfileType: v.WorkloadProfileType, + } + + if v.Name != consumption { + r.MaximumCount = pointer.To(int64(v.MaximumCount)) + r.MinimumCount = pointer.To(int64(v.MinimumCount)) + } + + result = append(result, r) + } + + result = append(result, managedenvironments.WorkloadProfile{ + Name: consumption, + WorkloadProfileType: consumption, + }) + + return &result +} + +func FlattenWorkloadProfiles(input *[]managedenvironments.WorkloadProfile) []WorkloadProfileModel { + if input == nil || len(*input) == 0 { + return []WorkloadProfileModel{} + } + result := make([]WorkloadProfileModel, 0) + + for _, v := range *input { + if strings.EqualFold(v.WorkloadProfileType, consumption) { + continue + } + result = append(result, WorkloadProfileModel{ + Name: v.Name, + MaximumCount: int(pointer.From(v.MaximumCount)), + MinimumCount: int(pointer.From(v.MinimumCount)), + WorkloadProfileType: v.WorkloadProfileType, + }) + } + + return result +} diff --git a/internal/services/paloalto/local_rulestack_data_source_test.go b/internal/services/paloalto/local_rulestack_data_source_test.go index aa7cb48cd9fd..a56016e691c9 100644 --- a/internal/services/paloalto/local_rulestack_data_source_test.go +++ b/internal/services/paloalto/local_rulestack_data_source_test.go @@ -29,7 +29,7 @@ func (d LocalRulestackDataSource) basic(data acceptance.TestData) string { return fmt.Sprintf(` %s -data "azurerm_palo_alto_local_rulestack" "test"{ +data "azurerm_palo_alto_local_rulestack" "test" { name = azurerm_palo_alto_local_rulestack.test.name resource_group_name = azurerm_palo_alto_local_rulestack.test.resource_group_name } diff --git a/website/docs/r/container_app_environment.html.markdown b/website/docs/r/container_app_environment.html.markdown index 6c229d4cb4f8..d0fac14c3d86 100644 --- a/website/docs/r/container_app_environment.html.markdown +++ b/website/docs/r/container_app_environment.html.markdown @@ -62,8 +62,22 @@ The following arguments are supported: * `log_analytics_workspace_id` - (Optional) The ID for the Log Analytics Workspace to link this Container Apps Managed Environment to. Changing this forces a new resource to be created. +* `workload_profile` - (Optional) The profile of the workload to scope the container app execution. A `workload_profile` block as defined below. + * `tags` - (Optional) A mapping of tags to assign to the resource. +--- + +A `workload_profile` block supports the following: + +* `name` - (Required) The name of the workload profile. + +* `workload_profile_type` - (Required) Workload profile type for the workloads to run on. Possible values include `D4`, `D8`, `D16`, `D32`, `E4`, `E8`, `E16` and `E32`. + +* `maximum_container_count` - (Optional) The maximum number of instances of workload profile that can be deployed in the Container App Environment. + +* `minimum_container_count` - (Optional) The minimum number of instances of workload profile that can be deployed in the Container App Environment. + ## Attributes Reference In addition to the Arguments listed above - the following Attributes are exported: