diff --git a/internal/services/appconfiguration/app_configuration.go b/internal/services/appconfiguration/app_configuration.go index 527dcd2186ff..b54c2d691f1e 100644 --- a/internal/services/appconfiguration/app_configuration.go +++ b/internal/services/appconfiguration/app_configuration.go @@ -4,11 +4,15 @@ package appconfiguration import ( + "bytes" "context" + "fmt" "github.com/Azure/go-autorest/autorest" + "github.com/hashicorp/go-azure-helpers/lang/pointer" "github.com/hashicorp/go-azure-helpers/lang/response" "github.com/hashicorp/go-azure-sdk/resource-manager/appconfiguration/2023-03-01/configurationstores" + "github.com/hashicorp/go-azure-sdk/resource-manager/appconfiguration/2023-03-01/replicas" "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" "github.com/tombuildsstuff/kermit/sdk/appconfiguration/1.0/appconfiguration" ) @@ -25,6 +29,39 @@ func flattenAppConfigurationEncryption(input *configurationstores.EncryptionProp }, } } + +func flattenAppConfigurationReplicas(input []replicas.Replica) ([]interface{}, error) { + results := make([]interface{}, 0) + for _, v := range input { + if v.Properties == nil { + return results, fmt.Errorf("retrieving Replica %s Properties is nil", *v.Id) + } + + replicaId, err := replicas.ParseReplicaIDInsensitively(pointer.From(v.Id)) + if err != nil { + return results, err + } + + result := map[string]interface{}{ + "name": pointer.From(v.Name), + "location": pointer.From(v.Location), + "endpoint": pointer.From(v.Properties.Endpoint), + "id": replicaId.ID(), + } + results = append(results, result) + } + return results, nil +} + +func resourceConfigurationStoreReplicaHash(input interface{}) int { + var buf bytes.Buffer + if rawData, ok := input.(map[string]interface{}); ok { + buf.WriteString(rawData["name"].(string)) + buf.WriteString(rawData["location"].(string)) + } + return pluginsdk.HashString(buf.String()) +} + func appConfigurationGetKeyRefreshFunc(ctx context.Context, client *appconfiguration.BaseClient, key, label string) pluginsdk.StateRefreshFunc { return func() (interface{}, string, error) { res, err := client.GetKeyValue(ctx, key, label, "", "", "", []appconfiguration.KeyValueFields{}) diff --git a/internal/services/appconfiguration/app_configuration_data_source.go b/internal/services/appconfiguration/app_configuration_data_source.go index 6672cf34e69c..f7f57558c422 100644 --- a/internal/services/appconfiguration/app_configuration_data_source.go +++ b/internal/services/appconfiguration/app_configuration_data_source.go @@ -14,6 +14,7 @@ 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/appconfiguration/2023-03-01/configurationstores" + "github.com/hashicorp/go-azure-sdk/resource-manager/appconfiguration/2023-03-01/replicas" "github.com/hashicorp/terraform-provider-azurerm/internal/clients" "github.com/hashicorp/terraform-provider-azurerm/internal/services/appconfiguration/validate" "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" @@ -93,6 +94,28 @@ func dataSourceAppConfiguration() *pluginsdk.Resource { Computed: true, }, + "replica": { + Type: pluginsdk.TypeList, + Computed: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "name": { + Type: pluginsdk.TypeString, + Computed: true, + }, + "location": commonschema.LocationComputed(), + "endpoint": { + Type: pluginsdk.TypeString, + Computed: true, + }, + "id": { + Type: pluginsdk.TypeString, + Computed: true, + }, + }, + }, + }, + "primary_read_key": { Type: pluginsdk.TypeList, Computed: true, @@ -257,6 +280,19 @@ func dataSourceAppConfigurationRead(d *pluginsdk.ResourceData, meta interface{}) return fmt.Errorf("setting `identity`: %+v", err) } + replicasClient := meta.(*clients.Client).AppConfiguration.ReplicasClient + resp, err := replicasClient.ListByConfigurationStoreComplete(ctx, replicas.NewConfigurationStoreID(id.SubscriptionId, id.ResourceGroupName, id.ConfigurationStoreName)) + if err != nil { + return fmt.Errorf("retrieving replicas for %s: %+v", id, err) + } + + replica, err := flattenAppConfigurationReplicas(resp.Items) + if err != nil { + return fmt.Errorf("flattening replicas for %s: %+v", id, err) + } + + d.Set("replica", replica) + return tags.FlattenAndSet(d, model.Tags) } diff --git a/internal/services/appconfiguration/app_configuration_data_source_test.go b/internal/services/appconfiguration/app_configuration_data_source_test.go index 0cdaf2c96d26..2bbbd31e0b55 100644 --- a/internal/services/appconfiguration/app_configuration_data_source_test.go +++ b/internal/services/appconfiguration/app_configuration_data_source_test.go @@ -49,6 +49,10 @@ func TestAccAppConfigurationDataSource_basic(t *testing.T) { check.That(data.ResourceName).Key("secondary_write_key.0.secret").Exists(), check.That(data.ResourceName).Key("sku").Exists(), check.That(data.ResourceName).Key("soft_delete_retention_days").Exists(), + check.That(data.ResourceName).Key("replica.#").HasValue("2"), + check.That(data.ResourceName).Key("replica.0.name").Exists(), + check.That(data.ResourceName).Key("replica.1.id").Exists(), + check.That(data.ResourceName).Key("replica.1.endpoint").Exists(), ), }, }) diff --git a/internal/services/appconfiguration/app_configuration_resource.go b/internal/services/appconfiguration/app_configuration_resource.go index 4c1bd7415e50..e2e3d7679a47 100644 --- a/internal/services/appconfiguration/app_configuration_resource.go +++ b/internal/services/appconfiguration/app_configuration_resource.go @@ -21,9 +21,9 @@ import ( "github.com/hashicorp/go-azure-sdk/resource-manager/appconfiguration/2023-03-01/configurationstores" "github.com/hashicorp/go-azure-sdk/resource-manager/appconfiguration/2023-03-01/deletedconfigurationstores" "github.com/hashicorp/go-azure-sdk/resource-manager/appconfiguration/2023-03-01/operations" + "github.com/hashicorp/go-azure-sdk/resource-manager/appconfiguration/2023-03-01/replicas" "github.com/hashicorp/go-azure-sdk/sdk/client" "github.com/hashicorp/go-azure-sdk/sdk/client/pollers" - "github.com/hashicorp/terraform-provider-azurerm/helpers/azure" "github.com/hashicorp/terraform-provider-azurerm/helpers/tf" "github.com/hashicorp/terraform-provider-azurerm/internal/clients" "github.com/hashicorp/terraform-provider-azurerm/internal/services/appconfiguration/validate" @@ -41,10 +41,10 @@ func resourceAppConfiguration() *pluginsdk.Resource { Delete: resourceAppConfigurationDelete, Timeouts: &pluginsdk.ResourceTimeout{ - Create: pluginsdk.DefaultTimeout(30 * time.Minute), + Create: pluginsdk.DefaultTimeout(60 * time.Minute), Read: pluginsdk.DefaultTimeout(5 * time.Minute), - Update: pluginsdk.DefaultTimeout(30 * time.Minute), - Delete: pluginsdk.DefaultTimeout(30 * time.Minute), + Update: pluginsdk.DefaultTimeout(60 * time.Minute), + Delete: pluginsdk.DefaultTimeout(60 * time.Minute), }, Importer: pluginsdk.ImporterValidatingResourceId(func(id string) error { @@ -132,6 +132,31 @@ func resourceAppConfiguration() *pluginsdk.Resource { ValidateFunc: validation.StringInSlice(configurationstores.PossibleValuesForPublicNetworkAccess(), true), }, + "replica": { + Type: pluginsdk.TypeSet, + Optional: true, + MinItems: 1, + Set: resourceConfigurationStoreReplicaHash, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "name": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validate.ConfigurationStoreReplicaName, + }, + "location": commonschema.LocationWithoutForceNew(), + "endpoint": { + Type: pluginsdk.TypeString, + Computed: true, + }, + "id": { + Type: pluginsdk.TypeString, + Computed: true, + }, + }, + }, + }, + "primary_read_key": { Type: pluginsdk.TypeList, Computed: true, @@ -256,7 +281,7 @@ func resourceAppConfigurationCreate(d *pluginsdk.ResourceData, meta interface{}) return tf.ImportAsExistsError("azurerm_app_configuration", resourceId.ID()) } - location := azure.NormalizeLocation(d.Get("location").(string)) + location := location.Normalize(d.Get("location").(string)) recoverSoftDeleted := false if meta.(*clients.Client).Features.AppConfiguration.RecoverSoftDeleted { @@ -325,6 +350,21 @@ func resourceAppConfigurationCreate(d *pluginsdk.ResourceData, meta interface{}) } meta.(*clients.Client).AppConfiguration.AddToCache(resourceId, *resp.Model.Properties.Endpoint) + expandedReplicas, err := expandAppConfigurationReplicas(d.Get("replica").(*pluginsdk.Set).List(), name, location) + if err != nil { + return fmt.Errorf("expanding `replica`: %+v", err) + } + + replicaClient := meta.(*clients.Client).AppConfiguration.ReplicasClient + for _, replica := range *expandedReplicas { + replicaId := replicas.NewReplicaID(resourceId.SubscriptionId, resourceId.ResourceGroupName, resourceId.ConfigurationStoreName, *replica.Name) + + if err := replicaClient.CreateThenPoll(ctx, replicaId, replica); err != nil { + return fmt.Errorf("creating %s: %+v", replicaId, err) + } + + } + return resourceAppConfigurationRead(d, meta) } @@ -434,6 +474,67 @@ func resourceAppConfigurationUpdate(d *pluginsdk.ResourceData, meta interface{}) return fmt.Errorf("updating %s: %+v", *id, err) } + if d.HasChange("replica") { + replicaClient := meta.(*clients.Client).AppConfiguration.ReplicasClient + operationsClient := meta.(*clients.Client).AppConfiguration.OperationsClient + + // check if a replica has been removed from config and if so, delete it + deleteReplicaIds := make([]replicas.ReplicaId, 0) + unchangedReplicaNames := make(map[string]struct{}, 0) + oldReplicas, newReplicas := d.GetChange("replica") + for _, oldReplica := range oldReplicas.(*pluginsdk.Set).List() { + isRemoved := true + oldReplicaMap := oldReplica.(map[string]interface{}) + + for _, newReplica := range newReplicas.(*pluginsdk.Set).List() { + newReplicaMap := newReplica.(map[string]interface{}) + + if strings.EqualFold(oldReplicaMap["name"].(string), newReplicaMap["name"].(string)) && strings.EqualFold(location.Normalize(oldReplicaMap["location"].(string)), location.Normalize(newReplicaMap["location"].(string))) { + unchangedReplicaNames[oldReplicaMap["name"].(string)] = struct{}{} + isRemoved = false + break + } + } + + if isRemoved { + deleteReplicaIds = append(deleteReplicaIds, replicas.NewReplicaID(id.SubscriptionId, id.ResourceGroupName, id.ConfigurationStoreName, oldReplicaMap["name"].(string))) + } + } + + if err := deleteReplicas(ctx, replicaClient, operationsClient, deleteReplicaIds); err != nil { + return err + } + + expandedReplicas, err := expandAppConfigurationReplicas(d.Get("replica").(*pluginsdk.Set).List(), id.ConfigurationStoreName, location.Normalize(existing.Model.Location)) + if err != nil { + return fmt.Errorf("expanding `replica`: %+v", err) + } + + // check if a replica has been added or an existing one changed its location, (re)create it + for _, replica := range *expandedReplicas { + if _, isUnchanged := unchangedReplicaNames[*replica.Name]; isUnchanged { + continue + } + + replicaId := replicas.NewReplicaID(id.SubscriptionId, id.ResourceGroupName, id.ConfigurationStoreName, *replica.Name) + + existingReplica, err := replicaClient.Get(ctx, replicaId) + if err != nil { + if !response.WasNotFound(existingReplica.HttpResponse) { + return fmt.Errorf("retrieving %s: %+v", replicaId, err) + } + } + + if !response.WasNotFound(existingReplica.HttpResponse) { + return fmt.Errorf("updating %s: replica %s already exists", *id, replicaId) + } + + if err = replicaClient.CreateThenPoll(ctx, replicaId, replica); err != nil { + return fmt.Errorf("creating %s: %+v", replicaId, err) + } + } + } + return resourceAppConfigurationRead(d, meta) } @@ -508,6 +609,18 @@ func resourceAppConfigurationRead(d *pluginsdk.ResourceData, meta interface{}) e return fmt.Errorf("setting `identity`: %+v", err) } + replicasClient := meta.(*clients.Client).AppConfiguration.ReplicasClient + resp, err := replicasClient.ListByConfigurationStoreComplete(ctx, replicas.NewConfigurationStoreID(id.SubscriptionId, id.ResourceGroupName, id.ConfigurationStoreName)) + if err != nil { + return fmt.Errorf("retrieving replicas for %s: %+v", *id, err) + } + + replica, err := flattenAppConfigurationReplicas(resp.Items) + if err != nil { + return fmt.Errorf("flattening replicas for %s: %+v", *id, err) + } + d.Set("replica", replica) + return tags.FlattenAndSet(d, model.Tags) } @@ -666,6 +779,46 @@ func expandAppConfigurationEncryption(input []interface{}) *configurationstores. return result } +func expandAppConfigurationReplicas(input []interface{}, configurationStoreName, configurationStoreLocation string) (*[]replicas.Replica, error) { + result := make([]replicas.Replica, 0) + + // check if there are duplicated replica names or locations + // location cannot be same as original configuration store and other replicas + locationSet := make(map[string]string, 0) + replicaNameSet := make(map[string]struct{}, 0) + + for _, v := range input { + replica := v.(map[string]interface{}) + replicaName := replica["name"].(string) + replicaLocation := location.Normalize(replica["location"].(string)) + if strings.EqualFold(replicaLocation, configurationStoreLocation) { + return nil, fmt.Errorf("location (%q) of replica %q is duplicated with original configuration store %q", replicaName, replicaLocation, configurationStoreName) + } + + if name, ok := locationSet[replicaLocation]; ok { + return nil, fmt.Errorf("location (%q) of replica %q is duplicated with replica %q", replicaName, replicaLocation, name) + } + locationSet[replicaLocation] = replicaName + + normalizedReplicaName := strings.ToLower(replicaName) + if _, ok := replicaNameSet[normalizedReplicaName]; ok { + return nil, fmt.Errorf("replica name %q is duplicated", replicaName) + } + replicaNameSet[normalizedReplicaName] = struct{}{} + + if len(replicaName)+len(configurationStoreName) > 60 { + return nil, fmt.Errorf("replica name %q is too long, the total length of replica name and configuration store name should be greater than 60", replicaName) + } + + result = append(result, replicas.Replica{ + Name: pointer.To(replicaName), + Location: pointer.To(replicaLocation), + }) + } + + return &result, nil +} + func flattenAppConfigurationAccessKeys(values []configurationstores.ApiKey) flattenedAccessKeys { result := flattenedAccessKeys{ primaryReadKey: make([]interface{}, 0), @@ -767,9 +920,8 @@ func resourceConfigurationStoreWaitForNameAvailable(ctx context.Context, client Timeout: time.Until(deadline), } - _, err := state.WaitForStateContext(ctx) - if err != nil { - return fmt.Errorf("waiting for the Name from %s to become available: %+v", configurationStoreId, err) + if _, err := state.WaitForStateContext(ctx); err != nil { + return fmt.Errorf("waiting for the name from %s to become available: %+v", configurationStoreId, err) } return nil @@ -806,3 +958,72 @@ func resourceConfigurationStoreNameAvailabilityRefreshFunc(ctx context.Context, return resp, "Available", nil } } + +func deleteReplicas(ctx context.Context, replicaClient *replicas.ReplicasClient, operationClient *operations.OperationsClient, configurationStoreReplicaIds []replicas.ReplicaId) error { + for _, configurationStoreReplicaId := range configurationStoreReplicaIds { + log.Printf("[DEBUG] Deleting Replica %q", configurationStoreReplicaId) + if err := replicaClient.DeleteThenPoll(ctx, configurationStoreReplicaId); err != nil { + return fmt.Errorf("deleting replica %q: %+v", configurationStoreReplicaId, err) + } + } + + for _, configurationStoreReplicaId := range configurationStoreReplicaIds { + if err := resourceConfigurationStoreReplicaWaitForNameAvailable(ctx, operationClient, configurationStoreReplicaId); err != nil { + return fmt.Errorf("waiting for replica %q name to be released: %+v", configurationStoreReplicaId, err) + } + } + + return nil +} + +func resourceConfigurationStoreReplicaWaitForNameAvailable(ctx context.Context, client *operations.OperationsClient, configurationStoreReplicaId replicas.ReplicaId) error { + deadline, ok := ctx.Deadline() + if !ok { + return fmt.Errorf("internal error: context had no deadline") + } + state := &pluginsdk.StateChangeConf{ + MinTimeout: 10 * time.Second, + ContinuousTargetOccurence: 2, + Pending: []string{"Unavailable"}, + Target: []string{"Available"}, + Refresh: resourceConfigurationStoreReplicaNameAvailabilityRefreshFunc(ctx, client, configurationStoreReplicaId), + Timeout: time.Until(deadline), + } + + if _, err := state.WaitForStateContext(ctx); err != nil { + return fmt.Errorf("waiting for the name from %s to become available: %+v", configurationStoreReplicaId, err) + } + + return nil +} + +func resourceConfigurationStoreReplicaNameAvailabilityRefreshFunc(ctx context.Context, client *operations.OperationsClient, configurationStoreReplicaId replicas.ReplicaId) pluginsdk.StateRefreshFunc { + return func() (interface{}, string, error) { + log.Printf("[DEBUG] Checking to see if the name for %s is available ..", configurationStoreReplicaId) + + subscriptionId := commonids.NewSubscriptionID(configurationStoreReplicaId.SubscriptionId) + + parameters := operations.CheckNameAvailabilityParameters{ + Name: fmt.Sprintf("%s-%s", configurationStoreReplicaId.ConfigurationStoreName, configurationStoreReplicaId.ReplicaName), + Type: operations.ConfigurationResourceTypeMicrosoftPointAppConfigurationConfigurationStores, + } + + resp, err := client.CheckNameAvailability(ctx, subscriptionId, parameters) + if err != nil { + return resp, "Error", fmt.Errorf("retrieving Deployment: %+v", err) + } + + if resp.Model == nil { + return resp, "Error", fmt.Errorf("unexpected null model of %s", configurationStoreReplicaId) + } + + if resp.Model.NameAvailable == nil { + return resp, "Error", fmt.Errorf("unexpected null NameAvailable property of %s", configurationStoreReplicaId) + } + + if !*resp.Model.NameAvailable { + return resp, "Unavailable", nil + } + return resp, "Available", nil + } +} diff --git a/internal/services/appconfiguration/app_configuration_resource_test.go b/internal/services/appconfiguration/app_configuration_resource_test.go index 76c01eaf8707..7605b102cad3 100644 --- a/internal/services/appconfiguration/app_configuration_resource_test.go +++ b/internal/services/appconfiguration/app_configuration_resource_test.go @@ -185,6 +185,86 @@ func TestAccAppConfiguration_encryption(t *testing.T) { }) } +func TestAccAppConfiguration_replica(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_app_configuration", "test") + r := AppConfigurationResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.replica(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccAppConfiguration_replicaRemove(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_app_configuration", "test") + r := AppConfigurationResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.replica(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.standard(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccAppConfiguration_replicaUpdate(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_app_configuration", "test") + r := AppConfigurationResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.standard(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.replica(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.replicaUpdated(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.replicaUpdatedPartial(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(), + }) +} + func TestAccAppConfiguration_encryptionUpdated(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_app_configuration", "test") r := AppConfigurationResource{} @@ -419,6 +499,91 @@ resource "azurerm_app_configuration" "test" { `, data.RandomInteger, data.Locations.Primary, data.RandomInteger) } +func (AppConfigurationResource) replica(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-appconfig-%d" + location = "%s" +} + +resource "azurerm_app_configuration" "test" { + name = "testaccappconf%d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + sku = "standard" + + replica { + name = "replica1" + location = "%s" + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.Locations.Ternary) +} + +func (AppConfigurationResource) replicaUpdated(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-appconfig-%d" + location = "%s" +} + +resource "azurerm_app_configuration" "test" { + name = "testaccappconf%d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + sku = "standard" + + replica { + name = "replica1" + location = "%s" + } + + replica { + name = "replica2" + location = "%s" + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.Locations.Ternary, data.Locations.Secondary) +} + +func (AppConfigurationResource) replicaUpdatedPartial(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-appconfig-%d" + location = "%s" +} + +resource "azurerm_app_configuration" "test" { + name = "testaccappconf%d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + sku = "standard" + + replica { + name = "replica3" + location = "%s" + } + + replica { + name = "replica2" + location = "%s" + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.Locations.Ternary, data.Locations.Secondary) +} + func (r AppConfigurationResource) requiresImport(data acceptance.TestData) string { template := r.standard(data) return fmt.Sprintf(` @@ -527,6 +692,16 @@ resource "azurerm_app_configuration" "test" { identity_client_id = azurerm_user_assigned_identity.test.client_id } + replica { + name = "replica1" + location = "%[3]s" + } + + replica { + name = "replica2" + location = "%[4]s" + } + tags = { environment = "development" } @@ -536,7 +711,7 @@ resource "azurerm_app_configuration" "test" { azurerm_key_vault_access_policy.server, ] } -`, data.RandomInteger, data.Locations.Primary) +`, data.RandomInteger, data.Locations.Primary, data.Locations.Secondary, data.Locations.Ternary) } func (AppConfigurationResource) identity(data acceptance.TestData) string { diff --git a/internal/services/appconfiguration/client/client.go b/internal/services/appconfiguration/client/client.go index 2053af90c79c..9cb616eb0438 100644 --- a/internal/services/appconfiguration/client/client.go +++ b/internal/services/appconfiguration/client/client.go @@ -4,14 +4,13 @@ package client import ( - "context" "fmt" "github.com/Azure/go-autorest/autorest" - "github.com/hashicorp/go-azure-helpers/lang/response" "github.com/hashicorp/go-azure-sdk/resource-manager/appconfiguration/2023-03-01/configurationstores" "github.com/hashicorp/go-azure-sdk/resource-manager/appconfiguration/2023-03-01/deletedconfigurationstores" "github.com/hashicorp/go-azure-sdk/resource-manager/appconfiguration/2023-03-01/operations" + "github.com/hashicorp/go-azure-sdk/resource-manager/appconfiguration/2023-03-01/replicas" authWrapper "github.com/hashicorp/go-azure-sdk/sdk/auth/autorest" "github.com/hashicorp/go-azure-sdk/sdk/environments" "github.com/hashicorp/terraform-provider-azurerm/internal/common" @@ -23,6 +22,7 @@ type Client struct { ConfigurationStoresClient *configurationstores.ConfigurationStoresClient DeletedConfigurationStoresClient *deletedconfigurationstores.DeletedConfigurationStoresClient OperationsClient *operations.OperationsClient + ReplicasClient *replicas.ReplicasClient authorizerFunc common.ApiAuthorizerFunc configureClientFunc func(c *autorest.Client, authorizer autorest.Authorizer) } @@ -54,73 +54,6 @@ func (c Client) LinkWorkaroundDataPlaneClientWithEndpoint(configurationStoreEndp return &workaroundClient, nil } -func (c Client) DataPlaneClient(ctx context.Context, configurationStoreId string) (*appconfiguration.BaseClient, error) { - appConfigId, err := configurationstores.ParseConfigurationStoreID(configurationStoreId) - if err != nil { - return nil, err - } - - // TODO: caching all of this - appConfig, err := c.ConfigurationStoresClient.Get(ctx, *appConfigId) - if err != nil { - if response.WasNotFound(appConfig.HttpResponse) { - return nil, nil - } - - return nil, err - } - - if appConfig.Model == nil || appConfig.Model.Properties == nil || appConfig.Model.Properties.Endpoint == nil { - return nil, fmt.Errorf("endpoint was nil") - } - - endpoint := *appConfig.Model.Properties.Endpoint - - api := environments.NewApiEndpoint("AppConfiguration", endpoint, nil) - appConfigAuth, err := c.authorizerFunc(api) - if err != nil { - return nil, fmt.Errorf("obtaining auth token for %q: %+v", endpoint, err) - } - - client := appconfiguration.NewWithoutDefaults("", endpoint) - c.configureClientFunc(&client.Client, authWrapper.AutorestAuthorizer(appConfigAuth)) - - return &client, nil -} - -func (c Client) LinkWorkaroundDataPlaneClient(ctx context.Context, configurationStoreId string) (*azuresdkhacks.DataPlaneClient, error) { - appConfigId, err := configurationstores.ParseConfigurationStoreID(configurationStoreId) - if err != nil { - return nil, err - } - - // TODO: caching all of this - appConfig, err := c.ConfigurationStoresClient.Get(ctx, *appConfigId) - if err != nil { - if response.WasNotFound(appConfig.HttpResponse) { - return nil, nil - } - - return nil, err - } - - if appConfig.Model == nil || appConfig.Model.Properties == nil || appConfig.Model.Properties.Endpoint == nil { - return nil, fmt.Errorf("endpoint was nil") - } - - api := environments.NewApiEndpoint("AppConfiguration", *appConfig.Model.Properties.Endpoint, nil) - appConfigAuth, err := c.authorizerFunc(api) - if err != nil { - return nil, fmt.Errorf("obtaining auth token for %q: %+v", *appConfig.Model.Properties.Endpoint, err) - } - - client := appconfiguration.NewWithoutDefaults("", *appConfig.Model.Properties.Endpoint) - c.configureClientFunc(&client.Client, authWrapper.AutorestAuthorizer(appConfigAuth)) - workaroundClient := azuresdkhacks.NewDataPlaneClient(client) - - return &workaroundClient, nil -} - func NewClient(o *common.ClientOptions) (*Client, error) { configurationStores, err := configurationstores.NewConfigurationStoresClientWithBaseURI(o.Environment.ResourceManager) if err != nil { @@ -140,10 +73,17 @@ func NewClient(o *common.ClientOptions) (*Client, error) { } o.Configure(operationsClient.Client, o.Authorizers.ResourceManager) + replicasClient, err := replicas.NewReplicasClientWithBaseURI(o.Environment.ResourceManager) + if err != nil { + return nil, fmt.Errorf("building DeletedConfigurationStores client: %+v", err) + } + o.Configure(replicasClient.Client, o.Authorizers.ResourceManager) + return &Client{ ConfigurationStoresClient: configurationStores, DeletedConfigurationStoresClient: deletedConfigurationStores, OperationsClient: operationsClient, + ReplicasClient: replicasClient, authorizerFunc: o.Authorizers.AuthorizerFunc, configureClientFunc: o.ConfigureClient, }, nil diff --git a/internal/services/appconfiguration/validate/configuration_store_replica_name.go b/internal/services/appconfiguration/validate/configuration_store_replica_name.go new file mode 100644 index 000000000000..9e7f513f7684 --- /dev/null +++ b/internal/services/appconfiguration/validate/configuration_store_replica_name.go @@ -0,0 +1,15 @@ +package validate + +import ( + "fmt" + "regexp" +) + +func ConfigurationStoreReplicaName(v interface{}, k string) (warnings []string, errors []error) { + value := v.(string) + if matched := regexp.MustCompile(`^[a-zA-Z0-9]{1,50}$`).Match([]byte(value)); !matched { + errors = append(errors, fmt.Errorf("%q Replica name may only contain alphanumeric characters and must be between 1-50 chars", k)) + } + + return warnings, errors +} diff --git a/internal/services/appconfiguration/validate/configuration_store_replica_name_test.go b/internal/services/appconfiguration/validate/configuration_store_replica_name_test.go new file mode 100644 index 000000000000..f0db179e91bd --- /dev/null +++ b/internal/services/appconfiguration/validate/configuration_store_replica_name_test.go @@ -0,0 +1,60 @@ +package validate + +import ( + "testing" +) + +func TestAppConfigurationReplicaName(t *testing.T) { + cases := []struct { + Value string + ErrCount int + }{ + { + Value: "four", + ErrCount: 0, + }, + { + Value: "5five", + ErrCount: 0, + }, + { + Value: "hello-world", + ErrCount: 1, + }, + { + Value: "hello_world", + ErrCount: 1, + }, + { + Value: "helloWorld", + ErrCount: 0, + }, + { + Value: "helloworld12", + ErrCount: 0, + }, + { + Value: "hello@world", + ErrCount: 1, + }, + { + Value: "qfvbdsbvipqdbwsbddbdcwqffewsqwcdw21ddwqwd3324120", + ErrCount: 0, + }, + { + Value: "qfvbdsbvipqdbwsbddbdcwqffewsqwcdw21ddwqwd332412020", + ErrCount: 0, + }, + { + Value: "qfvbdsbvipqdbwsbddbdcwqfjjfewsqwcdw21ddwqwd33241201", + ErrCount: 1, + }, + } + + for _, tc := range cases { + _, errors := ConfigurationStoreReplicaName(tc.Value, "azurerm_app_configuration") + if len(errors) != tc.ErrCount { + t.Fatalf("Expected the Azure App Configuration Replica Name to trigger a validation error: %v", tc) + } + } +} diff --git a/vendor/github.com/hashicorp/go-azure-sdk/resource-manager/appconfiguration/2023-03-01/replicas/README.md b/vendor/github.com/hashicorp/go-azure-sdk/resource-manager/appconfiguration/2023-03-01/replicas/README.md new file mode 100644 index 000000000000..2e4d4d60eb24 --- /dev/null +++ b/vendor/github.com/hashicorp/go-azure-sdk/resource-manager/appconfiguration/2023-03-01/replicas/README.md @@ -0,0 +1,82 @@ + +## `github.com/hashicorp/go-azure-sdk/resource-manager/appconfiguration/2023-03-01/replicas` Documentation + +The `replicas` SDK allows for interaction with the Azure Resource Manager Service `appconfiguration` (API Version `2023-03-01`). + +This readme covers example usages, but further information on [using this SDK can be found in the project root](https://github.com/hashicorp/go-azure-sdk/tree/main/docs). + +### Import Path + +```go +import "github.com/hashicorp/go-azure-sdk/resource-manager/appconfiguration/2023-03-01/replicas" +``` + + +### Client Initialization + +```go +client := replicas.NewReplicasClientWithBaseURI("https://management.azure.com") +client.Client.Authorizer = authorizer +``` + + +### Example Usage: `ReplicasClient.Create` + +```go +ctx := context.TODO() +id := replicas.NewReplicaID("12345678-1234-9876-4563-123456789012", "example-resource-group", "configurationStoreValue", "replicaValue") + +payload := replicas.Replica{ + // ... +} + + +if err := client.CreateThenPoll(ctx, id, payload); err != nil { + // handle the error +} +``` + + +### Example Usage: `ReplicasClient.Delete` + +```go +ctx := context.TODO() +id := replicas.NewReplicaID("12345678-1234-9876-4563-123456789012", "example-resource-group", "configurationStoreValue", "replicaValue") + +if err := client.DeleteThenPoll(ctx, id); err != nil { + // handle the error +} +``` + + +### Example Usage: `ReplicasClient.Get` + +```go +ctx := context.TODO() +id := replicas.NewReplicaID("12345678-1234-9876-4563-123456789012", "example-resource-group", "configurationStoreValue", "replicaValue") + +read, err := client.Get(ctx, id) +if err != nil { + // handle the error +} +if model := read.Model; model != nil { + // do something with the model/response object +} +``` + + +### Example Usage: `ReplicasClient.ListByConfigurationStore` + +```go +ctx := context.TODO() +id := replicas.NewConfigurationStoreID("12345678-1234-9876-4563-123456789012", "example-resource-group", "configurationStoreValue") + +// alternatively `client.ListByConfigurationStore(ctx, id)` can be used to do batched pagination +items, err := client.ListByConfigurationStoreComplete(ctx, id) +if err != nil { + // handle the error +} +for _, item := range items { + // do something +} +``` diff --git a/vendor/github.com/hashicorp/go-azure-sdk/resource-manager/appconfiguration/2023-03-01/replicas/client.go b/vendor/github.com/hashicorp/go-azure-sdk/resource-manager/appconfiguration/2023-03-01/replicas/client.go new file mode 100644 index 000000000000..d971742c1bdc --- /dev/null +++ b/vendor/github.com/hashicorp/go-azure-sdk/resource-manager/appconfiguration/2023-03-01/replicas/client.go @@ -0,0 +1,26 @@ +package replicas + +import ( + "fmt" + + "github.com/hashicorp/go-azure-sdk/sdk/client/resourcemanager" + sdkEnv "github.com/hashicorp/go-azure-sdk/sdk/environments" +) + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See NOTICE.txt in the project root for license information. + +type ReplicasClient struct { + Client *resourcemanager.Client +} + +func NewReplicasClientWithBaseURI(sdkApi sdkEnv.Api) (*ReplicasClient, error) { + client, err := resourcemanager.NewResourceManagerClient(sdkApi, "replicas", defaultApiVersion) + if err != nil { + return nil, fmt.Errorf("instantiating ReplicasClient: %+v", err) + } + + return &ReplicasClient{ + Client: client, + }, nil +} diff --git a/vendor/github.com/hashicorp/go-azure-sdk/resource-manager/appconfiguration/2023-03-01/replicas/constants.go b/vendor/github.com/hashicorp/go-azure-sdk/resource-manager/appconfiguration/2023-03-01/replicas/constants.go new file mode 100644 index 000000000000..2a2698617064 --- /dev/null +++ b/vendor/github.com/hashicorp/go-azure-sdk/resource-manager/appconfiguration/2023-03-01/replicas/constants.go @@ -0,0 +1,60 @@ +package replicas + +import ( + "encoding/json" + "fmt" + "strings" +) + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See NOTICE.txt in the project root for license information. + +type ReplicaProvisioningState string + +const ( + ReplicaProvisioningStateCanceled ReplicaProvisioningState = "Canceled" + ReplicaProvisioningStateCreating ReplicaProvisioningState = "Creating" + ReplicaProvisioningStateDeleting ReplicaProvisioningState = "Deleting" + ReplicaProvisioningStateFailed ReplicaProvisioningState = "Failed" + ReplicaProvisioningStateSucceeded ReplicaProvisioningState = "Succeeded" +) + +func PossibleValuesForReplicaProvisioningState() []string { + return []string{ + string(ReplicaProvisioningStateCanceled), + string(ReplicaProvisioningStateCreating), + string(ReplicaProvisioningStateDeleting), + string(ReplicaProvisioningStateFailed), + string(ReplicaProvisioningStateSucceeded), + } +} + +func (s *ReplicaProvisioningState) UnmarshalJSON(bytes []byte) error { + var decoded string + if err := json.Unmarshal(bytes, &decoded); err != nil { + return fmt.Errorf("unmarshaling: %+v", err) + } + out, err := parseReplicaProvisioningState(decoded) + if err != nil { + return fmt.Errorf("parsing %q: %+v", decoded, err) + } + *s = *out + return nil +} + +func parseReplicaProvisioningState(input string) (*ReplicaProvisioningState, error) { + vals := map[string]ReplicaProvisioningState{ + "canceled": ReplicaProvisioningStateCanceled, + "creating": ReplicaProvisioningStateCreating, + "deleting": ReplicaProvisioningStateDeleting, + "failed": ReplicaProvisioningStateFailed, + "succeeded": ReplicaProvisioningStateSucceeded, + } + if v, ok := vals[strings.ToLower(input)]; ok { + return &v, nil + } + + // otherwise presume it's an undefined value and best-effort it + out := ReplicaProvisioningState(input) + return &out, nil +} diff --git a/vendor/github.com/hashicorp/go-azure-sdk/resource-manager/appconfiguration/2023-03-01/replicas/id_configurationstore.go b/vendor/github.com/hashicorp/go-azure-sdk/resource-manager/appconfiguration/2023-03-01/replicas/id_configurationstore.go new file mode 100644 index 000000000000..64c7ad6a2440 --- /dev/null +++ b/vendor/github.com/hashicorp/go-azure-sdk/resource-manager/appconfiguration/2023-03-01/replicas/id_configurationstore.go @@ -0,0 +1,127 @@ +package replicas + +import ( + "fmt" + "strings" + + "github.com/hashicorp/go-azure-helpers/resourcemanager/resourceids" +) + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See NOTICE.txt in the project root for license information. + +var _ resourceids.ResourceId = ConfigurationStoreId{} + +// ConfigurationStoreId is a struct representing the Resource ID for a Configuration Store +type ConfigurationStoreId struct { + SubscriptionId string + ResourceGroupName string + ConfigurationStoreName string +} + +// NewConfigurationStoreID returns a new ConfigurationStoreId struct +func NewConfigurationStoreID(subscriptionId string, resourceGroupName string, configurationStoreName string) ConfigurationStoreId { + return ConfigurationStoreId{ + SubscriptionId: subscriptionId, + ResourceGroupName: resourceGroupName, + ConfigurationStoreName: configurationStoreName, + } +} + +// ParseConfigurationStoreID parses 'input' into a ConfigurationStoreId +func ParseConfigurationStoreID(input string) (*ConfigurationStoreId, error) { + parser := resourceids.NewParserFromResourceIdType(ConfigurationStoreId{}) + parsed, err := parser.Parse(input, false) + if err != nil { + return nil, fmt.Errorf("parsing %q: %+v", input, err) + } + + var ok bool + id := ConfigurationStoreId{} + + if id.SubscriptionId, ok = parsed.Parsed["subscriptionId"]; !ok { + return nil, resourceids.NewSegmentNotSpecifiedError(id, "subscriptionId", *parsed) + } + + if id.ResourceGroupName, ok = parsed.Parsed["resourceGroupName"]; !ok { + return nil, resourceids.NewSegmentNotSpecifiedError(id, "resourceGroupName", *parsed) + } + + if id.ConfigurationStoreName, ok = parsed.Parsed["configurationStoreName"]; !ok { + return nil, resourceids.NewSegmentNotSpecifiedError(id, "configurationStoreName", *parsed) + } + + return &id, nil +} + +// ParseConfigurationStoreIDInsensitively parses 'input' case-insensitively into a ConfigurationStoreId +// note: this method should only be used for API response data and not user input +func ParseConfigurationStoreIDInsensitively(input string) (*ConfigurationStoreId, error) { + parser := resourceids.NewParserFromResourceIdType(ConfigurationStoreId{}) + parsed, err := parser.Parse(input, true) + if err != nil { + return nil, fmt.Errorf("parsing %q: %+v", input, err) + } + + var ok bool + id := ConfigurationStoreId{} + + if id.SubscriptionId, ok = parsed.Parsed["subscriptionId"]; !ok { + return nil, resourceids.NewSegmentNotSpecifiedError(id, "subscriptionId", *parsed) + } + + if id.ResourceGroupName, ok = parsed.Parsed["resourceGroupName"]; !ok { + return nil, resourceids.NewSegmentNotSpecifiedError(id, "resourceGroupName", *parsed) + } + + if id.ConfigurationStoreName, ok = parsed.Parsed["configurationStoreName"]; !ok { + return nil, resourceids.NewSegmentNotSpecifiedError(id, "configurationStoreName", *parsed) + } + + return &id, nil +} + +// ValidateConfigurationStoreID checks that 'input' can be parsed as a Configuration Store ID +func ValidateConfigurationStoreID(input interface{}, key string) (warnings []string, errors []error) { + v, ok := input.(string) + if !ok { + errors = append(errors, fmt.Errorf("expected %q to be a string", key)) + return + } + + if _, err := ParseConfigurationStoreID(v); err != nil { + errors = append(errors, err) + } + + return +} + +// ID returns the formatted Configuration Store ID +func (id ConfigurationStoreId) ID() string { + fmtString := "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.AppConfiguration/configurationStores/%s" + return fmt.Sprintf(fmtString, id.SubscriptionId, id.ResourceGroupName, id.ConfigurationStoreName) +} + +// Segments returns a slice of Resource ID Segments which comprise this Configuration Store ID +func (id ConfigurationStoreId) Segments() []resourceids.Segment { + return []resourceids.Segment{ + resourceids.StaticSegment("staticSubscriptions", "subscriptions", "subscriptions"), + resourceids.SubscriptionIdSegment("subscriptionId", "12345678-1234-9876-4563-123456789012"), + resourceids.StaticSegment("staticResourceGroups", "resourceGroups", "resourceGroups"), + resourceids.ResourceGroupSegment("resourceGroupName", "example-resource-group"), + resourceids.StaticSegment("staticProviders", "providers", "providers"), + resourceids.ResourceProviderSegment("staticMicrosoftAppConfiguration", "Microsoft.AppConfiguration", "Microsoft.AppConfiguration"), + resourceids.StaticSegment("staticConfigurationStores", "configurationStores", "configurationStores"), + resourceids.UserSpecifiedSegment("configurationStoreName", "configurationStoreValue"), + } +} + +// String returns a human-readable description of this Configuration Store ID +func (id ConfigurationStoreId) String() string { + components := []string{ + fmt.Sprintf("Subscription: %q", id.SubscriptionId), + fmt.Sprintf("Resource Group Name: %q", id.ResourceGroupName), + fmt.Sprintf("Configuration Store Name: %q", id.ConfigurationStoreName), + } + return fmt.Sprintf("Configuration Store (%s)", strings.Join(components, "\n")) +} diff --git a/vendor/github.com/hashicorp/go-azure-sdk/resource-manager/appconfiguration/2023-03-01/replicas/id_replica.go b/vendor/github.com/hashicorp/go-azure-sdk/resource-manager/appconfiguration/2023-03-01/replicas/id_replica.go new file mode 100644 index 000000000000..464e8bae7e91 --- /dev/null +++ b/vendor/github.com/hashicorp/go-azure-sdk/resource-manager/appconfiguration/2023-03-01/replicas/id_replica.go @@ -0,0 +1,140 @@ +package replicas + +import ( + "fmt" + "strings" + + "github.com/hashicorp/go-azure-helpers/resourcemanager/resourceids" +) + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See NOTICE.txt in the project root for license information. + +var _ resourceids.ResourceId = ReplicaId{} + +// ReplicaId is a struct representing the Resource ID for a Replica +type ReplicaId struct { + SubscriptionId string + ResourceGroupName string + ConfigurationStoreName string + ReplicaName string +} + +// NewReplicaID returns a new ReplicaId struct +func NewReplicaID(subscriptionId string, resourceGroupName string, configurationStoreName string, replicaName string) ReplicaId { + return ReplicaId{ + SubscriptionId: subscriptionId, + ResourceGroupName: resourceGroupName, + ConfigurationStoreName: configurationStoreName, + ReplicaName: replicaName, + } +} + +// ParseReplicaID parses 'input' into a ReplicaId +func ParseReplicaID(input string) (*ReplicaId, error) { + parser := resourceids.NewParserFromResourceIdType(ReplicaId{}) + parsed, err := parser.Parse(input, false) + if err != nil { + return nil, fmt.Errorf("parsing %q: %+v", input, err) + } + + var ok bool + id := ReplicaId{} + + if id.SubscriptionId, ok = parsed.Parsed["subscriptionId"]; !ok { + return nil, resourceids.NewSegmentNotSpecifiedError(id, "subscriptionId", *parsed) + } + + if id.ResourceGroupName, ok = parsed.Parsed["resourceGroupName"]; !ok { + return nil, resourceids.NewSegmentNotSpecifiedError(id, "resourceGroupName", *parsed) + } + + if id.ConfigurationStoreName, ok = parsed.Parsed["configurationStoreName"]; !ok { + return nil, resourceids.NewSegmentNotSpecifiedError(id, "configurationStoreName", *parsed) + } + + if id.ReplicaName, ok = parsed.Parsed["replicaName"]; !ok { + return nil, resourceids.NewSegmentNotSpecifiedError(id, "replicaName", *parsed) + } + + return &id, nil +} + +// ParseReplicaIDInsensitively parses 'input' case-insensitively into a ReplicaId +// note: this method should only be used for API response data and not user input +func ParseReplicaIDInsensitively(input string) (*ReplicaId, error) { + parser := resourceids.NewParserFromResourceIdType(ReplicaId{}) + parsed, err := parser.Parse(input, true) + if err != nil { + return nil, fmt.Errorf("parsing %q: %+v", input, err) + } + + var ok bool + id := ReplicaId{} + + if id.SubscriptionId, ok = parsed.Parsed["subscriptionId"]; !ok { + return nil, resourceids.NewSegmentNotSpecifiedError(id, "subscriptionId", *parsed) + } + + if id.ResourceGroupName, ok = parsed.Parsed["resourceGroupName"]; !ok { + return nil, resourceids.NewSegmentNotSpecifiedError(id, "resourceGroupName", *parsed) + } + + if id.ConfigurationStoreName, ok = parsed.Parsed["configurationStoreName"]; !ok { + return nil, resourceids.NewSegmentNotSpecifiedError(id, "configurationStoreName", *parsed) + } + + if id.ReplicaName, ok = parsed.Parsed["replicaName"]; !ok { + return nil, resourceids.NewSegmentNotSpecifiedError(id, "replicaName", *parsed) + } + + return &id, nil +} + +// ValidateReplicaID checks that 'input' can be parsed as a Replica ID +func ValidateReplicaID(input interface{}, key string) (warnings []string, errors []error) { + v, ok := input.(string) + if !ok { + errors = append(errors, fmt.Errorf("expected %q to be a string", key)) + return + } + + if _, err := ParseReplicaID(v); err != nil { + errors = append(errors, err) + } + + return +} + +// ID returns the formatted Replica ID +func (id ReplicaId) ID() string { + fmtString := "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.AppConfiguration/configurationStores/%s/replicas/%s" + return fmt.Sprintf(fmtString, id.SubscriptionId, id.ResourceGroupName, id.ConfigurationStoreName, id.ReplicaName) +} + +// Segments returns a slice of Resource ID Segments which comprise this Replica ID +func (id ReplicaId) Segments() []resourceids.Segment { + return []resourceids.Segment{ + resourceids.StaticSegment("staticSubscriptions", "subscriptions", "subscriptions"), + resourceids.SubscriptionIdSegment("subscriptionId", "12345678-1234-9876-4563-123456789012"), + resourceids.StaticSegment("staticResourceGroups", "resourceGroups", "resourceGroups"), + resourceids.ResourceGroupSegment("resourceGroupName", "example-resource-group"), + resourceids.StaticSegment("staticProviders", "providers", "providers"), + resourceids.ResourceProviderSegment("staticMicrosoftAppConfiguration", "Microsoft.AppConfiguration", "Microsoft.AppConfiguration"), + resourceids.StaticSegment("staticConfigurationStores", "configurationStores", "configurationStores"), + resourceids.UserSpecifiedSegment("configurationStoreName", "configurationStoreValue"), + resourceids.StaticSegment("staticReplicas", "replicas", "replicas"), + resourceids.UserSpecifiedSegment("replicaName", "replicaValue"), + } +} + +// String returns a human-readable description of this Replica ID +func (id ReplicaId) String() string { + components := []string{ + fmt.Sprintf("Subscription: %q", id.SubscriptionId), + fmt.Sprintf("Resource Group Name: %q", id.ResourceGroupName), + fmt.Sprintf("Configuration Store Name: %q", id.ConfigurationStoreName), + fmt.Sprintf("Replica Name: %q", id.ReplicaName), + } + return fmt.Sprintf("Replica (%s)", strings.Join(components, "\n")) +} diff --git a/vendor/github.com/hashicorp/go-azure-sdk/resource-manager/appconfiguration/2023-03-01/replicas/method_create.go b/vendor/github.com/hashicorp/go-azure-sdk/resource-manager/appconfiguration/2023-03-01/replicas/method_create.go new file mode 100644 index 000000000000..58b36592090f --- /dev/null +++ b/vendor/github.com/hashicorp/go-azure-sdk/resource-manager/appconfiguration/2023-03-01/replicas/method_create.go @@ -0,0 +1,74 @@ +package replicas + +import ( + "context" + "fmt" + "net/http" + + "github.com/hashicorp/go-azure-sdk/sdk/client" + "github.com/hashicorp/go-azure-sdk/sdk/client/pollers" + "github.com/hashicorp/go-azure-sdk/sdk/client/resourcemanager" + "github.com/hashicorp/go-azure-sdk/sdk/odata" +) + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See NOTICE.txt in the project root for license information. + +type CreateOperationResponse struct { + Poller pollers.Poller + HttpResponse *http.Response + OData *odata.OData +} + +// Create ... +func (c ReplicasClient) Create(ctx context.Context, id ReplicaId, input Replica) (result CreateOperationResponse, err error) { + opts := client.RequestOptions{ + ContentType: "application/json; charset=utf-8", + ExpectedStatusCodes: []int{ + http.StatusCreated, + http.StatusOK, + }, + HttpMethod: http.MethodPut, + Path: id.ID(), + } + + req, err := c.Client.NewRequest(ctx, opts) + if err != nil { + return + } + + if err = req.Marshal(input); err != nil { + return + } + + var resp *client.Response + resp, err = req.Execute(ctx) + if resp != nil { + result.OData = resp.OData + result.HttpResponse = resp.Response + } + if err != nil { + return + } + + result.Poller, err = resourcemanager.PollerFromResponse(resp, c.Client) + if err != nil { + return + } + + return +} + +// CreateThenPoll performs Create then polls until it's completed +func (c ReplicasClient) CreateThenPoll(ctx context.Context, id ReplicaId, input Replica) error { + result, err := c.Create(ctx, id, input) + if err != nil { + return fmt.Errorf("performing Create: %+v", err) + } + + if err := result.Poller.PollUntilDone(ctx); err != nil { + return fmt.Errorf("polling after Create: %+v", err) + } + + return nil +} diff --git a/vendor/github.com/hashicorp/go-azure-sdk/resource-manager/appconfiguration/2023-03-01/replicas/method_delete.go b/vendor/github.com/hashicorp/go-azure-sdk/resource-manager/appconfiguration/2023-03-01/replicas/method_delete.go new file mode 100644 index 000000000000..a245c7836583 --- /dev/null +++ b/vendor/github.com/hashicorp/go-azure-sdk/resource-manager/appconfiguration/2023-03-01/replicas/method_delete.go @@ -0,0 +1,71 @@ +package replicas + +import ( + "context" + "fmt" + "net/http" + + "github.com/hashicorp/go-azure-sdk/sdk/client" + "github.com/hashicorp/go-azure-sdk/sdk/client/pollers" + "github.com/hashicorp/go-azure-sdk/sdk/client/resourcemanager" + "github.com/hashicorp/go-azure-sdk/sdk/odata" +) + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See NOTICE.txt in the project root for license information. + +type DeleteOperationResponse struct { + Poller pollers.Poller + HttpResponse *http.Response + OData *odata.OData +} + +// Delete ... +func (c ReplicasClient) Delete(ctx context.Context, id ReplicaId) (result DeleteOperationResponse, err error) { + opts := client.RequestOptions{ + ContentType: "application/json; charset=utf-8", + ExpectedStatusCodes: []int{ + http.StatusAccepted, + http.StatusNoContent, + http.StatusOK, + }, + HttpMethod: http.MethodDelete, + Path: id.ID(), + } + + req, err := c.Client.NewRequest(ctx, opts) + if err != nil { + return + } + + var resp *client.Response + resp, err = req.Execute(ctx) + if resp != nil { + result.OData = resp.OData + result.HttpResponse = resp.Response + } + if err != nil { + return + } + + result.Poller, err = resourcemanager.PollerFromResponse(resp, c.Client) + if err != nil { + return + } + + return +} + +// DeleteThenPoll performs Delete then polls until it's completed +func (c ReplicasClient) DeleteThenPoll(ctx context.Context, id ReplicaId) error { + result, err := c.Delete(ctx, id) + if err != nil { + return fmt.Errorf("performing Delete: %+v", err) + } + + if err := result.Poller.PollUntilDone(ctx); err != nil { + return fmt.Errorf("polling after Delete: %+v", err) + } + + return nil +} diff --git a/vendor/github.com/hashicorp/go-azure-sdk/resource-manager/appconfiguration/2023-03-01/replicas/method_get.go b/vendor/github.com/hashicorp/go-azure-sdk/resource-manager/appconfiguration/2023-03-01/replicas/method_get.go new file mode 100644 index 000000000000..45e517f34af7 --- /dev/null +++ b/vendor/github.com/hashicorp/go-azure-sdk/resource-manager/appconfiguration/2023-03-01/replicas/method_get.go @@ -0,0 +1,51 @@ +package replicas + +import ( + "context" + "net/http" + + "github.com/hashicorp/go-azure-sdk/sdk/client" + "github.com/hashicorp/go-azure-sdk/sdk/odata" +) + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See NOTICE.txt in the project root for license information. + +type GetOperationResponse struct { + HttpResponse *http.Response + OData *odata.OData + Model *Replica +} + +// Get ... +func (c ReplicasClient) Get(ctx context.Context, id ReplicaId) (result GetOperationResponse, err error) { + opts := client.RequestOptions{ + ContentType: "application/json; charset=utf-8", + ExpectedStatusCodes: []int{ + http.StatusOK, + }, + HttpMethod: http.MethodGet, + Path: id.ID(), + } + + req, err := c.Client.NewRequest(ctx, opts) + if err != nil { + return + } + + var resp *client.Response + resp, err = req.Execute(ctx) + if resp != nil { + result.OData = resp.OData + result.HttpResponse = resp.Response + } + if err != nil { + return + } + + if err = resp.Unmarshal(&result.Model); err != nil { + return + } + + return +} diff --git a/vendor/github.com/hashicorp/go-azure-sdk/resource-manager/appconfiguration/2023-03-01/replicas/method_listbyconfigurationstore.go b/vendor/github.com/hashicorp/go-azure-sdk/resource-manager/appconfiguration/2023-03-01/replicas/method_listbyconfigurationstore.go new file mode 100644 index 000000000000..01a7747d8076 --- /dev/null +++ b/vendor/github.com/hashicorp/go-azure-sdk/resource-manager/appconfiguration/2023-03-01/replicas/method_listbyconfigurationstore.go @@ -0,0 +1,89 @@ +package replicas + +import ( + "context" + "fmt" + "net/http" + + "github.com/hashicorp/go-azure-sdk/sdk/client" + "github.com/hashicorp/go-azure-sdk/sdk/odata" +) + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See NOTICE.txt in the project root for license information. + +type ListByConfigurationStoreOperationResponse struct { + HttpResponse *http.Response + OData *odata.OData + Model *[]Replica +} + +type ListByConfigurationStoreCompleteResult struct { + Items []Replica +} + +// ListByConfigurationStore ... +func (c ReplicasClient) ListByConfigurationStore(ctx context.Context, id ConfigurationStoreId) (result ListByConfigurationStoreOperationResponse, err error) { + opts := client.RequestOptions{ + ContentType: "application/json; charset=utf-8", + ExpectedStatusCodes: []int{ + http.StatusOK, + }, + HttpMethod: http.MethodGet, + Path: fmt.Sprintf("%s/replicas", id.ID()), + } + + req, err := c.Client.NewRequest(ctx, opts) + if err != nil { + return + } + + var resp *client.Response + resp, err = req.ExecutePaged(ctx) + if resp != nil { + result.OData = resp.OData + result.HttpResponse = resp.Response + } + if err != nil { + return + } + + var values struct { + Values *[]Replica `json:"value"` + } + if err = resp.Unmarshal(&values); err != nil { + return + } + + result.Model = values.Values + + return +} + +// ListByConfigurationStoreComplete retrieves all the results into a single object +func (c ReplicasClient) ListByConfigurationStoreComplete(ctx context.Context, id ConfigurationStoreId) (ListByConfigurationStoreCompleteResult, error) { + return c.ListByConfigurationStoreCompleteMatchingPredicate(ctx, id, ReplicaOperationPredicate{}) +} + +// ListByConfigurationStoreCompleteMatchingPredicate retrieves all the results and then applies the predicate +func (c ReplicasClient) ListByConfigurationStoreCompleteMatchingPredicate(ctx context.Context, id ConfigurationStoreId, predicate ReplicaOperationPredicate) (result ListByConfigurationStoreCompleteResult, err error) { + items := make([]Replica, 0) + + resp, err := c.ListByConfigurationStore(ctx, id) + if err != nil { + err = fmt.Errorf("loading results: %+v", err) + return + } + if resp.Model != nil { + for _, v := range *resp.Model { + if predicate.Matches(v) { + items = append(items, v) + } + } + } + + result = ListByConfigurationStoreCompleteResult{ + Items: items, + } + return +} diff --git a/vendor/github.com/hashicorp/go-azure-sdk/resource-manager/appconfiguration/2023-03-01/replicas/model_replica.go b/vendor/github.com/hashicorp/go-azure-sdk/resource-manager/appconfiguration/2023-03-01/replicas/model_replica.go new file mode 100644 index 000000000000..95deda8039b9 --- /dev/null +++ b/vendor/github.com/hashicorp/go-azure-sdk/resource-manager/appconfiguration/2023-03-01/replicas/model_replica.go @@ -0,0 +1,17 @@ +package replicas + +import ( + "github.com/hashicorp/go-azure-helpers/resourcemanager/systemdata" +) + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See NOTICE.txt in the project root for license information. + +type Replica struct { + Id *string `json:"id,omitempty"` + Location *string `json:"location,omitempty"` + Name *string `json:"name,omitempty"` + Properties *ReplicaProperties `json:"properties,omitempty"` + SystemData *systemdata.SystemData `json:"systemData,omitempty"` + Type *string `json:"type,omitempty"` +} diff --git a/vendor/github.com/hashicorp/go-azure-sdk/resource-manager/appconfiguration/2023-03-01/replicas/model_replicaproperties.go b/vendor/github.com/hashicorp/go-azure-sdk/resource-manager/appconfiguration/2023-03-01/replicas/model_replicaproperties.go new file mode 100644 index 000000000000..361a71b2a0d2 --- /dev/null +++ b/vendor/github.com/hashicorp/go-azure-sdk/resource-manager/appconfiguration/2023-03-01/replicas/model_replicaproperties.go @@ -0,0 +1,9 @@ +package replicas + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See NOTICE.txt in the project root for license information. + +type ReplicaProperties struct { + Endpoint *string `json:"endpoint,omitempty"` + ProvisioningState *ReplicaProvisioningState `json:"provisioningState,omitempty"` +} diff --git a/vendor/github.com/hashicorp/go-azure-sdk/resource-manager/appconfiguration/2023-03-01/replicas/predicates.go b/vendor/github.com/hashicorp/go-azure-sdk/resource-manager/appconfiguration/2023-03-01/replicas/predicates.go new file mode 100644 index 000000000000..436f7639b383 --- /dev/null +++ b/vendor/github.com/hashicorp/go-azure-sdk/resource-manager/appconfiguration/2023-03-01/replicas/predicates.go @@ -0,0 +1,32 @@ +package replicas + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See NOTICE.txt in the project root for license information. + +type ReplicaOperationPredicate struct { + Id *string + Location *string + Name *string + Type *string +} + +func (p ReplicaOperationPredicate) Matches(input Replica) bool { + + if p.Id != nil && (input.Id == nil || *p.Id != *input.Id) { + return false + } + + if p.Location != nil && (input.Location == nil || *p.Location != *input.Location) { + return false + } + + if p.Name != nil && (input.Name == nil || *p.Name != *input.Name) { + return false + } + + if p.Type != nil && (input.Type == nil || *p.Type != *input.Type) { + return false + } + + return true +} diff --git a/vendor/github.com/hashicorp/go-azure-sdk/resource-manager/appconfiguration/2023-03-01/replicas/version.go b/vendor/github.com/hashicorp/go-azure-sdk/resource-manager/appconfiguration/2023-03-01/replicas/version.go new file mode 100644 index 000000000000..198b0d4d213b --- /dev/null +++ b/vendor/github.com/hashicorp/go-azure-sdk/resource-manager/appconfiguration/2023-03-01/replicas/version.go @@ -0,0 +1,12 @@ +package replicas + +import "fmt" + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See NOTICE.txt in the project root for license information. + +const defaultApiVersion = "2023-03-01" + +func userAgent() string { + return fmt.Sprintf("hashicorp/go-azure-sdk/replicas/%s", defaultApiVersion) +} diff --git a/vendor/modules.txt b/vendor/modules.txt index e3ab2e90dba3..cf90be61a402 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -216,6 +216,7 @@ github.com/hashicorp/go-azure-sdk/resource-manager/apimanagement/2021-08-01/user github.com/hashicorp/go-azure-sdk/resource-manager/appconfiguration/2023-03-01/configurationstores github.com/hashicorp/go-azure-sdk/resource-manager/appconfiguration/2023-03-01/deletedconfigurationstores github.com/hashicorp/go-azure-sdk/resource-manager/appconfiguration/2023-03-01/operations +github.com/hashicorp/go-azure-sdk/resource-manager/appconfiguration/2023-03-01/replicas github.com/hashicorp/go-azure-sdk/resource-manager/applicationinsights/2020-11-20/workbooktemplatesapis github.com/hashicorp/go-azure-sdk/resource-manager/applicationinsights/2022-04-01/workbooksapis github.com/hashicorp/go-azure-sdk/resource-manager/applicationinsights/2022-06-15/webtestsapis diff --git a/website/docs/d/app_configuration.html.markdown b/website/docs/d/app_configuration.html.markdown index a61a8dd59528..8a042fd13f86 100644 --- a/website/docs/d/app_configuration.html.markdown +++ b/website/docs/d/app_configuration.html.markdown @@ -53,6 +53,8 @@ In addition to the Arguments listed above - the following Attributes are exporte * `purge_protection_enabled` - Whether Purge Protection is enabled. +* `replica` - One or more `replica` blocks as defined below. + * `secondary_read_key` - A `secondary_read_key` block as defined below containing the secondary read access key. * `secondary_write_key` - A `secondary_write_key` block as defined below containing the secondary write access key. @@ -83,6 +85,19 @@ A `primary_write_key` block exports the following: * `secret` - The Secret of the Access Key. +--- + +A `replica` block exports the following: + +* `id` - The ID of the App Configuration Replica. + +* `endpoint` - The URL of the App Configuration Replica. + +* `location` - The supported Azure location where the App Configuration Replica exists. + +* `name` - The name of the App Configuration Replica. + + --- A `secondary_read_key` block exports the following: diff --git a/website/docs/r/app_configuration.html.markdown b/website/docs/r/app_configuration.html.markdown index 5921934f6c4b..115b6f4cfab6 100644 --- a/website/docs/r/app_configuration.html.markdown +++ b/website/docs/r/app_configuration.html.markdown @@ -127,6 +127,11 @@ resource "azurerm_app_configuration" "example" { identity_client_id = azurerm_user_assigned_identity.example.client_id } + replica { + name = "replica1" + location = "West US" + } + tags = { environment = "development" } @@ -164,6 +169,8 @@ The following arguments are supported: !> **Note:** Once Purge Protection has been enabled it's not possible to disable it. Deleting the App Configuration with Purge Protection enabled will schedule the App Configuration to be deleted (which will happen by Azure in the configured number of days). +* `replica` - (Optional) One or more `replica` blocks as defined below. + * `sku` - (Optional) The SKU name of the App Configuration. Possible values are `free` and `standard`. Defaults to `free`. * `soft_delete_retention_days` - (Optional) The number of days that items should be retained for once soft-deleted. This field only works for `standard` sku. This value can be between `1` and `7` days. Defaults to `7`. Changing this forces a new resource to be created. @@ -192,6 +199,14 @@ An `identity` block supports the following: --- +A `replica` block supports the following: + +* `location` - (Required) Specifies the supported Azure location where the replica exists. Changing this forces a new replica to be created. + +* `name` - (Required) Specifies the name of the replica. Changing this forces a new replica to be created. + +--- + ## Attributes Reference In addition to the Arguments listed above - the following Attributes are exported: @@ -220,6 +235,14 @@ An `identity` block exports the following: --- +A `replica` block exports the following: + +* `id` - The ID of the App Configuration Replica. + +* `endpoint` - The URL of the App Configuration Replica. + +--- + A `primary_read_key` block exports the following: * `connection_string` - The Connection String for this Access Key - comprising of the Endpoint, ID and Secret. @@ -262,10 +285,10 @@ A `secondary_write_key` block exports the following: The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/language/resources/syntax#operation-timeouts) for certain actions: -* `create` - (Defaults to 30 minutes) Used when creating the App Configuration. -* `update` - (Defaults to 30 minutes) Used when updating the App Configuration. +* `create` - (Defaults to 60 minutes) Used when creating the App Configuration. +* `update` - (Defaults to 60 minutes) Used when updating the App Configuration. * `read` - (Defaults to 5 minutes) Used when retrieving the App Configuration. -* `delete` - (Defaults to 30 minutes) Used when deleting the App Configuration. +* `delete` - (Defaults to 60 minutes) Used when deleting the App Configuration. ## Import