Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

containerapps - fix breaking schema behavioural change #28639

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .teamcity/components/settings.kt
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ var serviceTestConfigurationOverrides = mapOf(

// Container App Managed Environments are limited to 20 per location, using 10 as they can take some time to clear
// Enable rotation test to mitigate resource burden in a single region
"containerapps" to testConfiguration(parallelism = 10, locationOverride = LocationConfiguration("eastus2","westus2","northcentralus", true)),
"containerapps" to testConfiguration(parallelism = 10, locationOverride = LocationConfiguration("eastus2","westus2","southcentralus", true)),

// The AKS API has a low rate limit
"containers" to testConfiguration(parallelism = 5, locationOverride = LocationConfiguration("eastus","westeurope","eastus2", false), timeout = 18),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"github.com/hashicorp/go-azure-helpers/resourcemanager/tags"
"github.com/hashicorp/go-azure-sdk/resource-manager/containerapps/2024-03-01/managedenvironments"
"github.com/hashicorp/go-azure-sdk/resource-manager/operationalinsights/2020-08-01/workspaces"
"github.com/hashicorp/terraform-provider-azurerm/internal/features"
"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"
Expand All @@ -28,7 +29,7 @@ import (
const (
LogsDestinationLogAnalytics string = "log-analytics"
LogsDestinationAzureMonitor string = "azure-monitor"
LogsDestinationAzureNone string = ""
LogsDestinationNone string = ""
)

type ContainerAppEnvironmentResource struct{}
Expand Down Expand Up @@ -74,7 +75,7 @@ func (r ContainerAppEnvironmentResource) IDValidationFunc() pluginsdk.SchemaVali
}

func (r ContainerAppEnvironmentResource) Arguments() map[string]*pluginsdk.Schema {
return map[string]*pluginsdk.Schema{
schema := map[string]*pluginsdk.Schema{
"name": {
Type: pluginsdk.TypeString,
Required: true,
Expand Down Expand Up @@ -106,12 +107,12 @@ func (r ContainerAppEnvironmentResource) Arguments() map[string]*pluginsdk.Schem
"logs_destination": {
Type: pluginsdk.TypeString,
Optional: true,
Default: LogsDestinationAzureNone,
Default: LogsDestinationNone,
ValidateFunc: validation.StringInSlice([]string{
LogsDestinationLogAnalytics,
LogsDestinationAzureMonitor,
LogsDestinationLogAnalytics,
}, false),
Description: "The destination for the application logs. Possible values are `log-analytics` or `azure-monitor`.",
Description: "The destination for the application logs. Possible values include `log-analytics` and `azure-monitor`. Omitting this value will result in logs being streamed only.",
},

"infrastructure_resource_group_name": {
Expand Down Expand Up @@ -170,6 +171,21 @@ func (r ContainerAppEnvironmentResource) Arguments() map[string]*pluginsdk.Schem

"tags": commonschema.Tags(),
}

if !features.FivePointOh() {
schema["logs_destination"] = &pluginsdk.Schema{
Type: pluginsdk.TypeString,
Optional: true,
Computed: true, // NOTE: O+C as the introduction of this property is a behavioural change where we previously set it behind the scenes if `log_analytics_workspace_id` was set.
ValidateFunc: validation.StringInSlice([]string{
LogsDestinationAzureMonitor,
LogsDestinationNone,
LogsDestinationLogAnalytics,
}, false),
}
}

return schema
}

func (r ContainerAppEnvironmentResource) Attributes() map[string]*pluginsdk.Schema {
Expand Down Expand Up @@ -275,6 +291,8 @@ func (r ContainerAppEnvironmentResource) Create() sdk.ResourceFunc {
return fmt.Errorf("retrieving access keys to Log Analytics Workspace for %s: %+v", id, err)
}

managedEnvironment.Properties.AppLogsConfiguration.Destination = pointer.To(LogsDestinationLogAnalytics)

managedEnvironment.Properties.AppLogsConfiguration.LogAnalyticsConfiguration = &managedenvironments.LogAnalyticsConfiguration{
CustomerId: customerId,
SharedKey: sharedKey,
Expand Down Expand Up @@ -424,24 +442,31 @@ func (r ContainerAppEnvironmentResource) Update() sdk.ResourceFunc {
}

if metadata.ResourceData.HasChanges("logs_destination", "log_analytics_workspace_id") {
// For 4.x we need to be compensate for the legacy behaviour of setting log destination based on the presence of log_analytics_workspace_id
if !features.FivePointOh() && metadata.ResourceData.GetRawConfig().AsValueMap()["logs_destination"].IsNull() && state.LogAnalyticsWorkspaceId == "" {
state.LogsDestination = LogsDestinationNone
}

switch state.LogsDestination {
case LogsDestinationAzureMonitor:
existing.Model.Properties.AppLogsConfiguration = &managedenvironments.AppLogsConfiguration{
Destination: pointer.To(LogsDestinationAzureMonitor),
LogAnalyticsConfiguration: nil,
}
case LogsDestinationLogAnalytics:
customerId, sharedKey, err := getSharedKeyForWorkspace(ctx, metadata, state.LogAnalyticsWorkspaceId)
if err != nil {
return fmt.Errorf("retrieving access keys to Log Analytics Workspace for %s: %+v", id, err)
}
if state.LogAnalyticsWorkspaceId != "" {
customerId, sharedKey, err := getSharedKeyForWorkspace(ctx, metadata, state.LogAnalyticsWorkspaceId)
if err != nil {
return fmt.Errorf("retrieving access keys to Log Analytics Workspace for %s: %+v", id, err)
}

existing.Model.Properties.AppLogsConfiguration = &managedenvironments.AppLogsConfiguration{
Destination: pointer.To(LogsDestinationLogAnalytics),
LogAnalyticsConfiguration: &managedenvironments.LogAnalyticsConfiguration{
CustomerId: customerId,
SharedKey: sharedKey,
},
existing.Model.Properties.AppLogsConfiguration = &managedenvironments.AppLogsConfiguration{
Destination: pointer.To(LogsDestinationLogAnalytics),
LogAnalyticsConfiguration: &managedenvironments.LogAnalyticsConfiguration{
CustomerId: customerId,
SharedKey: sharedKey,
},
}
}

default:
Expand Down Expand Up @@ -498,20 +523,42 @@ func (r ContainerAppEnvironmentResource) CustomizeDiff() sdk.ResourceFunc {
}
}

if metadata.ResourceDiff.HasChanges("logs_destination", "log_analytics_workspace_id") {
logsDestination := metadata.ResourceDiff.Get("logs_destination").(string)
logAnalyticsWorkspaceID := metadata.ResourceDiff.Get("log_analytics_workspace_id").(string)
logAnalyticsWorkspaceIDIsNull := metadata.ResourceDiff.GetRawConfig().AsValueMap()["log_analytics_workspace_id"].IsNull()
if !features.FivePointOh() { // in 4.x `logs_destination` is Computed due to legacy code implying destination from presence of a valid id in `log_analytics_workspace_id` so we need to check explicit config values here
if metadata.ResourceDiff.HasChanges("logs_destination", "log_analytics_workspace_id") {
logsDestination := metadata.ResourceDiff.Get("logs_destination").(string)
logDestinationIsNull := metadata.ResourceDiff.GetRawConfig().AsValueMap()["logs_destination"].IsNull()
logAnalyticsWorkspaceID := metadata.ResourceDiff.Get("log_analytics_workspace_id").(string)
logAnalyticsWorkspaceIDIsNull := metadata.ResourceDiff.GetRawConfig().AsValueMap()["log_analytics_workspace_id"].IsNull()

if !logDestinationIsNull || !logAnalyticsWorkspaceIDIsNull {
switch logsDestination {
case LogsDestinationLogAnalytics:
if logAnalyticsWorkspaceIDIsNull {
return fmt.Errorf("`log_analytics_workspace_id` must be set when `logs_destination` is set to `log-analytics`")
}

switch logsDestination {
case LogsDestinationLogAnalytics:
if logAnalyticsWorkspaceIDIsNull {
return fmt.Errorf("`log_analytics_workspace_id` must be set when `logs_destination` is set to `log-analytics`")
case LogsDestinationAzureMonitor, LogsDestinationNone:
if (logAnalyticsWorkspaceID != "" || !logAnalyticsWorkspaceIDIsNull) && !logDestinationIsNull {
return fmt.Errorf("`log_analytics_workspace_id` can only be set when `logs_destination` is set to `log-analytics` or omitted")
}
}
}
}
} else {
if metadata.ResourceDiff.HasChanges("logs_destination", "log_analytics_workspace_id") {
logsDestination := metadata.ResourceDiff.Get("logs_destination").(string)
logAnalyticsWorkspaceID := metadata.ResourceDiff.Get("log_analytics_workspace_id").(string)

switch logsDestination {
case LogsDestinationLogAnalytics:
if logAnalyticsWorkspaceID == "" {
return fmt.Errorf("`log_analytics_workspace_id` must be set when `logs_destination` is set to `log-analytics`")
}

case LogsDestinationAzureMonitor, LogsDestinationAzureNone:
if logAnalyticsWorkspaceID != "" || !logAnalyticsWorkspaceIDIsNull {
return fmt.Errorf("`log_analytics_workspace_id` can only be set when `logs_destination` is set to `log-analytics`")
case LogsDestinationAzureMonitor, LogsDestinationNone:
if logAnalyticsWorkspaceID != "" {
return fmt.Errorf("`log_analytics_workspace_id` can only be set when `logs_destination` is set to `log-analytics` or omitted")
}
}
}
}
Expand Down
8 changes: 4 additions & 4 deletions internal/services/containerapps/validate/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ func ContainerAppName(i interface{}, k string) (warnings []string, errors []erro
return
}

if matched := regexp.MustCompile(`^([a-z])[a-z0-9-]{0,30}[a-z0-9]?$`).Match([]byte(v)); !matched || strings.HasSuffix(v, "-") || strings.Contains(v, "--") {
if matched := regexp.MustCompile(`^([a-z]\z)|(^([a-z0-9])([a-z0-9-.]{0,30})([^A-Z\W]))$`).Match([]byte(v)); !matched || strings.Contains(v, "--") {
errors = append(errors, fmt.Errorf("%q must consist of lower case alphanumeric characters or '-', start with an alphabetic character, and end with an alphanumeric character and cannot have '--'. The length must not be more than 32 characters", k))
return
}
Expand Down Expand Up @@ -106,8 +106,8 @@ func ManagedEnvironmentName(i interface{}, k string) (warnings []string, errors

if matched := regexp.MustCompile(`^([a-zA-Z])[a-zA-Z0-9-]{0,58}[a-z]?$`).Match([]byte(v)); !matched || strings.HasSuffix(v, "-") {
errors = append(errors, fmt.Errorf("%q must consist of lower case alphanumeric characters or '-', start with an alphabetic character, and end with an alphanumeric character. The length must not be more than 60 characters", k))
return
}

return
}

Expand All @@ -118,8 +118,8 @@ func ContainerAppContainerName(i interface{}, k string) (warnings []string, erro
return
}

if matched := regexp.MustCompile(`^([a-z])[a-z0-9-.]{0,58}[a-z]$`).Match([]byte(v)); !matched || strings.HasSuffix(v, "-") {
errors = append(errors, fmt.Errorf("%q must consist of lower case alphanumeric characters, '-', or '.', start with an alphabetic character, and end with an alphanumeric character. The length must not be more than 60 characters", k))
if matched := regexp.MustCompile(`^([a-z0-9]\z)|(^([a-z0-9])([a-z0-9-.]{0,44})([^A-Z\W])$)`).Match([]byte(v)); !matched {
errors = append(errors, fmt.Errorf("%q must consist of lower case alphanumeric characters, '-', or '.', start with an alphabetic character, and end with an alphanumeric character. The length must not be more than 46 characters", k))
}

return
Expand Down
23 changes: 17 additions & 6 deletions internal/services/containerapps/validate/validate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,7 @@ func TestContainerAppScaleRuleConcurrentRequests(t *testing.T) {
}

func TestValidateContainerAppName(t *testing.T) {
// From Portal: Value must consist of lower case alphanumeric characters or '-', start with an alphabetic character, and end with an alphanumeric character and cannot have '--'. The length must not be more than 32 characters.
cases := []struct {
Input string
Valid bool
Expand Down Expand Up @@ -376,6 +377,7 @@ func TestValidateContainerAppName(t *testing.T) {
}

func TestValidateContainerAppContainerName(t *testing.T) {
// From Portal: Value must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character. The length must not be more than 46 characters.
cases := []struct {
Input string
Valid bool
Expand All @@ -386,21 +388,29 @@ func TestValidateContainerAppContainerName(t *testing.T) {
{
Input: "-",
},
{
Input: "9",
},
{
Input: "a-",
},
{
Input: "a.",
},
{
Input: "a--a",
Input: "Cannothavecapitals",
},
{
Input: "invalid1234567890123456789012345678901234567890",
},
{
Input: "9",
Valid: true,
},
{
Input: "Cannothavecapitals",
Input: "a",
Valid: true,
},
{
Input: "a--a",
Valid: true,
},
{
Input: "a-a",
Expand All @@ -411,7 +421,8 @@ func TestValidateContainerAppContainerName(t *testing.T) {
Valid: true,
},
{
Input: "invalid12345678901234567890123456",
Input: "validbutverylong123456789012345678901234567890",
Valid: true,
},
}

Expand Down
4 changes: 4 additions & 0 deletions website/docs/5.0-upgrade-guide.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@ Please follow the format in the example below for listing breaking changes in re

* The `minimal_tls_version` property no longer accepts `Tls` or `Tls11` as a value.

### `azurerm_container_app_environment`

* The `logs_destination` property is no longer Computed and now must be set to `log-analytics` to be able to specify a value for `log_analytics_workspace_id`. It will now default to empty, meaning Streaming Only in the Azure Portal.

### `azurerm_eventhub`

* The deprecated `namespace_name` property has been removed in favour of the `namespace_id` property.
Expand Down
2 changes: 1 addition & 1 deletion website/docs/r/container_app_environment.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ The following arguments are supported:

~> **Note:** required if `logs_destination` is set to `log-analytics`. Cannot be set if `logs_destination` is set to `azure-monitor`.

* `logs_destination` - (Optional) Where the application logs will be saved for this Container Apps Managed Environment. Options are `log-analytics` or `azure-monitor`.
* `logs_destination` - (Optional) Where the application logs will be saved for this Container Apps Managed Environment. Possible values include `log-analytics` and `azure-monitor`. Omitting this value will result in logs being streamed only.

* `workload_profile` - (Optional) The profile of the workload to scope the container app execution. A `workload_profile` block as defined below.

Expand Down
Loading