diff --git a/azurerm/internal/services/postgres/client/client.go b/azurerm/internal/services/postgres/client/client.go index 39e56844d646..5ec30cf626ae 100644 --- a/azurerm/internal/services/postgres/client/client.go +++ b/azurerm/internal/services/postgres/client/client.go @@ -14,6 +14,7 @@ type Client struct { ServerSecurityAlertPoliciesClient *postgresql.ServerSecurityAlertPoliciesClient VirtualNetworkRulesClient *postgresql.VirtualNetworkRulesClient ServerAdministratorsClient *postgresql.ServerAdministratorsClient + ReplicasClient *postgresql.ReplicasClient } func NewClient(o *common.ClientOptions) *Client { @@ -41,6 +42,9 @@ func NewClient(o *common.ClientOptions) *Client { serverAdministratorsClient := postgresql.NewServerAdministratorsClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) o.ConfigureClient(&serverAdministratorsClient.Client, o.ResourceManagerAuthorizer) + replicasClient := postgresql.NewReplicasClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) + o.ConfigureClient(&replicasClient.Client, o.ResourceManagerAuthorizer) + return &Client{ ConfigurationsClient: &configurationsClient, DatabasesClient: &databasesClient, @@ -50,5 +54,6 @@ func NewClient(o *common.ClientOptions) *Client { ServerSecurityAlertPoliciesClient: &serverSecurityAlertPoliciesClient, VirtualNetworkRulesClient: &virtualNetworkRulesClient, ServerAdministratorsClient: &serverAdministratorsClient, + ReplicasClient: &replicasClient, } } diff --git a/azurerm/internal/services/postgres/postgresql_server_resource.go b/azurerm/internal/services/postgres/postgresql_server_resource.go index 22c60f3e20d8..17c26746374f 100644 --- a/azurerm/internal/services/postgres/postgresql_server_resource.go +++ b/azurerm/internal/services/postgres/postgresql_server_resource.go @@ -16,6 +16,7 @@ import ( "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/locks" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/postgres/parse" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/postgres/validate" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tags" @@ -28,6 +29,29 @@ const ( postgreSQLServerResourceName = "azurerm_postgresql_server" ) +var skuList = []string{ + "B_Gen4_1", + "B_Gen4_2", + "B_Gen5_1", + "B_Gen5_2", + "GP_Gen4_2", + "GP_Gen4_4", + "GP_Gen4_8", + "GP_Gen4_16", + "GP_Gen4_32", + "GP_Gen5_2", + "GP_Gen5_4", + "GP_Gen5_8", + "GP_Gen5_16", + "GP_Gen5_32", + "GP_Gen5_64", + "MO_Gen5_2", + "MO_Gen5_4", + "MO_Gen5_8", + "MO_Gen5_16", + "MO_Gen5_32", +} + func resourcePostgreSQLServer() *schema.Resource { return &schema.Resource{ Create: resourcePostgreSQLServerCreate, @@ -70,30 +94,9 @@ func resourcePostgreSQLServer() *schema.Resource { "resource_group_name": azure.SchemaResourceGroupName(), "sku_name": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringInSlice([]string{ - "B_Gen4_1", - "B_Gen4_2", - "B_Gen5_1", - "B_Gen5_2", - "GP_Gen4_2", - "GP_Gen4_4", - "GP_Gen4_8", - "GP_Gen4_16", - "GP_Gen4_32", - "GP_Gen5_2", - "GP_Gen5_4", - "GP_Gen5_8", - "GP_Gen5_16", - "GP_Gen5_32", - "GP_Gen5_64", - "MO_Gen5_2", - "MO_Gen5_4", - "MO_Gen5_8", - "MO_Gen5_16", - "MO_Gen5_32", - }, false), + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice(skuList, false), }, "version": { @@ -569,6 +572,7 @@ func resourcePostgreSQLServerCreate(d *schema.ResourceData, meta interface{}) er func resourcePostgreSQLServerUpdate(d *schema.ResourceData, meta interface{}) error { client := meta.(*clients.Client).Postgres.ServersClient securityClient := meta.(*clients.Client).Postgres.ServerSecurityAlertPoliciesClient + replicasClient := meta.(*clients.Client).Postgres.ReplicasClient ctx, cancel := timeouts.ForUpdate(meta.(*clients.Client).StopContext, d) defer cancel() @@ -581,6 +585,18 @@ func resourcePostgreSQLServerUpdate(d *schema.ResourceData, meta interface{}) er return fmt.Errorf("parsing Postgres Server ID : %v", err) } + // Locks for scaling of replica functionality + mode := postgresql.CreateMode(d.Get("create_mode").(string)) + source := d.Get("creation_source_server_id").(string) + + primaryID := id.String() + if mode == postgresql.CreateModeReplica { + primaryID = source + } + + locks.ByID(primaryID) + defer locks.UnlockByID(primaryID) + sku, err := expandServerSkuName(d.Get("sku_name").(string)) if err != nil { return fmt.Errorf("expanding `sku_name` for PostgreSQL Server %s (Resource Group %q): %v", id.Name, id.ResourceGroup, err) @@ -612,6 +628,29 @@ func resourcePostgreSQLServerUpdate(d *schema.ResourceData, meta interface{}) er Tags: tags.Expand(d.Get("tags").(map[string]interface{})), } + if d.HasChange("sku_name") && mode != postgresql.CreateModeReplica { + oldRaw, newRaw := d.GetChange("sku_name") + old := oldRaw.(string) + new := newRaw.(string) + + if indexOfSku(old) < indexOfSku(new) { + listReplicas, err := replicasClient.ListByServer(ctx, id.ResourceGroup, id.Name) + if err != nil { + return fmt.Errorf("request error for list of replicas for PostgreSQL Server %q (Resource Group %q): %+v", id.Name, id.ResourceGroup, err) + } + for _, replica := range *listReplicas.Value { + future, err := client.Update(ctx, id.ResourceGroup, *replica.Name, properties) + if err != nil { + return fmt.Errorf("updating PostgreSQL Server Replica %q (Resource Group %q): %+v", *replica.Name, id.ResourceGroup, err) + } + + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("waiting for update of PostgreSQL Server Replica %q (Resource Group %q): %+v", id.Name, id.ResourceGroup, err) + } + } + } + } + future, err := client.Update(ctx, id.ResourceGroup, id.Name, properties) if err != nil { return fmt.Errorf("updating PostgreSQL Server %q (Resource Group %q): %+v", id.Name, id.ResourceGroup, err) @@ -621,6 +660,29 @@ func resourcePostgreSQLServerUpdate(d *schema.ResourceData, meta interface{}) er return fmt.Errorf("waiting for update of PostgreSQL Server %q (Resource Group %q): %+v", id.Name, id.ResourceGroup, err) } + if d.HasChange("sku_name") && mode != postgresql.CreateModeReplica { + oldRaw, newRaw := d.GetChange("sku_name") + old := oldRaw.(string) + new := newRaw.(string) + + if indexOfSku(old) > indexOfSku(new) { + listReplicas, err := replicasClient.ListByServer(ctx, id.ResourceGroup, id.Name) + if err != nil { + return fmt.Errorf("request error for list of replicas for PostgreSQL Server %q (Resource Group %q): %+v", id.Name, id.ResourceGroup, err) + } + for _, replica := range *listReplicas.Value { + future, err := client.Update(ctx, id.ResourceGroup, *replica.Name, properties) + if err != nil { + return fmt.Errorf("updating PostgreSQL Server Replica %q (Resource Group %q): %+v", *replica.Name, id.ResourceGroup, err) + } + + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("waiting for update of PostgreSQL Server Replica %q (Resource Group %q): %+v", id.Name, id.ResourceGroup, err) + } + } + } + } + if v, ok := d.GetOk("threat_detection_policy"); ok { alert := expandSecurityAlertPolicy(v) if alert != nil { @@ -750,6 +812,15 @@ func resourcePostgreSQLServerDelete(d *schema.ResourceData, meta interface{}) er return nil } +func indexOfSku(skuName string) int { + for k, v := range skuList { + if skuName == v { + return k + } + } + return -1 // not found. +} + func expandServerSkuName(skuName string) (*postgresql.Sku, error) { parts := strings.Split(skuName, "_") if len(parts) != 3 {