Skip to content

Commit

Permalink
azurerm_recovery_services_vault - support direct creation with immu…
Browse files Browse the repository at this point in the history
…tability `locked` (#23806)

* `azurerm_recovery_services_vault`: support create with immutability `locked`

* update per comments

* update per comment
  • Loading branch information
ziyeqf committed Jan 4, 2024
1 parent dca1108 commit a5af833
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,9 @@ func resourceRecoveryServicesVault() *pluginsdk.Resource {
pluginsdk.ForceNewIfChange("cross_region_restore_enabled", func(ctx context.Context, old, new, meta interface{}) bool {
return old.(bool) && !new.(bool)
}),
pluginsdk.ForceNewIfChange("immutability", func(ctx context.Context, old, new, meta interface{}) bool {
return old.(string) == string(vaults.ImmutabilityStateLocked)
}),
),
}
}
Expand Down Expand Up @@ -249,7 +252,20 @@ func resourceRecoveryServicesVaultCreate(d *pluginsdk.ResourceData, meta interfa
vault.Sku.Tier = utils.String("Standard")
}

requireAddtionalUpdate := false
updatePatch := vaults.PatchVault{
Properties: &vaults.VaultProperties{},
}
if immutability, ok := d.GetOk("immutability"); ok {
// The API doesn't allow to set the immutability to "Locked" on creation.
// Here we firstly make it "Unlocked", and once created, we will update it to "Locked".
// Note: The `immutability` could be transitioned only in the limited directions.
// Locked <- Unlocked <-> Disabled
if immutability == string(vaults.ImmutabilityStateLocked) {
updatePatch.Properties.SecuritySettings = expandRecoveryServicesVaultSecuritySettings(immutability)
requireAddtionalUpdate = true
immutability = string(vaults.ImmutabilityStateUnlocked)
}
vault.Properties.SecuritySettings = expandRecoveryServicesVaultSecuritySettings(immutability)
}

Expand All @@ -261,14 +277,16 @@ func resourceRecoveryServicesVaultCreate(d *pluginsdk.ResourceData, meta interfa
// `encryption` needs to be set before `cross_region_restore_enabled` is set. Or the service will return an error. "If CRR is enabled for the Vault, the storage state will be locked and it will interfere with further operations"
// recovery vault's encryption config cannot be set while creation, so a standalone update is required.
if _, ok := d.GetOk("encryption"); ok {
err = client.UpdateThenPoll(ctx, id, vaults.PatchVault{
Properties: &vaults.VaultProperties{
Encryption: expandEncryption(d),
},
})
requireAddtionalUpdate = true
updatePatch.Properties.Encryption = expandEncryption(d)
}

if requireAddtionalUpdate {
err := client.UpdateThenPoll(ctx, id, updatePatch)
if err != nil {
return fmt.Errorf("updating Recovery Service Encryption %s: %+v, but recovery vault was created, a manually import might be required", id.String(), err)
return fmt.Errorf("updating Recovery Service %s: %+v, but recovery vault was created, a manually import might be required", id.String(), err)
}

}

storageType := backupresourcestorageconfigsnoncrr.StorageType(d.Get("storage_mode_type").(string))
Expand Down Expand Up @@ -524,6 +542,10 @@ func resourceRecoveryServicesVaultUpdate(d *pluginsdk.ResourceData, meta interfa
}
}

requireAddtionalUpdate := false
additionalUpdatePatch := vaults.PatchVault{
Properties: &vaults.VaultProperties{},
}
vault := vaults.PatchVault{
Properties: &vaults.VaultProperties{},
}
Expand All @@ -549,14 +571,32 @@ func resourceRecoveryServicesVaultUpdate(d *pluginsdk.ResourceData, meta interfa
}

if d.HasChange("immutability") {
vault.Properties.SecuritySettings = expandRecoveryServicesVaultSecuritySettings(d.Get("immutability"))
// The API does not allow to set the immutability from `Disabled` to `Locked` directly,
// Hence we firstly make it `Unlocked`, and once created, we will update it to `Locked`.
// Note: The `immutability` could be transitioned only in the limited directions.
// Locked <- Unlocked <-> Disabled
currentImmutability := model.Properties.SecuritySettings.ImmutabilitySettings.State
immutability := d.Get("immutability")
if currentImmutability != nil && string(*currentImmutability) == string(vaults.ImmutabilityStateDisabled) && immutability == string(vaults.ImmutabilityStateLocked) {
additionalUpdatePatch.Properties.SecuritySettings = expandRecoveryServicesVaultSecuritySettings(immutability)
requireAddtionalUpdate = true
immutability = string(vaults.ImmutabilityStateUnlocked)
}
vault.Properties.SecuritySettings = expandRecoveryServicesVaultSecuritySettings(immutability)
}

err = client.UpdateThenPoll(ctx, id, vault)
if err != nil {
return fmt.Errorf("updating %s: %+v", id, err)
}

if requireAddtionalUpdate {
err := client.UpdateThenPoll(ctx, id, additionalUpdatePatch)
if err != nil {
return fmt.Errorf("updating Recovery Service %s: %+v, but recovery vault was created, a manually import might be required", id.String(), err)
}
}

// an update on vault will cause the vault config reset to default, so whether the config has change or not, it needs to be updated.
var StateRefreshPendingStrings []string
var StateRefreshTargetStrings []string
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,13 +188,43 @@ func TestAccRecoveryServicesVault_UserAssignedIdentity(t *testing.T) {
})
}

func TestAccRecoveryServicesVault_immutabilityLocked(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_recovery_services_vault", "test")
r := RecoveryServicesVaultResource{}

data.ResourceTest(t, r, []acceptance.TestStep{
{
// To test creation with `Locked`, it is irreversible.
Config: r.basicWithImmutability(data, "Locked"),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
),
},
data.ImportStep(),
{
Config: r.basicWithImmutability(data, "Disabled"),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
),
},
data.ImportStep(),
{
Config: r.basicWithImmutability(data, "Locked"),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
),
},
data.ImportStep(),
})
}

func TestAccRecoveryServicesVault_immutability(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_recovery_services_vault", "test")
r := RecoveryServicesVaultResource{}

data.ResourceTest(t, r, []acceptance.TestStep{
{
Config: r.basicWithImmutability(data, true),
Config: r.basicWithImmutability(data, "Unlocked"),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
),
Expand All @@ -208,7 +238,21 @@ func TestAccRecoveryServicesVault_immutability(t *testing.T) {
},
data.ImportStep(),
{
Config: r.basicWithImmutability(data, false),
Config: r.basicWithImmutability(data, "Disabled"),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
),
},
data.ImportStep(),
{
Config: r.basicWithImmutability(data, "Unlocked"),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
),
},
data.ImportStep(),
{
Config: r.basicWithImmutability(data, "Locked"),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
),
Expand Down Expand Up @@ -694,12 +738,7 @@ resource "azurerm_recovery_services_vault" "test" {
`, data.RandomInteger, data.Locations.Primary)
}

func (RecoveryServicesVaultResource) basicWithImmutability(data acceptance.TestData, enabled bool) string {
immutability := `Disabled`
if enabled {
immutability = `Unlocked`
}

func (RecoveryServicesVaultResource) basicWithImmutability(data acceptance.TestData, immutability string) string {
return fmt.Sprintf(`
provider "azurerm" {
features {}
Expand Down Expand Up @@ -1185,7 +1224,6 @@ resource "azurerm_recovery_services_vault" "test" {
}

func (RecoveryServicesVaultResource) crossRegionRestoreEnabledWithEncryption(data acceptance.TestData) string {

return fmt.Sprintf(`
provider "azurerm" {
features {
Expand Down
2 changes: 2 additions & 0 deletions website/docs/r/recovery_services_vault.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ The following arguments are supported:

* `immutability` - (Optional) Immutability Settings of vault, possible values include: `Locked`, `Unlocked` and `Disabled`.

-> **Note:** Once `immutability` is set to `Locked`, changing it to other values forces a new Recovery Services Vault to be created.

* `storage_mode_type` - (Optional) The storage type of the Recovery Services Vault. Possible values are `GeoRedundant`, `LocallyRedundant` and `ZoneRedundant`. Defaults to `GeoRedundant`.

* `cross_region_restore_enabled` - (Optional) Is cross region restore enabled for this Vault? Only can be `true`, when `storage_mode_type` is `GeoRedundant`. Defaults to `false`.
Expand Down

0 comments on commit a5af833

Please sign in to comment.