Skip to content

Commit

Permalink
Including #25508
Browse files Browse the repository at this point in the history
* Initial Check-in...

* Slight tweak to behavior...

* Fix lint errors...

* Update documentation, comments and test case...

* Update documentation and fix test case...

* Update internal/services/mssql/mssql_database_resource.go

---------

Co-authored-by: kt <[email protected]>
  • Loading branch information
WodansSon and katbyte authored Apr 5, 2024
1 parent 8b8edff commit 8b9f54d
Show file tree
Hide file tree
Showing 7 changed files with 109 additions and 54 deletions.
4 changes: 2 additions & 2 deletions internal/services/mssql/helper/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,11 +108,11 @@ func FindDatabaseReplicationPartners(ctx context.Context, databasesClient *datab
}

if partnerDatabase.Id != nil && partnerDatabase.Properties != nil && partnerDatabase.Properties.PreferredEnclaveType != nil {
if primaryEnclaveType == *partnerDatabase.Properties.PreferredEnclaveType {
if primaryEnclaveType != "" && primaryEnclaveType == *partnerDatabase.Properties.PreferredEnclaveType {
log.Printf("[INFO] Found Partner %s", partnerDatabaseId)
partnerDatabases = append(partnerDatabases, *partnerDatabase)
} else {
log.Printf("[INFO] Mismatch of possible Partner Database based on enclave type (%s vs %s) for %s", string(primaryEnclaveType), string(*partnerDatabase.Properties.PreferredEnclaveType), id)
log.Printf("[INFO] Mismatch of possible Partner Database based on enclave type (%q vs %q) for %s", primaryEnclaveType, string(*partnerDatabase.Properties.PreferredEnclaveType), id)
}
}
}
Expand Down
64 changes: 42 additions & 22 deletions internal/services/mssql/mssql_database_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"github.com/hashicorp/go-azure-sdk/resource-manager/sql/2023-02-01-preview/backupshorttermretentionpolicies"
"github.com/hashicorp/go-azure-sdk/resource-manager/sql/2023-02-01-preview/databases"
"github.com/hashicorp/go-azure-sdk/resource-manager/sql/2023-02-01-preview/databasesecurityalertpolicies"
"github.com/hashicorp/go-azure-sdk/resource-manager/sql/2023-02-01-preview/elasticpools"
"github.com/hashicorp/go-azure-sdk/resource-manager/sql/2023-02-01-preview/geobackuppolicies"
"github.com/hashicorp/go-azure-sdk/resource-manager/sql/2023-02-01-preview/longtermretentionpolicies"
"github.com/hashicorp/go-azure-sdk/resource-manager/sql/2023-02-01-preview/servers"
Expand Down Expand Up @@ -69,9 +70,20 @@ func resourceMsSqlDatabase() *pluginsdk.Resource {

CustomizeDiff: pluginsdk.CustomDiffWithAll(
pluginsdk.ForceNewIfChange("sku_name", func(ctx context.Context, old, new, _ interface{}) bool {
// "hyperscale can not change to other sku
// hyperscale can not be changed to another sku
return strings.HasPrefix(old.(string), "HS") && !strings.HasPrefix(new.(string), "HS")
}),
pluginsdk.ForceNewIfChange("enclave_type", func(ctx context.Context, old, new, _ interface{}) bool {
// enclave_type cannot be removed once it has been set
// but can be changed between VBS and Default...
// this Diff will not work until 4.0 when we remove
// the computed property from the field scheam.
if old.(string) != "" && new.(string) == "" {
return true
}

return false
}),
func(ctx context.Context, d *schema.ResourceDiff, meta interface{}) error {
transparentDataEncryption := d.Get("transparent_data_encryption_enabled").(bool)
skuName := d.Get("sku_name").(string)
Expand Down Expand Up @@ -102,10 +114,12 @@ func resourceMsSqlDatabaseImporter(ctx context.Context, d *pluginsdk.ResourceDat
return nil, err
}

enclaveType := databases.AlwaysEncryptedEnclaveTypeDefault
if v := d.Get("enclave_type").(string); v != "" {
enclaveType = databases.AlwaysEncryptedEnclaveTypeVBS
// NOTE: The service default is actually nil/empty which indicates enclave is disabled. the value `Default` is NOT the default.
var enclaveType databases.AlwaysEncryptedEnclaveType
if v, ok := d.GetOk("enclave_type"); ok && v.(string) != "" {
enclaveType = databases.AlwaysEncryptedEnclaveType(v.(string))
}
d.Set("enclave_type", enclaveType)

partnerDatabases, err := helper.FindDatabaseReplicationPartners(ctx, client, legacyreplicationLinksClient, resourcesClient, *id, enclaveType, []sql.ReplicationRole{sql.ReplicationRolePrimary})
if err != nil {
Expand Down Expand Up @@ -200,11 +214,10 @@ func resourceMsSqlDatabaseCreate(d *pluginsdk.ResourceData, meta interface{}) er
locks.ByID(id.ID())
defer locks.UnlockByID(id.ID())

// NOTE: Set the default value, if the field exists in the config the only value
// that it could be is 'VBS'...
enclaveType := databases.AlwaysEncryptedEnclaveTypeDefault
if _, ok := d.GetOk("enclave_type"); ok {
enclaveType = databases.AlwaysEncryptedEnclaveTypeVBS
// NOTE: The service default is actually nil/empty which indicates enclave is disabled. the value `Default` is NOT the default.
var enclaveType databases.AlwaysEncryptedEnclaveType
if v, ok := d.GetOk("enclave_type"); ok && v.(string) != "" {
enclaveType = databases.AlwaysEncryptedEnclaveType(v.(string))
}

skuName := d.Get("sku_name").(string)
Expand Down Expand Up @@ -247,7 +260,7 @@ func resourceMsSqlDatabaseCreate(d *pluginsdk.ResourceData, meta interface{}) er
}

// NOTE: If the database is being added to an elastic pool, we need to GET the elastic pool and check
// if the 'enclave_type' match. If they don't we need to raise an error stating that they must match.
// if the 'enclave_type' matches. If they don't we need to raise an error stating that they must match.
elasticPoolId := d.Get("elastic_pool_id").(string)
if elasticPoolId != "" {
elasticId, err := commonids.ParseSqlElasticPoolID(elasticPoolId)
Expand Down Expand Up @@ -291,7 +304,7 @@ func resourceMsSqlDatabaseCreate(d *pluginsdk.ResourceData, meta interface{}) er
}

// NOTE: The 'PreferredEnclaveType' field cannot be passed to the APIs Create if the 'sku_name' is a DW or DC-series SKU...
if !strings.HasPrefix(strings.ToLower(skuName), "dw") && !strings.Contains(strings.ToLower(skuName), "_dc_") {
if !strings.HasPrefix(strings.ToLower(skuName), "dw") && !strings.Contains(strings.ToLower(skuName), "_dc_") && enclaveType != "" {
input.Properties.PreferredEnclaveType = pointer.To(enclaveType)
}

Expand Down Expand Up @@ -687,15 +700,17 @@ func resourceMsSqlDatabaseUpdate(d *pluginsdk.ResourceData, meta interface{}) er
}

if d.HasChange("enclave_type") {
enclaveType := databases.AlwaysEncryptedEnclaveTypeDefault
if v := d.Get("enclave_type").(string); v != "" {
enclaveType = databases.AlwaysEncryptedEnclaveTypeVBS
var enclaveType databases.AlwaysEncryptedEnclaveType
if v, ok := d.GetOk("enclave_type"); ok && v.(string) != "" {
enclaveType = databases.AlwaysEncryptedEnclaveType(v.(string))
}

// The 'PreferredEnclaveType' field cannot be passed to the APIs Update if the
// 'sku_name' is a DW or DC-series SKU...
if !strings.HasPrefix(strings.ToLower(skuName), "dw") && !strings.Contains(strings.ToLower(skuName), "_dc_") {
if !strings.HasPrefix(strings.ToLower(skuName), "dw") && !strings.Contains(strings.ToLower(skuName), "_dc_") && enclaveType != "" {
props.PreferredEnclaveType = pointer.To(enclaveType)
} else {
props.PreferredEnclaveType = nil
}

// If the database belongs to an elastic pool, we need to GET the elastic pool and check
Expand All @@ -712,12 +727,14 @@ func resourceMsSqlDatabaseUpdate(d *pluginsdk.ResourceData, meta interface{}) er
return fmt.Errorf("retrieving %s: %s", elasticId, err)
}

var elasticEnclaveType elasticpools.AlwaysEncryptedEnclaveType
if elasticPool.Model != nil && elasticPool.Model.Properties != nil && elasticPool.Model.Properties.PreferredEnclaveType != nil {
elasticEnclaveType := string(pointer.From(elasticPool.Model.Properties.PreferredEnclaveType))
databaseEnclaveType := string(enclaveType)
elasticEnclaveType = pointer.From(elasticPool.Model.Properties.PreferredEnclaveType)
}

if !strings.EqualFold(elasticEnclaveType, databaseEnclaveType) {
return fmt.Errorf("updating the %s with enclave type %q to the %s with enclave type %q is not supported. Before updating a database that belongs to an elastic pool please ensure that the 'enclave_type' is the same for both the database and the elastic pool", id, databaseEnclaveType, elasticId, elasticEnclaveType)
if elasticEnclaveType != "" || enclaveType != "" {
if !strings.EqualFold(string(elasticEnclaveType), string(enclaveType)) {
return fmt.Errorf("updating the %s with enclave type %q to the %s with enclave type %q is not supported. Before updating a database that belongs to an elastic pool please ensure that the 'enclave_type' is the same for both the database and the elastic pool", id, enclaveType, elasticId, elasticEnclaveType)
}
}
}
Expand Down Expand Up @@ -776,7 +793,7 @@ func resourceMsSqlDatabaseUpdate(d *pluginsdk.ResourceData, meta interface{}) er

// Place a lock for the current database so any partner resources can't bump its SKU out of band
if skuName != "" {
existingEnclaveType := databases.AlwaysEncryptedEnclaveTypeDefault
var existingEnclaveType databases.AlwaysEncryptedEnclaveType
if model := existing.Model; model != nil && model.Properties != nil && model.Properties.PreferredEnclaveType != nil {
existingEnclaveType = *model.Properties.PreferredEnclaveType
}
Expand Down Expand Up @@ -1128,8 +1145,9 @@ func resourceMsSqlDatabaseRead(d *pluginsdk.ResourceData, meta interface{}) erro
ledgerEnabled = *props.IsLedgerOn
}

// NOTE: Always set the PreferredEnclaveType to an empty string unless it isn't 'Default'...
if v := props.PreferredEnclaveType; v != nil && pointer.From(v) != databases.AlwaysEncryptedEnclaveTypeDefault {
// NOTE: Always set the PreferredEnclaveType to an empty string
// if not in the properties that were returned from Azure...
if v := props.PreferredEnclaveType; v != nil {
enclaveType = string(pointer.From(v))
}

Expand Down Expand Up @@ -1506,8 +1524,10 @@ func resourceMsSqlDatabaseSchema() map[string]*pluginsdk.Schema {
"enclave_type": {
Type: pluginsdk.TypeString,
Optional: true,
Computed: true, // TODO: Remove Computed in 4.0
ValidateFunc: validation.StringInSlice([]string{
string(databases.AlwaysEncryptedEnclaveTypeVBS),
string(databases.AlwaysEncryptedEnclaveTypeDefault),
}, false),
},

Expand Down
18 changes: 14 additions & 4 deletions internal/services/mssql/mssql_database_resource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ func TestAccMsSqlDatabase_complete(t *testing.T) {
check.That(data.ResourceName).ExistsInAzure(r),
check.That(data.ResourceName).Key("license_type").HasValue("LicenseIncluded"),
check.That(data.ResourceName).Key("max_size_gb").HasValue("2"),
check.That(data.ResourceName).Key("enclave_type").IsEmpty(),
check.That(data.ResourceName).Key("enclave_type").HasValue("Default"),
check.That(data.ResourceName).Key("tags.%").HasValue("1"),
check.That(data.ResourceName).Key("tags.ENV").HasValue("Staging"),
),
Expand Down Expand Up @@ -844,12 +844,13 @@ func TestAccMsSqlDatabase_enclaveType(t *testing.T) {
}

func TestAccMsSqlDatabase_enclaveTypeUpdate(t *testing.T) {
// NOTE: Once the enclave_type field has be set it cannot be changed...
data := acceptance.BuildTestData(t, "azurerm_mssql_database", "test")
r := MsSqlDatabaseResource{}

data.ResourceTest(t, r, []acceptance.TestStep{
{
Config: r.enclaveType(data, ""),
Config: r.basic(data),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
check.That(data.ResourceName).Key("enclave_type").IsEmpty(),
Expand All @@ -865,10 +866,18 @@ func TestAccMsSqlDatabase_enclaveTypeUpdate(t *testing.T) {
},
data.ImportStep(),
{
Config: r.enclaveType(data, ""),
Config: r.enclaveType(data, ` enclave_type = "Default"`),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
check.That(data.ResourceName).Key("enclave_type").IsEmpty(),
check.That(data.ResourceName).Key("enclave_type").HasValue("Default"),
),
},
data.ImportStep(),
{
Config: r.enclaveType(data, ` enclave_type = "VBS"`),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
check.That(data.ResourceName).Key("enclave_type").HasValue("VBS"),
),
},
data.ImportStep(),
Expand Down Expand Up @@ -1067,6 +1076,7 @@ resource "azurerm_mssql_database" "test" {
license_type = "LicenseIncluded"
max_size_gb = 2
sku_name = "GP_Gen5_2"
enclave_type = "Default"
storage_account_type = "Zone"
Expand Down
46 changes: 29 additions & 17 deletions internal/services/mssql/mssql_elasticpool_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,8 +182,10 @@ func resourceMsSqlElasticPool() *pluginsdk.Resource {
"enclave_type": {
Type: pluginsdk.TypeString,
Optional: true,
Computed: true, // TODO: Remove Computed in 4.0
ValidateFunc: validation.StringInSlice([]string{
string(databases.AlwaysEncryptedEnclaveTypeVBS),
string(databases.AlwaysEncryptedEnclaveTypeDefault),
}, false),
},

Expand All @@ -200,13 +202,27 @@ func resourceMsSqlElasticPool() *pluginsdk.Resource {
"tags": commonschema.Tags(),
},

CustomizeDiff: pluginsdk.CustomizeDiffShim(func(ctx context.Context, diff *pluginsdk.ResourceDiff, v interface{}) error {
if err := helper.MSSQLElasticPoolValidateSKU(diff); err != nil {
return err
}
CustomizeDiff: pluginsdk.CustomDiffWithAll(
func(ctx context.Context, diff *pluginsdk.ResourceDiff, v interface{}) error {
if err := helper.MSSQLElasticPoolValidateSKU(diff); err != nil {
return err
}

return nil
}),
return nil
},

pluginsdk.ForceNewIfChange("enclave_type", func(ctx context.Context, old, new, _ interface{}) bool {
// enclave_type cannot be removed once it has been set
// but can be changed between VBS and Default...
// this Diff will not work until 4.0 when we remove
// the computed property from the field scheam.
if old.(string) != "" && new.(string) == "" {
return true
}

return false
}),
),
}
}

Expand Down Expand Up @@ -236,13 +252,6 @@ func resourceMsSqlElasticPoolCreateUpdate(d *pluginsdk.ResourceData, meta interf
location := azure.NormalizeLocation(d.Get("location").(string))
sku := expandMsSqlElasticPoolSku(d)

// NOTE: Set the default value, if the field exists in the config the only value
// that it could be is 'VBS'...
enclaveType := elasticpools.AlwaysEncryptedEnclaveTypeDefault
if v, ok := d.GetOk("enclave_type"); ok {
enclaveType = elasticpools.AlwaysEncryptedEnclaveType(v.(string))
}

maintenanceConfigId := publicmaintenanceconfigurations.NewPublicMaintenanceConfigurationID(subscriptionId, d.Get("maintenance_configuration_name").(string))
elasticPool := elasticpools.ElasticPool{
Name: pointer.To(id.ElasticPoolName),
Expand All @@ -252,12 +261,17 @@ func resourceMsSqlElasticPoolCreateUpdate(d *pluginsdk.ResourceData, meta interf
Properties: &elasticpools.ElasticPoolProperties{
LicenseType: pointer.To(elasticpools.ElasticPoolLicenseType(d.Get("license_type").(string))),
PerDatabaseSettings: expandMsSqlElasticPoolPerDatabaseSettings(d),
PreferredEnclaveType: pointer.To(enclaveType),
ZoneRedundant: pointer.To(d.Get("zone_redundant").(bool)),
MaintenanceConfigurationId: pointer.To(maintenanceConfigId.ID()),
PreferredEnclaveType: nil,
},
}

// NOTE: The service default is actually nil/empty which indicates enclave is disabled. the value `Default` is NOT the default.
if v, ok := d.GetOk("enclave_type"); ok && v.(string) != "" {
elasticPool.Properties.PreferredEnclaveType = pointer.To(elasticpools.AlwaysEncryptedEnclaveType(v.(string)))
}

if d.HasChange("max_size_gb") {
if v, ok := d.GetOk("max_size_gb"); ok {
maxSizeBytes := v.(float64) * 1073741824
Expand Down Expand Up @@ -308,9 +322,7 @@ func resourceMsSqlElasticPoolRead(d *pluginsdk.ResourceData, meta interface{}) e

if props := model.Properties; props != nil {
enclaveType := ""

// NOTE: Always set the PreferredEnclaveType to an empty string unless it isn't 'Default'...
if v := props.PreferredEnclaveType; v != nil && pointer.From(v) != elasticpools.AlwaysEncryptedEnclaveTypeDefault {
if v := props.PreferredEnclaveType; v != nil {
enclaveType = string(pointer.From(v))
}
d.Set("enclave_type", enclaveType)
Expand Down
13 changes: 11 additions & 2 deletions internal/services/mssql/mssql_elasticpool_resource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,7 @@ func TestAccMsSqlElasticPool_vCoreToStandardDTU(t *testing.T) {
}

func TestAccMsSqlElasticPool_enclaveTypeUpdate(t *testing.T) {
// NOTE: Once the enclave_type has be set it cannot be removed...
data := acceptance.BuildTestData(t, "azurerm_mssql_elasticpool", "test")
r := MsSqlElasticPoolResource{}

Expand All @@ -390,10 +391,18 @@ func TestAccMsSqlElasticPool_enclaveTypeUpdate(t *testing.T) {
},
data.ImportStep("max_size_gb"),
{
Config: r.basicDTU(data, ""),
Config: r.basicDTU(data, `enclave_type = "Default"`),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
check.That(data.ResourceName).Key("enclave_type").IsEmpty(),
check.That(data.ResourceName).Key("enclave_type").HasValue("Default"),
),
},
data.ImportStep("max_size_gb"),
{
Config: r.basicDTU(data, `enclave_type = "VBS"`),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
check.That(data.ResourceName).Key("enclave_type").HasValue("VBS"),
),
},
data.ImportStep("max_size_gb"),
Expand Down
10 changes: 6 additions & 4 deletions website/docs/r/mssql_database.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -190,11 +190,13 @@ The following arguments are supported:

* `elastic_pool_id` - (Optional) Specifies the ID of the elastic pool containing this database.

* `enclave_type` - (Optional) Specifies the type of enclave to be used by the database. Possible value `VBS`.
* `enclave_type` - (Optional) Specifies the type of enclave to be used by the elastic pool. When `enclave_type` is not specified (e.g., the default) enclaves are not enabled on the database. <!-- TODO: Uncomment in 4.0: Once enabled (e.g., by specifying `Default` or `VBS`) removing the `enclave_type` field from the configuration file will force the creation of a new resource.--> Possible values are `Default` or `VBS`.

~> **NOTE:** `enclave_type` is currently not supported for DW (e.g, DataWarehouse) and DC-series SKUs.
-> **NOTE:** `enclave_type` is currently not supported for DW (e.g, DataWarehouse) and DC-series SKUs.

~> **NOTE:** Geo Replicated and Failover databases must have the same `enclave_type`.
-> **NOTE:** Geo Replicated and Failover databases must have the same `enclave_type`.

~> **NOTE:** The default value for the `enclave_type` field is unset not `Default`.

* `geo_backup_enabled` - (Optional) A boolean that specifies if the Geo Backup Policy is enabled. Defaults to `true`.

Expand Down Expand Up @@ -236,7 +238,7 @@ The following arguments are supported:

* `sku_name` - (Optional) Specifies the name of the SKU used by the database. For example, `GP_S_Gen5_2`,`HS_Gen4_1`,`BC_Gen5_2`, `ElasticPool`, `Basic`,`S0`, `P2` ,`DW100c`, `DS100`. Changing this from the HyperScale service tier to another service tier will create a new resource.

~> **NOTE:** The default `sku_name` value may differ between Azure locations depending on local availability of Gen4/Gen5 capacity. When databases are replicated using the `creation_source_database_id` property, the source (primary) database cannot have a higher SKU service tier than any secondary databases. When changing the `sku_name` of a database having one or more secondary databases, this resource will first update any secondary databases as necessary. In such cases it's recommended to use the same `sku_name` in your configuration for all related databases, as not doing so may cause an unresolvable diff during subsequent plans.
-> **NOTE:** The default `sku_name` value may differ between Azure locations depending on local availability of Gen4/Gen5 capacity. When databases are replicated using the `creation_source_database_id` property, the source (primary) database cannot have a higher SKU service tier than any secondary databases. When changing the `sku_name` of a database having one or more secondary databases, this resource will first update any secondary databases as necessary. In such cases it's recommended to use the same `sku_name` in your configuration for all related databases, as not doing so may cause an unresolvable diff during subsequent plans.

* `storage_account_type` - (Optional) Specifies the storage account type used to store backups for this database. Possible values are `Geo`, `GeoZone`, `Local` and `Zone`. Defaults to `Geo`.

Expand Down
Loading

0 comments on commit 8b9f54d

Please sign in to comment.