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

azurerm_app_configuration_key, azurerm_app_configuration_feature - change resource ID to Data Plane URL format #20082

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
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ import (
"context"
"encoding/json"
"fmt"
"log"
"time"

"github.com/Azure/go-autorest/autorest"
"github.com/hashicorp/go-azure-sdk/resource-manager/appconfiguration/2022-05-01/configurationstores"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-provider-azurerm/helpers/tf"
"github.com/hashicorp/terraform-provider-azurerm/internal/sdk"
"github.com/hashicorp/terraform-provider-azurerm/internal/services/appconfiguration/migration"
"github.com/hashicorp/terraform-provider-azurerm/internal/services/appconfiguration/parse"
"github.com/hashicorp/terraform-provider-azurerm/internal/services/appconfiguration/sdk/1.0/appconfiguration"
"github.com/hashicorp/terraform-provider-azurerm/internal/services/appconfiguration/validate"
Expand All @@ -29,6 +31,8 @@ type FeatureResource struct{}

var _ sdk.ResourceWithUpdate = FeatureResource{}

var _ sdk.ResourceWithStateMigration = FeatureResource{}

type FeatureResourceModel struct {
ConfigurationStoreId string `tfschema:"configuration_store_id"`
Description string `tfschema:"description"`
Expand Down Expand Up @@ -165,18 +169,19 @@ func (k FeatureResource) Create() sdk.ResourceFunc {
return fmt.Errorf("decoding %+v", err)
}

client, err := metadata.Client.AppConfiguration.DataPlaneClient(ctx, model.ConfigurationStoreId)
configurationStoreId, err := configurationstores.ParseConfigurationStoreID(model.ConfigurationStoreId)
if err != nil {
return err
}
if client == nil {
return fmt.Errorf("app configuration %q was not found", model.ConfigurationStoreId)

configurationStoreEndpoint, err := metadata.Client.AppConfiguration.EndpointForConfigurationStore(ctx, *configurationStoreId)
if err != nil {
return fmt.Errorf("retrieving Endpoint for feature %q in %q: %s", model.Name, *configurationStoreId, err)
}

appCfgFeatureResourceID := parse.AppConfigurationFeatureId{
ConfigurationStoreId: model.ConfigurationStoreId,
Name: model.Name,
Label: model.Label,
client, err := metadata.Client.AppConfiguration.DataPlaneClientWithEndpoint(*configurationStoreEndpoint)
if err != nil {
return err
}

featureKey := fmt.Sprintf("%s/%s", FeatureKeyPrefix, model.Name)
Expand All @@ -192,6 +197,11 @@ func (k FeatureResource) Create() sdk.ResourceFunc {
Timeout: 15 * time.Minute,
}

nestedItemId, err := parse.NewNestedItemID(client.Endpoint, featureKey, model.Label)
if err != nil {
return err
}

if _, err = stateConf.WaitForStateContext(ctx); err != nil {
return fmt.Errorf("waiting for App Configuration Key %q read permission to be propagated: %+v", featureKey, err)
}
Expand All @@ -206,19 +216,15 @@ func (k FeatureResource) Create() sdk.ResourceFunc {
return fmt.Errorf("while checking for key's %q existence: %+v", featureKey, err)
}
} else if kv.Response.StatusCode == 200 {
return tf.ImportAsExistsError(k.ResourceType(), appCfgFeatureResourceID.ID())
return tf.ImportAsExistsError(k.ResourceType(), nestedItemId.ID())
}

err = createOrUpdateFeature(ctx, client, model)
if err != nil {
return fmt.Errorf("while creating feature: %+v", err)
}
if appCfgFeatureResourceID.Label == "" {
// We set an empty label as %00 in the resource ID
// Otherwise it breaks the ID parsing logic
appCfgFeatureResourceID.Label = "%00"
}
metadata.SetID(appCfgFeatureResourceID)

metadata.SetID(nestedItemId)
return nil
},
Timeout: 45 * time.Minute,
Expand All @@ -228,37 +234,51 @@ func (k FeatureResource) Create() sdk.ResourceFunc {
func (k FeatureResource) Read() sdk.ResourceFunc {
return sdk.ResourceFunc{
Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error {
resourceID, err := parse.FeatureId(metadata.ResourceData.Id())
nestedItemId, err := parse.ParseNestedItemID(metadata.ResourceData.Id())
if err != nil {
return fmt.Errorf("while parsing resource ID: %+v", err)
}
featureKey := fmt.Sprintf("%s/%s", FeatureKeyPrefix, resourceID.Name)

// We set an empty label as %00 in the ID to make the ID validator happy
// but in reality the label is just an empty string
if resourceID.Label == "%00" {
resourceID.Label = ""
resourceClient := metadata.Client.Resource
configurationStoreIdRaw, err := metadata.Client.AppConfiguration.ConfigurationStoreIDFromEndpoint(ctx, resourceClient, nestedItemId.ConfigurationStoreEndpoint)
if err != nil {
return fmt.Errorf("while retrieving the Resource ID of Configuration Store at Endpoint: %q: %s", nestedItemId.ConfigurationStoreEndpoint, err)
}
if configurationStoreIdRaw == nil {
// if the AppConfiguration is gone then all the data inside it is too
log.Printf("[DEBUG] Unable to determine the Resource ID for Configuration Store at Endpoint %q - removing from state", nestedItemId.ConfigurationStoreEndpoint)
return metadata.MarkAsGone(nestedItemId)
}

client, err := metadata.Client.AppConfiguration.DataPlaneClient(ctx, resourceID.ConfigurationStoreId)
configurationStoreId, err := configurationstores.ParseConfigurationStoreID(*configurationStoreIdRaw)
if err != nil {
return err
}
if client == nil {
// if the AppConfiguration is gone then all the data inside it is too
return metadata.MarkAsGone(resourceID)

ok, err := metadata.Client.AppConfiguration.Exists(ctx, *configurationStoreId)
if err != nil {
return fmt.Errorf("while checking Configuration Store %q for feature %q existence: %v", *configurationStoreId, *nestedItemId, err)
}
if !ok {
log.Printf("[DEBUG] Configuration Store %q for feature %q was not found - removing from state", *configurationStoreId, *nestedItemId)
return metadata.MarkAsGone(nestedItemId)
}

kv, err := client.GetKeyValue(ctx, featureKey, resourceID.Label, "", "", "", []string{})
client, err := metadata.Client.AppConfiguration.DataPlaneClientWithEndpoint(nestedItemId.ConfigurationStoreEndpoint)
if err != nil {
return err
}

kv, err := client.GetKeyValue(ctx, nestedItemId.Key, nestedItemId.Label, "", "", "", []string{})
if err != nil {
if v, ok := err.(autorest.DetailedError); ok {
if utils.ResponseWasNotFound(autorest.Response{Response: v.Response}) {
return metadata.MarkAsGone(resourceID)
return metadata.MarkAsGone(nestedItemId)
}
} else {
return fmt.Errorf("while checking for key's %q existence: %+v", featureKey, err)
return fmt.Errorf("while checking for key %q existence: %+v", *nestedItemId, err)
}
return fmt.Errorf("while checking for key's %q existence: %+v", featureKey, err)
return fmt.Errorf("while checking for key %q existence: %+v", *nestedItemId, err)
}

var fv FeatureValue
Expand All @@ -268,7 +288,7 @@ func (k FeatureResource) Read() sdk.ResourceFunc {
}

model := FeatureResourceModel{
ConfigurationStoreId: resourceID.ConfigurationStoreId,
ConfigurationStoreId: configurationStoreId.ID(),
Description: fv.Description,
Enabled: fv.Enabled,
Name: fv.ID,
Expand Down Expand Up @@ -306,29 +326,32 @@ func (k FeatureResource) Read() sdk.ResourceFunc {
func (k FeatureResource) Update() sdk.ResourceFunc {
return sdk.ResourceFunc{
Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error {
resourceID, err := parse.FeatureId(metadata.ResourceData.Id())
nestedItemId, err := parse.ParseNestedItemID(metadata.ResourceData.Id())
if err != nil {
return fmt.Errorf("while parsing resource ID: %+v", err)
}
featureKey := fmt.Sprintf("%s/%s", FeatureKeyPrefix, resourceID.Name)

client, err := metadata.Client.AppConfiguration.DataPlaneClient(ctx, resourceID.ConfigurationStoreId)
client, err := metadata.Client.AppConfiguration.DataPlaneClientWithEndpoint(nestedItemId.ConfigurationStoreEndpoint)
if err != nil {
return err
}
if client == nil {
return fmt.Errorf("app configuration %q was not found", resourceID.ConfigurationStoreId)
}

var model FeatureResourceModel
if err := metadata.Decode(&model); err != nil {
return fmt.Errorf("decoding %+v", err)
}

configurationStoreId, err := configurationstores.ParseConfigurationStoreID(model.ConfigurationStoreId)
if err != nil {
return err
}

metadata.Client.AppConfiguration.AddToCache(*configurationStoreId, nestedItemId.ConfigurationStoreEndpoint)

if metadata.ResourceData.HasChange("tags") || metadata.ResourceData.HasChange("enabled") || metadata.ResourceData.HasChange("locked") || metadata.ResourceData.HasChange("description") {
// Remove the lock, if any. We will put it back again if the model says so.
if _, err = client.DeleteLock(ctx, featureKey, resourceID.Label, "", ""); err != nil {
return fmt.Errorf("while unlocking key/label pair %s/%s: %+v", resourceID.Name, resourceID.Label, err)
if _, err = client.DeleteLock(ctx, nestedItemId.Key, nestedItemId.Label, "", ""); err != nil {
return fmt.Errorf("while unlocking key/label pair %s/%s: %+v", nestedItemId.Key, nestedItemId.Label, err)
}
err = createOrUpdateFeature(ctx, client, model)
if err != nil {
Expand All @@ -345,37 +368,31 @@ func (k FeatureResource) Update() sdk.ResourceFunc {
func (k FeatureResource) Delete() sdk.ResourceFunc {
return sdk.ResourceFunc{
Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error {
resourceID, err := parse.FeatureId(metadata.ResourceData.Id())
featureKey := fmt.Sprintf("%s/%s", FeatureKeyPrefix, resourceID.Name)

nestedItemId, err := parse.ParseNestedItemID(metadata.ResourceData.Id())
if err != nil {
return fmt.Errorf("while parsing resource ID: %+v", err)
}

client, err := metadata.Client.AppConfiguration.DataPlaneClient(ctx, resourceID.ConfigurationStoreId)
if client == nil {
return fmt.Errorf("app configuration %q was not found", resourceID.ConfigurationStoreId)
}
client, err := metadata.Client.AppConfiguration.DataPlaneClientWithEndpoint(nestedItemId.ConfigurationStoreEndpoint)
if err != nil {
return err
}

kv, err := client.GetKeyValues(ctx, featureKey, resourceID.Label, "", "", []string{})
kv, err := client.GetKeyValues(ctx, nestedItemId.Key, nestedItemId.Label, "", "", []string{})
if err != nil {
return fmt.Errorf("while checking for feature's %q existence: %+v", resourceID.Name, err)
return fmt.Errorf("while checking for feature's %q existence: %+v", nestedItemId.Key, err)
}
keysFound := kv.Values()
if len(keysFound) == 0 {
return nil
}

if _, err = client.DeleteLock(ctx, featureKey, resourceID.Label, "", ""); err != nil {
return fmt.Errorf("while unlocking key/label pair %s/%s: %+v", resourceID.Name, resourceID.Label, err)
if _, err = client.DeleteLock(ctx, nestedItemId.Key, nestedItemId.Label, "", ""); err != nil {
return fmt.Errorf("while unlocking key %q: %+v", *nestedItemId, err)
}

_, err = client.DeleteKeyValue(ctx, featureKey, resourceID.Label, "")
if err != nil {
return fmt.Errorf("while removing key %q from App Configuration Store %q: %+v", resourceID.Name, resourceID.ConfigurationStoreId, err)
if _, err = client.DeleteKeyValue(ctx, nestedItemId.Key, nestedItemId.Label, ""); err != nil {
return fmt.Errorf("while removing key %q: %+v", *nestedItemId, err)
}

return nil
Expand All @@ -385,7 +402,7 @@ func (k FeatureResource) Delete() sdk.ResourceFunc {
}

func (k FeatureResource) IDValidationFunc() pluginsdk.SchemaValidateFunc {
return validate.AppConfigurationFeatureID
return validate.NestedItemId
}

func createOrUpdateFeature(ctx context.Context, client *appconfiguration.BaseClient, model FeatureResourceModel) error {
Expand Down Expand Up @@ -451,3 +468,11 @@ func createOrUpdateFeature(ctx context.Context, client *appconfiguration.BaseCli

return nil
}
func (k FeatureResource) StateUpgraders() sdk.StateUpgradeData {
return sdk.StateUpgradeData{
SchemaVersion: 1,
Upgraders: map[int]pluginsdk.StateUpgrade{
0: migration.FeatureResourceV0ToV1{},
},
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,20 @@ func TestAccAppConfigurationFeature_lockUpdate(t *testing.T) {
})
}

func TestAccAppConfigurationFeature_complicatedKeyLabel(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_app_configuration_feature", "test")
r := AppConfigurationFeatureResource{}
data.ResourceTest(t, r, []acceptance.TestStep{
{
Config: r.complicatedKeyLabel(data),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
),
},
data.ImportStep(),
})
}

func TestAccAppConfigurationFeature_enabledUpdate(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_app_configuration_feature", "test")
r := AppConfigurationFeatureResource{}
Expand All @@ -166,26 +180,22 @@ func TestAccAppConfigurationFeature_enabledUpdate(t *testing.T) {
}

func (t AppConfigurationFeatureResource) Exists(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) (*bool, error) {
resourceID, err := parse.FeatureId(state.ID)
nestedItemId, err := parse.ParseNestedItemID(state.ID)
if err != nil {
return nil, fmt.Errorf("while parsing resource ID: %+v", err)
}

client, err := clients.AppConfiguration.DataPlaneClient(ctx, resourceID.ConfigurationStoreId)
client, err := clients.AppConfiguration.DataPlaneClientWithEndpoint(nestedItemId.ConfigurationStoreEndpoint)
if err != nil {
return nil, err
}
if client == nil {
// if the AppConfiguration is gone all the data is too
return utils.Bool(false), nil
}

res, err := client.GetKeyValues(ctx, resourceID.Name, resourceID.Label, "", "", []string{})
res, err := client.GetKeyValue(ctx, nestedItemId.Key, nestedItemId.Label, "", "", "", []string{})
if err != nil {
return nil, fmt.Errorf("while checking for key's %q existence: %+v", resourceID.Name, err)
return nil, fmt.Errorf("while checking for key's %q existence: %+v", nestedItemId.Key, err)
}

return utils.Bool(res.Response().StatusCode == 200), nil
return utils.Bool(res.Response.StatusCode == 200), nil
}

func (t AppConfigurationFeatureResource) basic(data acceptance.TestData) string {
Expand Down Expand Up @@ -412,6 +422,46 @@ resource "azurerm_app_configuration_feature" "import" {
`, t.basic(data))
}

func (t AppConfigurationFeatureResource) complicatedKeyLabel(data acceptance.TestData) string {
return fmt.Sprintf(`
provider "azurerm" {
features {}
}

resource "azurerm_resource_group" "test" {
name = "acctestRG-appconfig-%d"
location = "%s"
}

data "azurerm_client_config" "test" {
}

resource "azurerm_role_assignment" "test" {
scope = azurerm_resource_group.test.id
role_definition_name = "App Configuration Data Owner"
principal_id = data.azurerm_client_config.test.object_id
}

resource "azurerm_app_configuration" "test" {
name = "testacc-appconf%d"
resource_group_name = azurerm_resource_group.test.name
location = azurerm_resource_group.test.location
sku = "standard"

depends_on = [
azurerm_role_assignment.test,
]
}

resource "azurerm_app_configuration_feature" "test" {
configuration_store_id = azurerm_app_configuration.test.id
name = "acctest-ackey-%d/Label/AppConfigurationKey/Label/"
label = "/Key/AppConfigurationKey/Label/acctest-ackeylabel-%d"
enabled = true
}
`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomInteger, data.RandomInteger)
}

func (t AppConfigurationFeatureResource) lockUpdate(data acceptance.TestData, lockStatus bool) string {
return fmt.Sprintf(`
provider "azurerm" {
Expand Down
Loading