Skip to content

Commit

Permalink
rebase and update for SDK changes. guard against secret removal
Browse files Browse the repository at this point in the history
  • Loading branch information
jackofallops committed Oct 11, 2022
1 parent aa8e963 commit 49f17bb
Show file tree
Hide file tree
Showing 4 changed files with 214 additions and 51 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,8 @@ import (
"github.com/hashicorp/go-azure-helpers/resourcemanager/location"
"github.com/hashicorp/go-azure-helpers/resourcemanager/tags"
"github.com/hashicorp/go-azure-sdk/resource-manager/containerapps/2022-03-01/managedenvironments"
"github.com/hashicorp/go-azure-sdk/resource-manager/operationalinsights/2020-08-01/workspaces"
"github.com/hashicorp/terraform-provider-azurerm/internal/sdk"
loganalyticsParse "github.com/hashicorp/terraform-provider-azurerm/internal/services/loganalytics/parse"
loganalyticsValidate "github.com/hashicorp/terraform-provider-azurerm/internal/services/loganalytics/validate"
networkValidate "github.com/hashicorp/terraform-provider-azurerm/internal/services/network/validate"
"github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk"
"github.com/hashicorp/terraform-provider-azurerm/internal/tf/validation"
Expand Down Expand Up @@ -77,7 +76,7 @@ func (r ContainerAppEnvironmentResource) Arguments() map[string]*pluginsdk.Schem
Type: pluginsdk.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: loganalyticsValidate.LogAnalyticsWorkspaceID,
ValidateFunc: workspaces.ValidateWorkspaceID,
Description: "The ID for the Log Analytics Workspace to link this Container Apps Managed Environment to.",
},

Expand Down Expand Up @@ -178,7 +177,6 @@ func (r ContainerAppEnvironmentResource) Create() sdk.ResourceFunc {
Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error {
client := metadata.Client.ContainerApps.ManagedEnvironmentClient
logAnalyticsClient := metadata.Client.LogAnalytics.WorkspacesClient
sharedKeyClient := metadata.Client.LogAnalytics.SharedKeysClient
subscriptionId := metadata.Client.Account.SubscriptionId

var containerAppEnvironment ContainerAppEnvironmentModel
Expand All @@ -200,26 +198,26 @@ func (r ContainerAppEnvironmentResource) Create() sdk.ResourceFunc {
return metadata.ResourceRequiresImport(r.ResourceType(), id)
}

logAnalyticsId, err := loganalyticsParse.LogAnalyticsWorkspaceID(containerAppEnvironment.LogAnalyticsWorkspaceId)
logAnalyticsId, err := workspaces.ParseWorkspaceID(containerAppEnvironment.LogAnalyticsWorkspaceId)
if err != nil {
return err
}

workspace, err := logAnalyticsClient.Get(ctx, logAnalyticsId.ResourceGroup, logAnalyticsId.WorkspaceName)
if err != nil || workspace.WorkspaceProperties == nil {
workspace, err := logAnalyticsClient.Get(ctx, *logAnalyticsId)
if err != nil || workspace.Model == nil || workspace.Model.Properties == nil {
return fmt.Errorf("retrieving %s for %s: %+v", logAnalyticsId, id, err)
}

if workspace.WorkspaceProperties.CustomerID == nil {
if workspace.Model.Properties.CustomerId == nil {
return fmt.Errorf("reading customer ID from %s", logAnalyticsId)
}

keys, err := sharedKeyClient.GetSharedKeys(ctx, logAnalyticsId.ResourceGroup, logAnalyticsId.WorkspaceName)
if err != nil {
keys, err := logAnalyticsClient.SharedKeysGetSharedKeys(ctx, *logAnalyticsId)
if err != nil || keys.Model == nil {
return fmt.Errorf("retrieving access keys to %s for %s: %+v", logAnalyticsId, id, err)
}

if keys.PrimarySharedKey == nil {
if keys.Model.PrimarySharedKey == nil {
return fmt.Errorf("reading shared key for %s in %s", logAnalyticsId, id)
}

Expand All @@ -230,8 +228,8 @@ func (r ContainerAppEnvironmentResource) Create() sdk.ResourceFunc {
AppLogsConfiguration: &managedenvironments.AppLogsConfiguration{
Destination: utils.String("log-analytics"),
LogAnalyticsConfiguration: &managedenvironments.LogAnalyticsConfiguration{
CustomerId: workspace.WorkspaceProperties.CustomerID,
SharedKey: keys.PrimarySharedKey,
CustomerId: workspace.Model.Properties.CustomerId,
SharedKey: keys.Model.PrimarySharedKey,
},
},
VnetConfiguration: &managedenvironments.VnetConfiguration{},
Expand Down
67 changes: 37 additions & 30 deletions internal/services/containerapps/container_app_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ type ContainerAppModel struct {

var _ sdk.ResourceWithUpdate = ContainerAppResource{}

//var _ sdk.ResourceWithCustomizeDiff = ContainerAppResource{}
var _ sdk.ResourceWithCustomizeDiff = ContainerAppResource{}

func (r ContainerAppResource) ModelObject() interface{} {
return &ContainerAppModel{}
Expand Down Expand Up @@ -372,6 +372,11 @@ func (r ContainerAppResource) Update() sdk.ResourceFunc {
}

model.Properties.Template = helpers.ExpandContainerAppTemplate(state.Template, metadata)

// Zero R/O - API rejects the request if eny of these are set
model.SystemData = nil
model.Properties.OutboundIPAddresses = nil

if err := client.CreateOrUpdateThenPoll(ctx, *id, *model); err != nil {
return fmt.Errorf("updating %s: %+v", *id, err)
}
Expand All @@ -381,32 +386,34 @@ func (r ContainerAppResource) Update() sdk.ResourceFunc {
}
}

//func (r ContainerAppResource) CustomizeDiff() sdk.ResourceFunc {
// return sdk.ResourceFunc{
// Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error {
// if metadata.ResourceData.HasChange("secret") {
// stateSecretsRaw, configSecretsRaw := metadata.ResourceData.GetChange("secret")
// stateSecrets := stateSecretsRaw.([]helpers.Secret)
// configSecret := configSecretsRaw.([]helpers.Secret)
// // Check there's not less
// if len(configSecret) < len(stateSecrets) {
// return fmt.Errorf("cannot remove secrets from Container Apps at this time")
// }
// // Check secrets names in state are all present in config, the values don't matter
// for _, s := range stateSecrets {
// found := false
// for _, c := range configSecret {
// if s.Name == c.Name {
// found = true
// break
// }
// if !found {
// return fmt.Errorf("previously configured secret %q was removed. Removing secrets is not supported at this time", s.Name)
// }
// }
// }
// }
// return nil
// },
// }
//}
func (r ContainerAppResource) CustomizeDiff() sdk.ResourceFunc {
return sdk.ResourceFunc{
Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error {
if metadata.ResourceDiff != nil && metadata.ResourceDiff.HasChange("secret") {
stateSecretsRaw, configSecretsRaw := metadata.ResourceDiff.GetChange("secret")
stateSecrets := stateSecretsRaw.([]interface{})
configSecrets := configSecretsRaw.([]interface{})
// Check there's not less
if len(configSecrets) < len(stateSecrets) {
return fmt.Errorf("cannot remove secrets from Container Apps at this time. Please see `https://github.com/microsoft/azure-container-apps/issues/395` for more details")
}
// Check secrets names in state are all present in config, the values don't matter
if len(stateSecrets) > 0 {
for _, s := range stateSecrets {
found := false
for _, c := range configSecrets {
if s.(map[string]interface{})["name"] == c.(map[string]interface{})["name"] {
found = true
break
}
if !found {
return fmt.Errorf("previously configured secret %q was removed. Removing secrets is not supported at this time, see `https://github.com/microsoft/azure-container-apps/issues/395` for more details", s.(map[string]interface{})["name"])
}
}
}
}
}
return nil
},
}
}
172 changes: 165 additions & 7 deletions internal/services/containerapps/container_app_resource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package containerapps_test
import (
"context"
"fmt"
"regexp"
"testing"

"github.com/hashicorp/go-azure-helpers/lang/response"
Expand Down Expand Up @@ -102,21 +103,59 @@ func TestAccContainerAppResource_completeUpdate(t *testing.T) {
),
},
data.ImportStep(),
// TODO - Uncomment the following stages when https://github.com/Azure/azure-rest-api-specs/issues/19285 is resolved
// TODO - Uncomment the following stages when https://github.com/Azure/azure-rest-api-specs/issues/19285 / https://github.com/microsoft/azure-container-apps/issues/395 are resolved and secrets can be managed?
// {
// Config: r.complete(data, "rev3"),
// Check: acceptance.ComposeTestCheckFunc(
// check.That(data.ResourceName).ExistsInAzure(r),
// ),
// },
// data.ImportStep(),
// {
// Config: r.completeUpdate2(data, "rev4"),
// Check: acceptance.ComposeTestCheckFunc(
// check.That(data.ResourceName).ExistsInAzure(r),
// ),
// },
// data.ImportStep(),
})
}

func TestAccContainerAppResource_secretRemoveShouldFail(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_container_app", "test")
r := ContainerAppResource{}

data.ResourceTest(t, r, []acceptance.TestStep{
{
Config: r.complete(data, "rev3"),
Config: r.completeUpdate(data, "rev1"),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
),
},
data.ImportStep(),
{
Config: r.completeUpdate2(data, "rev4"),
Config: r.complete(data, "rev2"),
ExpectError: regexp.MustCompile("cannot remove secrets from Container Apps at this time"),
},
})
}

func TestAccContainerAppResource_secretRemoveWithAddShouldFail(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_container_app", "test")
r := ContainerAppResource{}

data.ResourceTest(t, r, []acceptance.TestStep{
{
Config: r.completeUpdate(data, "rev1"),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
),
},
data.ImportStep(),
{
Config: r.completeChangedSecret(data, "rev2"),
ExpectError: regexp.MustCompile("previously configured secret \"rick\" was removed. Removing secrets is not supported at this time"),
},
})
}

Expand Down Expand Up @@ -304,6 +343,126 @@ resource "azurerm_container_app" "test" {
`, r.templatePlusExtras(data), data.RandomInteger, revisionSuffix)
}

func (r ContainerAppResource) completeChangedSecret(data acceptance.TestData, revisionSuffix string) string {
return fmt.Sprintf(`
%s
resource "azurerm_container_app" "test" {
name = "acctest-capp-%[2]d"
resource_group_name = azurerm_resource_group.test.name
container_app_environment_id = azurerm_container_app_environment.test.id
revision_mode = "Multiple"
template {
container {
name = "acctest-cont-%[2]d"
image = "jackofallops/azure-containerapps-python-acctest:v0.0.1"
cpu = 0.5
memory = "1Gi"
//args = ["HOSTNAME"] // TODO - Add a container Image where args and command can be used
//command = ["node"]
readiness_probe {
transport = "http"
port = 5000
path = "/uptime"
timeout = 2
failure_threshold = 1
success_threshold = 1
header {
name = "Cache-Control"
value = "no-cache"
}
}
liveness_probe {
transport = "http"
port = 5000
path = "/health"
header {
name = "Cache-Control"
value = "no-cache"
}
initial_delay = 5
interval = 20
timeout = 2
failure_threshold = 3
}
startup_probe {
transport = "tcp"
port = 5000
timeout = 5
failure_threshold = 1
}
//volume_mounts {
// name = "testVol"
// path = "/tmp/testdata"
//}
}
//volume {
// name = "testVol"
// storage_type = "EmptyDir"
//}
min_replicas = 1
max_replicas = 4
revision_suffix = "%[3]s"
}
ingress {
allow_insecure_connections = true
is_external = true
target_port = 5000
transport = "auto"
traffic_weight {
latest_revision = true
weight = 20
}
traffic_weight {
revision_suffix = "rev1"
weight = 80
}
}
registry {
server = azurerm_container_registry.test.login_server
username = azurerm_container_registry.test.admin_username
password_secret_reference = "registry-password"
}
secret {
name = "registry-password"
value = azurerm_container_registry.test.admin_password
}
secret {
name = "pickle"
value = "morty"
}
dapr {
app_id = "acctest-cont-%[2]d"
app_port = 5000
app_protocol = "http"
}
tags = {
foo = "Bar"
accTest = "1"
}
}
`, r.templatePlusExtras(data), data.RandomInteger, revisionSuffix)
}

func (r ContainerAppResource) completeUpdate(data acceptance.TestData, revisionSuffix string) string {
return fmt.Sprintf(`
%s
Expand Down Expand Up @@ -441,14 +600,13 @@ resource "azurerm_container_app" "test" {
cpu = 1.0
memory = "2Gi"
}
revision_suffix = "%[3]s"
}
secret {
name = "doesntMatter"
name = "doesnt-matter"
value = "anything"
}
revision_suffix = "%[3]s"
}
}
`, r.templatePlusExtras(data), data.RandomInteger, revisionSuffix)
}
Expand Down
2 changes: 1 addition & 1 deletion internal/services/containerapps/helpers/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func ValidateSecretName(i interface{}, k string) (warnings []string, errors []er
return
}

if matched := regexp.MustCompile(`^[a-z0-9][a-z0-9-.]*[a-z0-9]?$`).Match([]byte(v)); !matched || strings.HasSuffix(v, "-") || strings.HasSuffix(v, ".") {
if matched := regexp.MustCompile(`^[a-z0-9][a-z0-9-]*[a-z0-9]?$`).Match([]byte(v)); !matched || strings.HasSuffix(v, "-") || strings.HasSuffix(v, ".") {
errors = append(errors, fmt.Errorf("%q must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character", k))
}
return
Expand Down

0 comments on commit 49f17bb

Please sign in to comment.