Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
3 changes: 3 additions & 0 deletions internal/clients/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ func CombineRetryOptions(opts ...*policy.RetryOptions) *policy.RetryOptions {

statusCodeSet := make(map[int]bool)
for _, opt := range opts {
if opt == nil {
continue
}
for _, code := range opt.StatusCodes {
statusCodeSet[code] = true
}
Expand Down
15 changes: 12 additions & 3 deletions internal/services/azapi_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"github.com/Azure/terraform-provider-azapi/internal/locks"
"github.com/Azure/terraform-provider-azapi/internal/retry"
"github.com/Azure/terraform-provider-azapi/internal/services/common"
"github.com/Azure/terraform-provider-azapi/internal/services/customization"
"github.com/Azure/terraform-provider-azapi/internal/services/defaults"
"github.com/Azure/terraform-provider-azapi/internal/services/dynamic"
"github.com/Azure/terraform-provider-azapi/internal/services/migration"
Expand Down Expand Up @@ -1072,9 +1073,17 @@ func (r *AzapiResource) Delete(ctx context.Context, request resource.DeleteReque
QueryParameters: clients.NewQueryParameters(common.AsMapOfLists(model.DeleteQueryParameters)),
RetryOptions: clients.NewRetryOptions(model.Retry),
}
_, err = client.Delete(ctx, id.AzureResourceId, id.ApiVersion, requestOptions)
if err != nil && !utils.ResponseErrorWasNotFound(err) {
response.Diagnostics.AddError("Failed to delete resource", fmt.Errorf("deleting %s: %+v", id, err).Error())

if customizedResource := customization.GetCustomization(id.AzureResourceType); customizedResource != nil && (*customizedResource).DeleteFunc() != nil {
err = (*customizedResource).DeleteFunc()(ctx, *r.ProviderData, id, requestOptions)
if err != nil {
response.Diagnostics.AddError("Failed to delete resource", fmt.Errorf("deleting %s: %+v", id, err).Error())
}
} else {
_, err = client.Delete(ctx, id.AzureResourceId, id.ApiVersion, requestOptions)
if err != nil && !utils.ResponseErrorWasNotFound(err) {
response.Diagnostics.AddError("Failed to delete resource", fmt.Errorf("deleting %s: %+v", id, err).Error())
}
}
}

Expand Down
79 changes: 79 additions & 0 deletions internal/services/azapi_resource_customization_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package services_test

import (
"fmt"
"testing"

"github.com/Azure/terraform-provider-azapi/internal/acceptance"
"github.com/Azure/terraform-provider-azapi/internal/acceptance/check"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
)

func TestAccGenericResource_customizedKeyVaultKey(t *testing.T) {
data := acceptance.BuildTestData(t, "azapi_resource", "test")
r := GenericResource{}

data.ResourceTest(t, r, []resource.TestStep{
{
Config: r.customizedKeyVaultKey(data),
Check: resource.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
),
},
})
}

func (r GenericResource) customizedKeyVaultKey(data acceptance.TestData) string {
return fmt.Sprintf(`
%s


data "azapi_client_config" "current" {
}

resource "azapi_resource" "vault" {
type = "Microsoft.KeyVault/vaults@2023-02-01"
parent_id = azapi_resource.resourceGroup.id
name = "acctest%[2]s"
location = azapi_resource.resourceGroup.location
body = {
properties = {
sku = {
family = "A"
name = "standard"
}
accessPolicies = [
{
objectId = data.azapi_client_config.current.object_id
permissions = {
keys = [
"Get", "Create", "Delete", "List", "Restore", "Recover", "UnwrapKey", "WrapKey", "Purge", "Encrypt", "Decrypt", "Sign", "Verify"
]
}
tenantId = data.azapi_client_config.current.tenant_id
}
]
enableSoftDelete = true
enablePurgeProtection = true
tenantId = data.azapi_client_config.current.tenant_id
}
}
schema_validation_enabled = false
response_export_values = ["*"]
}


resource "azapi_resource" "test" {
type = "Microsoft.KeyVault/vaults/keys@2023-02-01"
parent_id = azapi_resource.vault.id
name = "acctest%[2]s"
body = {
properties = {
keySize = 2048
kty = "RSA"
keyOps = ["encrypt", "decrypt", "sign", "verify", "wrapKey", "unwrapKey"]
}
}
}
`, r.template(data), data.RandomString)
}
36 changes: 36 additions & 0 deletions internal/services/customization/interface.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package customization

import (
"context"

"github.com/Azure/terraform-provider-azapi/internal/clients"
"github.com/Azure/terraform-provider-azapi/internal/services/parse"
)

type Resource interface {
GetResourceType() string
CreateFunc() Func
UpdateFunc() Func
DeleteFunc() DeleteFunc
ReadFunc() Func
}

type Func func() error

type DeleteFunc func(ctx context.Context, clients clients.Client, id parse.ResourceId, options clients.RequestOptions) error

var customizations = make(map[string]Resource)

func init() {
var keyVaultKeyCustomization Resource = KeyVaultKeyCustomization{}
customizations[keyVaultKeyCustomization.GetResourceType()] = keyVaultKeyCustomization

}

func GetCustomization(resourceType string) *Resource {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that the interface will store a pointer to the concrete type, consider making this return a Resource rather than a *Resource.

Also consider returning a bool value, making it similar to the idiomatic go: if val, ok := GetCustomization(s); ok {}

customization, exists := customizations[resourceType]
if !exists {
return nil
}
return &customization
}
61 changes: 61 additions & 0 deletions internal/services/customization/key_vault_key_customization.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package customization

import (
"context"
"fmt"
"net/http"
"strings"

"github.com/Azure/terraform-provider-azapi/internal/clients"
"github.com/Azure/terraform-provider-azapi/internal/services/parse"
"github.com/Azure/terraform-provider-azapi/utils"
)

type KeyVaultKeyCustomization struct {
}

func (k KeyVaultKeyCustomization) GetResourceType() string {
return "Microsoft.KeyVault/vaults/keys"
}

func (k KeyVaultKeyCustomization) CreateFunc() Func {
return nil
}

func (k KeyVaultKeyCustomization) UpdateFunc() Func {
return nil
}

func (k KeyVaultKeyCustomization) ReadFunc() Func {
return nil
}

func (k KeyVaultKeyCustomization) DeleteFunc() DeleteFunc {
return func(ctx context.Context, clients clients.Client, id parse.ResourceId, options clients.RequestOptions) error {

dataPlaneClient := clients.DataPlaneClient

path := id.AzureResourceId
path = strings.TrimPrefix(path, "/")
path = strings.TrimSuffix(path, "/")
components := strings.Split(path, "/")
parts := make(map[string]string)
for i := 0; i < len(components)-1; i += 2 {
parts[components[i]] = components[i+1]
}
Comment on lines +38 to +45
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps this code should be part of the parse package, it seems pretty generic?


if parts["vaults"] == "" {
return fmt.Errorf("key vault name is missing in the resource ID: %s", id.AzureResourceId)
}

resourceID := fmt.Sprintf("%s.vault.azure.net/keys/%s", parts["vaults"], id.Name)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think hard coding this will be problematic for sovereign cloud:

For China cloud: https://vault.azure.cn

For US Gov cloud: https://vault.usgovcloudapi.net

For Germany: https://vault.microsoftazure.de


_, err := dataPlaneClient.Action(ctx, resourceID, "", "7.4", http.MethodDelete, nil, options)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two comments here:

  1. I think this will be problematic for callers using private endpoint, and will be confusing as they will assume they are talking to the management plane.
  2. Hard coding the Api-Version could make this harder to maintain over time.

if err != nil && !utils.ResponseErrorWasNotFound(err) {
return err
}
return nil
}
}

var _ Resource = &KeyVaultKeyCustomization{}
Loading