Skip to content

Commit 1bf9208

Browse files
committed
feat: new list resource for storage cmk
1 parent 2d84667 commit 1bf9208

File tree

4 files changed

+229
-22
lines changed

4 files changed

+229
-22
lines changed

internal/services/storage/registration.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,5 +114,6 @@ func (r Registration) EphemeralResources() []func() ephemeral.EphemeralResource
114114
func (r Registration) ListResources() []sdk.FrameworkListWrappedResource {
115115
return []sdk.FrameworkListWrappedResource{
116116
StorageAccountListResource{},
117+
StorageAccountCustomerManagedKeyListResource{},
117118
}
118119
}

internal/services/storage/storage_account_customer_managed_key_resource.go

Lines changed: 32 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
package storage
55

66
import (
7+
"context"
78
"fmt"
89
"log"
910
"time"
@@ -25,6 +26,8 @@ import (
2526

2627
//go:generate go run ../../tools/generator-tests resourceidentity -resource-name storage_account_customer_managed_key -service-package-name storage -compare-values "subscription_id:storage_account_id,resource_group_name:storage_account_id,storage_account_name:storage_account_id"
2728

29+
var storageAccountCustomerManagedKeyResourceName = "azurerm_storage_account_customer_managed_key"
30+
2831
func resourceStorageAccountCustomerManagedKey() *pluginsdk.Resource {
2932
resource := &pluginsdk.Resource{
3033
Create: resourceStorageAccountCustomerManagedKeyCreateUpdate,
@@ -161,7 +164,7 @@ func resourceStorageAccountCustomerManagedKeyCreateUpdate(d *pluginsdk.ResourceD
161164

162165
if d.IsNewResource() {
163166
if existing.Model.Properties.Encryption != nil && pointer.From(existing.Model.Properties.Encryption.KeySource) == storageaccounts.KeySourceMicrosoftPointKeyvault {
164-
return tf.ImportAsExistsError("azurerm_storage_account_customer_managed_key", id.ID())
167+
return tf.ImportAsExistsError(storageAccountCustomerManagedKeyResourceName, id.ID())
165168
}
166169
}
167170

@@ -310,7 +313,6 @@ func resourceStorageAccountCustomerManagedKeyCreateUpdate(d *pluginsdk.ResourceD
310313

311314
func resourceStorageAccountCustomerManagedKeyRead(d *pluginsdk.ResourceData, meta interface{}) error {
312315
storageClient := meta.(*clients.Client).Storage.ResourceManager.StorageAccounts
313-
keyVaultsClient := meta.(*clients.Client).KeyVault
314316

315317
ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d)
316318
defer cancel()
@@ -330,11 +332,15 @@ func resourceStorageAccountCustomerManagedKeyRead(d *pluginsdk.ResourceData, met
330332
return fmt.Errorf("retrieving %s: %+v", id, err)
331333
}
332334

335+
return resourceStorageAccountCustomerManagedKeyFlatten(ctx, meta.(*clients.Client), d, id, resp.Model, true)
336+
}
337+
338+
func resourceStorageAccountCustomerManagedKeyFlatten(ctx context.Context, metaClient *clients.Client, d *pluginsdk.ResourceData, id *commonids.StorageAccountId, storageAccount *storageaccounts.StorageAccount, fetchCompleteData bool) error {
333339
d.Set("storage_account_id", id.ID())
334340

335341
enabled := false
336-
if model := resp.Model; model != nil {
337-
if props := model.Properties; props != nil {
342+
if storageAccount != nil {
343+
if props := storageAccount.Properties; props != nil {
338344
if encryption := props.Encryption; encryption != nil && pointer.From(encryption.KeySource) == storageaccounts.KeySourceMicrosoftPointKeyvault {
339345
enabled = true
340346

@@ -371,26 +377,30 @@ func resourceStorageAccountCustomerManagedKeyRead(d *pluginsdk.ResourceData, met
371377
keyID = keyId
372378
}
373379

374-
federatedIdentityClientID := ""
375-
userAssignedIdentity := ""
376-
if identityProps := encryption.Identity; identityProps != nil {
377-
federatedIdentityClientID = pointer.From(identityProps.FederatedIdentityClientId)
378-
userAssignedIdentity = pointer.From(identityProps.UserAssignedIdentity)
379-
}
380-
// now we have the key vault uri we can look up the ID
381-
// we can't look up the ID when using federated identity as the key will be under different tenant
382-
keyVaultID := ""
383-
if federatedIdentityClientID == "" && keyID != nil && !keyID.IsManagedHSM() {
384-
subscriptionResourceId := commonids.NewSubscriptionID(id.SubscriptionId)
385-
tmpKeyVaultID, err := keyVaultsClient.KeyVaultIDFromBaseUrl(ctx, subscriptionResourceId, keyID.KeyVaultBaseURL)
386-
if err != nil {
387-
return fmt.Errorf("retrieving Key Vault ID from the Base URI %q: %+v", keyID.KeyVaultBaseURL, err)
380+
if fetchCompleteData {
381+
keyVaultsClient := metaClient.KeyVault
382+
383+
federatedIdentityClientID := ""
384+
userAssignedIdentity := ""
385+
if identityProps := encryption.Identity; identityProps != nil {
386+
federatedIdentityClientID = pointer.From(identityProps.FederatedIdentityClientId)
387+
userAssignedIdentity = pointer.From(identityProps.UserAssignedIdentity)
388+
}
389+
// now we have the key vault uri we can look up the ID
390+
// we can't look up the ID when using federated identity as the key will be under different tenant
391+
keyVaultID := ""
392+
if federatedIdentityClientID == "" && keyID != nil && !keyID.IsManagedHSM() {
393+
subscriptionResourceId := commonids.NewSubscriptionID(id.SubscriptionId)
394+
tmpKeyVaultID, err := keyVaultsClient.KeyVaultIDFromBaseUrl(ctx, subscriptionResourceId, keyID.KeyVaultBaseURL)
395+
if err != nil {
396+
return fmt.Errorf("retrieving Key Vault ID from the Base URI %q: %+v", keyID.KeyVaultBaseURL, err)
397+
}
398+
keyVaultID = pointer.From(tmpKeyVaultID)
388399
}
389-
keyVaultID = pointer.From(tmpKeyVaultID)
400+
d.Set("key_vault_id", keyVaultID)
401+
d.Set("user_assigned_identity_id", userAssignedIdentity)
402+
d.Set("federated_identity_client_id", federatedIdentityClientID)
390403
}
391-
d.Set("key_vault_id", keyVaultID)
392-
d.Set("user_assigned_identity_id", userAssignedIdentity)
393-
d.Set("federated_identity_client_id", federatedIdentityClientID)
394404
}
395405
}
396406
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
package storage
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/hashicorp/go-azure-helpers/lang/pointer"
8+
"github.com/hashicorp/go-azure-helpers/resourcemanager/commonids"
9+
"github.com/hashicorp/go-azure-sdk/resource-manager/storage/2023-05-01/storageaccounts"
10+
"github.com/hashicorp/terraform-plugin-framework/list"
11+
"github.com/hashicorp/terraform-plugin-framework/resource"
12+
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
13+
"github.com/hashicorp/terraform-provider-azurerm/internal/sdk"
14+
"github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk"
15+
)
16+
17+
type StorageAccountCustomerManagedKeyListResource struct{}
18+
19+
var _ sdk.FrameworkListWrappedResource = new(StorageAccountCustomerManagedKeyListResource)
20+
21+
func (StorageAccountCustomerManagedKeyListResource) ResourceFunc() *pluginsdk.Resource {
22+
return resourceStorageAccountCustomerManagedKey()
23+
}
24+
25+
func (r StorageAccountCustomerManagedKeyListResource) Metadata(_ context.Context, _ resource.MetadataRequest, response *resource.MetadataResponse) {
26+
response.TypeName = storageAccountCustomerManagedKeyResourceName
27+
}
28+
29+
func (StorageAccountCustomerManagedKeyListResource) List(ctx context.Context, request list.ListRequest, stream *list.ListResultsStream, metadata sdk.ResourceMetadata) {
30+
client := metadata.Client.Storage.ResourceManager.StorageAccounts
31+
32+
// retrieve the deadline from the supplied context
33+
deadline, ok := ctx.Deadline()
34+
if !ok {
35+
// This *should* never happen given the List Wrapper instantiates a context with a timeout
36+
sdk.SetResponseErrorDiagnostic(stream, "internal-error", "context had no deadline")
37+
return
38+
}
39+
40+
var data sdk.DefaultListModel
41+
diags := request.Config.Get(ctx, &data)
42+
if diags.HasError() {
43+
stream.Results = list.ListResultsStreamDiagnostics(diags)
44+
return
45+
}
46+
47+
results := make([]storageaccounts.StorageAccount, 0)
48+
49+
subscriptionID := metadata.SubscriptionId
50+
if !data.SubscriptionId.IsNull() {
51+
subscriptionID = data.SubscriptionId.ValueString()
52+
}
53+
54+
switch {
55+
case !data.ResourceGroupName.IsNull():
56+
resp, err := client.ListByResourceGroupComplete(ctx, commonids.NewResourceGroupID(subscriptionID, data.ResourceGroupName.ValueString()))
57+
if err != nil {
58+
sdk.SetResponseErrorDiagnostic(stream, fmt.Sprintf("listing `%s`", storageAccountCustomerManagedKeyResourceName), err)
59+
return
60+
}
61+
62+
results = resp.Items
63+
default:
64+
resp, err := client.ListComplete(ctx, commonids.NewSubscriptionID(subscriptionID))
65+
if err != nil {
66+
sdk.SetResponseErrorDiagnostic(stream, fmt.Sprintf("listing `%s`", storageAccountCustomerManagedKeyResourceName), err)
67+
return
68+
}
69+
70+
results = resp.Items
71+
}
72+
73+
stream.Results = func(push func(list.ListResult) bool) {
74+
deadlineCtx, cancel := context.WithDeadline(context.Background(), deadline)
75+
defer cancel()
76+
77+
for _, storageAccount := range results {
78+
// results contain all storage accounts including without CMK
79+
// skip any accounts that do not have CMK
80+
if storageAccount.Properties.Encryption == nil || pointer.From(storageAccount.Properties.Encryption.KeySource) != "Microsoft.Keyvault" {
81+
continue
82+
}
83+
84+
result := request.NewListResult(deadlineCtx)
85+
result.DisplayName = pointer.From(storageAccount.Name)
86+
rd := resourceStorageAccountCustomerManagedKey().Data(&terraform.InstanceState{})
87+
88+
id, err := commonids.ParseStorageAccountID(pointer.From(storageAccount.Id))
89+
if err != nil {
90+
sdk.SetErrorDiagnosticAndPushListResult(result, push, "parsing Storage Account ID", err)
91+
return
92+
}
93+
rd.SetId(id.ID())
94+
95+
if err := resourceStorageAccountCustomerManagedKeyFlatten(deadlineCtx, metadata.Client, rd, id, &storageAccount, request.IncludeResource); err != nil {
96+
sdk.SetErrorDiagnosticAndPushListResult(result, push, fmt.Sprintf("encoding `%s` resource data", storageAccountCustomerManagedKeyResourceName), err)
97+
return
98+
}
99+
100+
sdk.EncodeListResult(deadlineCtx, rd, &result)
101+
if result.Diagnostics.HasError() {
102+
push(result)
103+
return
104+
}
105+
106+
if !push(result) {
107+
return
108+
}
109+
}
110+
}
111+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package storage_test
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"regexp"
7+
"strconv"
8+
"testing"
9+
10+
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
11+
"github.com/hashicorp/terraform-plugin-testing/knownvalue"
12+
"github.com/hashicorp/terraform-plugin-testing/querycheck"
13+
"github.com/hashicorp/terraform-plugin-testing/tfversion"
14+
"github.com/hashicorp/terraform-provider-azurerm/internal/acceptance"
15+
"github.com/hashicorp/terraform-provider-azurerm/internal/provider/framework"
16+
)
17+
18+
func TestAccStorageAccountCustomerManagedKey_list(t *testing.T) {
19+
data := acceptance.BuildTestData(t, "azurerm_storage_account_customer_managed_key", "testlist")
20+
r := StorageAccountCustomerManagedKeyResource{}
21+
22+
resource.Test(t, resource.TestCase{
23+
TerraformVersionChecks: []tfversion.TerraformVersionCheck{
24+
tfversion.SkipBelow(tfversion.Version1_14_0),
25+
},
26+
ProtoV5ProviderFactories: framework.ProtoV5ProviderFactoriesInit(context.Background(), "azurerm"),
27+
Steps: []resource.TestStep{
28+
{
29+
Config: r.basic(data),
30+
},
31+
{
32+
Query: true,
33+
Config: r.basicListQuery(),
34+
QueryResultChecks: []querycheck.QueryResultCheck{
35+
querycheck.ExpectLengthAtLeast("azurerm_storage_account_customer_managed_key.list", 1), // expect at least the 1 we created
36+
querycheck.ExpectIdentity(
37+
"azurerm_storage_account_customer_managed_key.list",
38+
map[string]knownvalue.Check{
39+
"storage_account_name": knownvalue.StringRegexp(regexp.MustCompile(data.RandomString)),
40+
"resource_group_name": knownvalue.StringRegexp(regexp.MustCompile(strconv.Itoa(data.RandomInteger))),
41+
"subscription_id": knownvalue.StringExact(data.Subscriptions.Primary),
42+
},
43+
),
44+
},
45+
},
46+
{
47+
Query: true,
48+
Config: r.basicListQueryByResourceGroupName(data),
49+
QueryResultChecks: []querycheck.QueryResultCheck{
50+
querycheck.ExpectLength("azurerm_storage_account_customer_managed_key.list", 1), // only 1 should be returned
51+
querycheck.ExpectIdentity(
52+
"azurerm_storage_account_customer_managed_key.list",
53+
map[string]knownvalue.Check{
54+
"storage_account_name": knownvalue.StringRegexp(regexp.MustCompile(data.RandomString)),
55+
"resource_group_name": knownvalue.StringRegexp(regexp.MustCompile(strconv.Itoa(data.RandomInteger))),
56+
"subscription_id": knownvalue.StringExact(data.Subscriptions.Primary),
57+
},
58+
),
59+
},
60+
},
61+
},
62+
})
63+
}
64+
65+
func (r StorageAccountCustomerManagedKeyResource) basicListQuery() string {
66+
return `
67+
list "azurerm_storage_account_customer_managed_key" "list" {
68+
provider = azurerm
69+
config {}
70+
}
71+
`
72+
}
73+
74+
func (r StorageAccountCustomerManagedKeyResource) basicListQueryByResourceGroupName(data acceptance.TestData) string {
75+
return fmt.Sprintf(`
76+
list "azurerm_storage_account_customer_managed_key" "list" {
77+
provider = azurerm
78+
include_resource = true
79+
config {
80+
subscription_id = "%s"
81+
resource_group_name = "acctestRG-%d"
82+
}
83+
}
84+
`, data.Subscriptions.Primary, data.RandomInteger)
85+
}

0 commit comments

Comments
 (0)