From 792e99892c34763d176588b6a6a97bbc9a85ecf3 Mon Sep 17 00:00:00 2001 From: Xu Wu Date: Tue, 13 Jun 2023 07:46:25 +0800 Subject: [PATCH] `azurerm_nginx_certificate`: support update `key_virtual_path`, `certificate_virtual_path` and `key_vault_secret_id` fields (#22100) --- .../nginx/nginx_certificate_resource.go | 53 +++++++++++- .../nginx/nginx_certificate_resource_test.go | 83 ++++++++++++++++++- .../docs/r/nginx_certificate.html.markdown | 7 +- 3 files changed, 135 insertions(+), 8 deletions(-) diff --git a/internal/services/nginx/nginx_certificate_resource.go b/internal/services/nginx/nginx_certificate_resource.go index 9832df1b9cb3..93c8480658d8 100644 --- a/internal/services/nginx/nginx_certificate_resource.go +++ b/internal/services/nginx/nginx_certificate_resource.go @@ -25,7 +25,7 @@ type CertificateModel struct { type CertificateResource struct{} -var _ sdk.Resource = (*CertificateResource)(nil) +var _ sdk.ResourceWithUpdate = (*CertificateResource)(nil) func (m CertificateResource) Arguments() map[string]*pluginsdk.Schema { return map[string]*pluginsdk.Schema{ @@ -46,21 +46,18 @@ func (m CertificateResource) Arguments() map[string]*pluginsdk.Schema { "key_virtual_path": { Type: pluginsdk.TypeString, Required: true, - ForceNew: true, ValidateFunc: validation.StringIsNotEmpty, }, "certificate_virtual_path": { Type: pluginsdk.TypeString, Required: true, - ForceNew: true, ValidateFunc: validation.StringIsNotEmpty, }, "key_vault_secret_id": { Type: pluginsdk.TypeString, Required: true, - ForceNew: true, ValidateFunc: keyvaultValidate.NestedItemIdWithOptionalVersion, }, } @@ -120,6 +117,54 @@ func (m CertificateResource) Create() sdk.ResourceFunc { } } +func (m CertificateResource) Update() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 30 * time.Minute, + Func: func(ctx context.Context, meta sdk.ResourceMetaData) error { + + client := meta.Client.Nginx.NginxCertificate + id, err := nginxcertificate.ParseCertificateID(meta.ResourceData.Id()) + if err != nil { + return err + } + + var model CertificateModel + if err = meta.Decode(&model); err != nil { + return fmt.Errorf("decoding err: %+v", err) + } + + // retrieve from GET + existing, err := client.CertificatesGet(ctx, *id) + if err != nil { + return fmt.Errorf("retrieving exists when updating: +%v", *id) + } + if existing.Model == nil && existing.Model.Properties == nil { + return fmt.Errorf("retrieving as nil when updating for %v", *id) + } + + // have to pass all existing properties to update + upd := existing.Model + if meta.ResourceData.HasChange("key_virtual_path") { + upd.Properties.KeyVirtualPath = pointer.FromString(model.KeyVirtualPath) + } + + if meta.ResourceData.HasChange("certificate_virtual_path") { + upd.Properties.CertificateVirtualPath = pointer.To(model.CertificateVirtualPath) + } + + if meta.ResourceData.HasChange("key_vault_secret_id") { + upd.Properties.KeyVaultSecretId = pointer.To(model.KeyVaultSecretId) + } + + err = client.CertificatesCreateOrUpdateThenPoll(ctx, *id, *upd) + if err != nil { + return fmt.Errorf("updating %s: %v", id, err) + } + return nil + }, + } +} + func (m CertificateResource) Read() sdk.ResourceFunc { return sdk.ResourceFunc{ Timeout: 5 * time.Minute, diff --git a/internal/services/nginx/nginx_certificate_resource_test.go b/internal/services/nginx/nginx_certificate_resource_test.go index 3b472c301a99..3785a4fff4c8 100644 --- a/internal/services/nginx/nginx_certificate_resource_test.go +++ b/internal/services/nginx/nginx_certificate_resource_test.go @@ -42,6 +42,27 @@ func TestAccCertificate_basic(t *testing.T) { }) } +func TestAccCertificate_update(t *testing.T) { + data := acceptance.BuildTestData(t, nginx.CertificateResource{}.ResourceType(), "test") + r := CertificateResource{} + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.update(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + func TestAccCertificate_requiresImport(t *testing.T) { data := acceptance.BuildTestData(t, nginx.CertificateResource{}.ResourceType(), "test") r := CertificateResource{} @@ -59,7 +80,6 @@ func TestAccCertificate_requiresImport(t *testing.T) { func (a CertificateResource) basic(data acceptance.TestData) string { return fmt.Sprintf(` - %s resource "azurerm_nginx_certificate" "test" { @@ -72,6 +92,67 @@ resource "azurerm_nginx_certificate" "test" { `, a.template(data), data.RandomInteger, data.Locations.Primary) } +func (a CertificateResource) update(data acceptance.TestData) string { + return fmt.Sprintf(` + +%s + +resource "azurerm_key_vault_certificate" "test2" { + name = "acctestcert2%[2]d" + 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 = "AutoRenew" + } + + trigger { + days_before_expiry = 30 + } + } + + secret_properties { + content_type = "application/x-pem-file" + } + + x509_certificate_properties { + key_usage = [ + "cRLSign", + "dataEncipherment", + "digitalSignature", + "keyAgreement", + "keyEncipherment", + "keyCertSign", + ] + + subject = "CN=hello-world" + validity_in_months = 12 + } + } +} + +resource "azurerm_nginx_certificate" "test" { + name = "acctest%[2]d" + nginx_deployment_id = azurerm_nginx_deployment.test.id + key_virtual_path = "/src/cert/soservermekey2.key" + certificate_virtual_path = "/src/cert/server2.cert" + key_vault_secret_id = azurerm_key_vault_certificate.test2.secret_id +} +`, a.template(data), data.RandomInteger) +} + func (a CertificateResource) requiresImport(data acceptance.TestData) string { return fmt.Sprintf(` %s diff --git a/website/docs/r/nginx_certificate.html.markdown b/website/docs/r/nginx_certificate.html.markdown index 736a60d66edd..fb33774a0c3f 100644 --- a/website/docs/r/nginx_certificate.html.markdown +++ b/website/docs/r/nginx_certificate.html.markdown @@ -127,11 +127,11 @@ The following arguments are supported: * `nginx_deployment_id` - (Required) The ID of the Nginx Deployment that this Certificate should be associated with. Changing this forces a new Nginx Certificate to be created. -* `certificate_virtual_path` - (Required) Specify the path to the cert file of this certificate. Changing this forces a new Nginx Certificate to be created. +* `certificate_virtual_path` - (Required) Specify the path to the cert file of this certificate. -* `key_virtual_path` - (Required) Specify the path to the key file of this certificate. Changing this forces a new Nginx Certificate to be created. +* `key_virtual_path` - (Required) Specify the path to the key file of this certificate. -* `key_vault_secret_id` - (Required) Specify the ID of the Key Vault Secret for this certificate. Changing this forces a new Nginx Certificate to be created. +* `key_vault_secret_id` - (Required) Specify the ID of the Key Vault Secret for this certificate. ## Attributes Reference @@ -144,6 +144,7 @@ In addition to the Arguments listed above - the following Attributes are exporte The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/language/resources/syntax#operation-timeouts) for certain actions: * `create` - (Defaults to 30 minutes) Used when creating the Nginx Certificate. +* `update` - (Defaults to 30 minutes) Used when updating the Nginx Certificate. * `read` - (Defaults to 5 minutes) Used when retrieving the Nginx Certificate. * `delete` - (Defaults to 10 minutes) Used when deleting the Nginx Certificate.