diff --git a/internal/services/keyvault/key_vault_certificate_resource.go b/internal/services/keyvault/key_vault_certificate_resource.go index 7bb4ed0809c9..90e02d80803f 100644 --- a/internal/services/keyvault/key_vault_certificate_resource.go +++ b/internal/services/keyvault/key_vault_certificate_resource.go @@ -15,6 +15,7 @@ import ( "time" "github.com/Azure/go-autorest/autorest" + "github.com/google/go-cmp/cmp" "github.com/hashicorp/go-azure-helpers/lang/pointer" "github.com/hashicorp/go-azure-helpers/lang/response" "github.com/hashicorp/go-azure-helpers/resourcemanager/commonids" @@ -620,7 +621,28 @@ func resourceKeyVaultCertificateUpdate(d *schema.ResourceData, meta interface{}) d.SetId(certificateId.ID()) } } - if d.HasChange("certificate_policy") { + + // update lifetime_action only should not recreate a certificate + var lifeTimeOld, lifeTimeNew interface{} + var policyOld, policyNew map[string]interface{} + + policyOldRaw, policyNewRaw := d.GetChange("certificate_policy") + policyOldList, policyNewList := policyOldRaw.([]interface{}), policyNewRaw.([]interface{}) + + if len(policyOldList) > 0 { + policyOld = policyOldList[0].(map[string]interface{}) + lifeTimeOld = policyOld["lifetime_action"] + delete(policyOld, "lifetime_action") + } + if len(policyNewList) > 0 { + policyNew = policyNewList[0].(map[string]interface{}) + lifeTimeNew = policyNew["lifetime_action"] + delete(policyNew, "lifetime_action") + } + + // do not recreate cerfiticate when only lifetime_action changes + if !cmp.Equal(policyNewList, policyOldList) { + policyNew["lifetime_action"] = lifeTimeNew newCert, err := createCertificate(d, meta) if err != nil { return err @@ -632,10 +654,18 @@ func resourceKeyVaultCertificateUpdate(d *schema.ResourceData, meta interface{}) d.SetId(certificateId.ID()) } - if d.HasChange("tags") { + if updateLifetime := !cmp.Equal(lifeTimeOld, lifeTimeNew); d.HasChange("tags") || updateLifetime { patch := keyvault.CertificateUpdateParameters{} - if t, ok := d.GetOk("tags"); ok { - patch.Tags = tags.Expand(t.(map[string]interface{})) + if d.HasChange("tags") { + if t, ok := d.GetOk("tags"); ok { + patch.Tags = tags.Expand(t.(map[string]interface{})) + } + } + + if updateLifetime { + patch.CertificatePolicy = &keyvault.CertificatePolicy{ + LifetimeActions: expandKeyVaultCertificatePolicyLifetimeAction(lifeTimeNew), + } } if _, err = client.UpdateCertificate(ctx, id.KeyVaultBaseUrl, id.Name, "", patch); err != nil { @@ -908,41 +938,7 @@ func expandKeyVaultCertificatePolicy(d *pluginsdk.ResourceData) (*keyvault.Certi ReuseKey: utils.Bool(props["reuse_key"].(bool)), } - lifetimeActions := make([]keyvault.LifetimeAction, 0) - actions := policyRaw["lifetime_action"].([]interface{}) - for _, v := range actions { - action := v.(map[string]interface{}) - lifetimeAction := keyvault.LifetimeAction{} - - if v, ok := action["action"]; ok { - as := v.([]interface{}) - a := as[0].(map[string]interface{}) - lifetimeAction.Action = &keyvault.Action{ - ActionType: keyvault.CertificatePolicyAction(a["action_type"].(string)), - } - } - - if v, ok := action["trigger"]; ok { - triggers := v.([]interface{}) - if triggers[0] != nil { - trigger := triggers[0].(map[string]interface{}) - lifetimeAction.Trigger = &keyvault.Trigger{} - - d := trigger["days_before_expiry"].(int) - if d > 0 { - lifetimeAction.Trigger.DaysBeforeExpiry = utils.Int32(int32(d)) - } - - p := trigger["lifetime_percentage"].(int) - if p > 0 { - lifetimeAction.Trigger.LifetimePercentage = utils.Int32(int32(p)) - } - } - } - - lifetimeActions = append(lifetimeActions, lifetimeAction) - } - policy.LifetimeActions = &lifetimeActions + policy.LifetimeActions = expandKeyVaultCertificatePolicyLifetimeAction(policyRaw["lifetime_action"]) secrets := policyRaw["secret_properties"].([]interface{}) secret := secrets[0].(map[string]interface{}) @@ -999,6 +995,47 @@ func expandKeyVaultCertificatePolicy(d *pluginsdk.ResourceData) (*keyvault.Certi return &policy, nil } +func expandKeyVaultCertificatePolicyLifetimeAction(actions interface{}) *[]keyvault.LifetimeAction { + lifetimeActions := make([]keyvault.LifetimeAction, 0) + if actions == nil { + return &lifetimeActions + } + + for _, v := range actions.([]interface{}) { + action := v.(map[string]interface{}) + lifetimeAction := keyvault.LifetimeAction{} + + if v, ok := action["action"]; ok { + as := v.([]interface{}) + a := as[0].(map[string]interface{}) + lifetimeAction.Action = &keyvault.Action{ + ActionType: keyvault.CertificatePolicyAction(a["action_type"].(string)), + } + } + + if v, ok := action["trigger"]; ok { + triggers := v.([]interface{}) + if triggers[0] != nil { + trigger := triggers[0].(map[string]interface{}) + lifetimeAction.Trigger = &keyvault.Trigger{} + + d := trigger["days_before_expiry"].(int) + if d > 0 { + lifetimeAction.Trigger.DaysBeforeExpiry = utils.Int32(int32(d)) + } + + p := trigger["lifetime_percentage"].(int) + if p > 0 { + lifetimeAction.Trigger.LifetimePercentage = utils.Int32(int32(p)) + } + } + } + + lifetimeActions = append(lifetimeActions, lifetimeAction) + } + return &lifetimeActions +} + func flattenKeyVaultCertificatePolicy(input *keyvault.CertificatePolicy, certData *[]byte) []interface{} { if input == nil { return []interface{}{} diff --git a/internal/services/keyvault/key_vault_certificate_resource_test.go b/internal/services/keyvault/key_vault_certificate_resource_test.go index dc0d9f72c358..f972b8f8839a 100644 --- a/internal/services/keyvault/key_vault_certificate_resource_test.go +++ b/internal/services/keyvault/key_vault_certificate_resource_test.go @@ -108,6 +108,28 @@ func TestAccKeyVaultCertificate_basicGenerate(t *testing.T) { }) } +func TestAccKeyVaultCertificate_updateLifeTime(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_key_vault_certificate", "test") + r := KeyVaultCertificateResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basicGenerate(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.basicGenerateUpdateLifetimeAction(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + func TestAccKeyVaultCertificate_basicGenerateUnknownIssuer(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_key_vault_certificate", "test") r := KeyVaultCertificateResource{} @@ -713,6 +735,62 @@ resource "azurerm_key_vault_certificate" "test" { `, r.template(data), data.RandomString) } +func (r KeyVaultCertificateResource) basicGenerateUpdateLifetimeAction(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +%s + +resource "azurerm_key_vault_certificate" "test" { + name = "acctestcert%s" + key_vault_id = azurerm_key_vault.test.id + + certificate_policy { + issuer_parameters { + name = "Self" + } + + key_properties { + exportable = true + key_size = 2048 + key_type = "RSA" + reuse_key = true + } + + lifetime_action { + action { + action_type = "EmailContacts" + } + + trigger { + days_before_expiry = 30 + } + } + + secret_properties { + content_type = "application/x-pkcs12" + } + + x509_certificate_properties { + key_usage = [ + "cRLSign", + "dataEncipherment", + "digitalSignature", + "keyAgreement", + "keyEncipherment", + "keyCertSign", + ] + + subject = "CN=hello-world" + validity_in_months = 12 + } + } +} +`, r.template(data), data.RandomString) +} + func (r KeyVaultCertificateResource) updateCertificate(data acceptance.TestData) string { return fmt.Sprintf(` provider "azurerm" { diff --git a/website/docs/r/key_vault_certificate.html.markdown b/website/docs/r/key_vault_certificate.html.markdown index 7763cf626a33..89198d584b5c 100644 --- a/website/docs/r/key_vault_certificate.html.markdown +++ b/website/docs/r/key_vault_certificate.html.markdown @@ -238,7 +238,7 @@ The following arguments are supported: * `certificate` - (Optional) A `certificate` block as defined below, used to Import an existing certificate. Changing this will create a new version of the Key Vault Certificate. -* `certificate_policy` - (Optional) A `certificate_policy` block as defined below. Changing this will create a new version of the Key Vault Certificate. +* `certificate_policy` - (Optional) A `certificate_policy` block as defined below. Changing this (except the `lifetime_action` field) will create a new version of the Key Vault Certificate. ~> **NOTE:** When creating a Key Vault Certificate, at least one of `certificate` or `certificate_policy` is required. Provide `certificate` to import an existing certificate, `certificate_policy` to generate a new certificate.