Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

azurerm_recovery_services_vault - support direct creation with immutability locked #23806

Merged
merged 3 commits into from
Jan 4, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,12 @@ 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 {
if old.(string) != new.(string) && old.(string) == string(vaults.ImmutabilityStateLocked) {
magodo marked this conversation as resolved.
Show resolved Hide resolved
return true
}
return false
}),
),
}
}
Expand Down Expand Up @@ -249,7 +255,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 ceartion.
magodo marked this conversation as resolved.
Show resolved Hide resolved
// 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 +280,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 +545,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 +574,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.
magodo marked this conversation as resolved.
Show resolved Hide resolved
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
Loading