Skip to content

Commit

Permalink
storage - add support for storage_account_id to `azurerm_storage_…
Browse files Browse the repository at this point in the history
…container` and `azurerm_storage_share` (#27733)

* start of dataplane / rm separation

* refactor resrouce functions

* review comments and feedback

* missed review comment

* missed review comment

* update comment

* missed review comments
  • Loading branch information
jackofallops authored Nov 5, 2024
1 parent 45c06a4 commit 8f3f582
Show file tree
Hide file tree
Showing 13 changed files with 2,230 additions and 538 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package custompollers

import (
"context"
"time"

"github.com/hashicorp/go-azure-helpers/lang/response"
"github.com/hashicorp/go-azure-sdk/resource-manager/storage/2023-01-01/fileshares"
"github.com/hashicorp/go-azure-sdk/sdk/client/pollers"
)

var _ pollers.PollerType = storageShareCreatePoller{}

type storageShareCreatePoller struct {
id fileshares.ShareId
client *fileshares.FileSharesClient
payload fileshares.FileShare
}

func NewStorageShareCreatePoller(client *fileshares.FileSharesClient, id fileshares.ShareId, payload fileshares.FileShare) *storageShareCreatePoller {
return &storageShareCreatePoller{
id: id,
client: client,
payload: payload,
}
}

func (p storageShareCreatePoller) Poll(ctx context.Context) (*pollers.PollResult, error) {
// Note - Whilst this is an antipattern for the Provider, the API provides no way currently to poll for deletion
// to ensure it's removed. To support rapid delete then re-creation we check for 409's that indicate the resource
// is still being removed.
resp, err := p.client.Create(ctx, p.id, p.payload, fileshares.DefaultCreateOperationOptions())
if err != nil {
if response.WasConflict(resp.HttpResponse) {
return &pollers.PollResult{
PollInterval: 5 * time.Second,
Status: pollers.PollingStatusInProgress,
}, nil
}

return &pollers.PollResult{
HttpResponse: nil,
PollInterval: 5 * time.Second,
Status: pollers.PollingStatusFailed,
}, err
}

return &pollers.PollResult{
PollInterval: 5,
Status: pollers.PollingStatusSucceeded,
}, nil
}
168 changes: 119 additions & 49 deletions internal/services/storage/storage_container_data_source.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,20 @@ import (
"fmt"
"time"

"github.com/hashicorp/go-azure-helpers/lang/pointer"
"github.com/hashicorp/go-azure-helpers/resourcemanager/commonids"
"github.com/hashicorp/terraform-provider-azurerm/internal/clients"
"github.com/hashicorp/terraform-provider-azurerm/internal/features"
"github.com/hashicorp/terraform-provider-azurerm/internal/services/storage/client"
"github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk"
"github.com/hashicorp/terraform-provider-azurerm/internal/tf/validation"
"github.com/hashicorp/terraform-provider-azurerm/internal/timeouts"
"github.com/tombuildsstuff/giovanni/storage/2023-11-03/blob/accounts"
"github.com/tombuildsstuff/giovanni/storage/2023-11-03/blob/containers"
)

func dataSourceStorageContainer() *pluginsdk.Resource {
return &pluginsdk.Resource{
r := &pluginsdk.Resource{
Read: dataSourceStorageContainerRead,

Timeouts: &pluginsdk.ResourceTimeout{
Expand All @@ -30,9 +33,10 @@ func dataSourceStorageContainer() *pluginsdk.Resource {
Required: true,
},

"storage_account_name": {
Type: pluginsdk.TypeString,
Required: true,
"storage_account_id": {
Type: pluginsdk.TypeString,
Required: true,
ValidateFunc: commonids.ValidateStorageAccountID,
},

"container_access_type": {
Expand All @@ -52,7 +56,6 @@ func dataSourceStorageContainer() *pluginsdk.Resource {

"metadata": MetaDataComputedSchema(),

// TODO: support for ACL's, Legal Holds and Immutability Policies
"has_immutability_policy": {
Type: pluginsdk.TypeBool,
Computed: true,
Expand All @@ -62,77 +65,144 @@ func dataSourceStorageContainer() *pluginsdk.Resource {
Type: pluginsdk.TypeBool,
Computed: true,
},
},
}

"resource_manager_id": {
Type: pluginsdk.TypeString,
Computed: true,
if !features.FivePointOhBeta() {
r.Schema["resource_manager_id"] = &pluginsdk.Schema{
Type: pluginsdk.TypeString,
Computed: true,
Deprecated: "this property has been deprecated in favour of `id` and will be removed in version 5.0 of the Provider.",
}

r.Schema["storage_account_name"] = &pluginsdk.Schema{
Type: pluginsdk.TypeString,
Optional: true,
ValidateFunc: validation.StringIsNotEmpty,
ExactlyOneOf: []string{
"storage_account_name",
"storage_account_id",
},
},
}

r.Schema["storage_account_id"] = &pluginsdk.Schema{
Type: pluginsdk.TypeString,
Optional: true,
ValidateFunc: commonids.ValidateStorageAccountID,
ExactlyOneOf: []string{
"storage_account_name",
"storage_account_id",
},
}
}

return r
}

func dataSourceStorageContainerRead(d *pluginsdk.ResourceData, meta interface{}) error {
storageClient := meta.(*clients.Client).Storage
containerClient := meta.(*clients.Client).Storage.ResourceManager.BlobContainers
subscriptionId := meta.(*clients.Client).Account.SubscriptionId
ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d)
defer cancel()

containerName := d.Get("name").(string)
accountName := d.Get("storage_account_name").(string)

account, err := storageClient.FindAccount(ctx, subscriptionId, accountName)
if err != nil {
return fmt.Errorf("retrieving Storage Account %q for Container %q: %v", accountName, containerName, err)
}
if account == nil {
return fmt.Errorf("locating Storage Account %q for Container %q", accountName, containerName)
}

containersDataPlaneClient, err := storageClient.ContainersDataPlaneClient(ctx, *account, storageClient.DataPlaneOperationSupportingAnyAuthMethod())
if err != nil {
return fmt.Errorf("building Containers Client: %v", err)
}

// Determine the blob endpoint, so we can build a data plane ID
endpoint, err := account.DataPlaneEndpoint(client.EndpointTypeBlob)
if err != nil {
return fmt.Errorf("determining Blob endpoint: %v", err)
if !features.FivePointOhBeta() {
storageClient := meta.(*clients.Client).Storage
accountName := d.Get("storage_account_name").(string)
if accountName != "" {
account, err := storageClient.FindAccount(ctx, subscriptionId, accountName)
if err != nil {
return fmt.Errorf("retrieving Storage Account %q for Container %q: %v", accountName, containerName, err)
}
if account == nil {
return fmt.Errorf("locating Storage Account %q for Container %q", accountName, containerName)
}

containersDataPlaneClient, err := storageClient.ContainersDataPlaneClient(ctx, *account, storageClient.DataPlaneOperationSupportingAnyAuthMethod())
if err != nil {
return fmt.Errorf("building Containers Client: %v", err)
}

// Determine the blob endpoint, so we can build a data plane ID
endpoint, err := account.DataPlaneEndpoint(client.EndpointTypeBlob)
if err != nil {
return fmt.Errorf("determining Blob endpoint: %v", err)
}

// Parse the blob endpoint as a data plane account ID
accountId, err := accounts.ParseAccountID(*endpoint, storageClient.StorageDomainSuffix)
if err != nil {
return fmt.Errorf("parsing Account ID: %v", err)
}

id := containers.NewContainerID(*accountId, containerName)

props, err := containersDataPlaneClient.Get(ctx, containerName)
if err != nil {
return fmt.Errorf("retrieving %s: %v", id, err)
}
if props == nil {
return fmt.Errorf("retrieving %s: result was nil", id)
}

d.SetId(id.ID())

d.Set("name", containerName)
d.Set("storage_account_name", accountName)
d.Set("container_access_type", flattenStorageContainerAccessLevel(props.AccessLevel))

d.Set("default_encryption_scope", props.DefaultEncryptionScope)
d.Set("encryption_scope_override_enabled", !props.EncryptionScopeOverrideDisabled)

if err = d.Set("metadata", FlattenMetaData(props.MetaData)); err != nil {
return fmt.Errorf("setting `metadata`: %v", err)
}

d.Set("has_immutability_policy", props.HasImmutabilityPolicy)
d.Set("has_legal_hold", props.HasLegalHold)

resourceManagerId := commonids.NewStorageContainerID(account.StorageAccountId.SubscriptionId, account.StorageAccountId.ResourceGroupName, account.StorageAccountId.StorageAccountName, containerName)
d.Set("resource_manager_id", resourceManagerId.ID())

return nil
}
}

// Parse the blob endpoint as a data plane account ID
accountId, err := accounts.ParseAccountID(*endpoint, storageClient.StorageDomainSuffix)
accountId, err := commonids.ParseStorageAccountID(d.Get("storage_account_id").(string))
if err != nil {
return fmt.Errorf("parsing Account ID: %v", err)
return err
}

id := containers.NewContainerID(*accountId, containerName)
id := commonids.NewStorageContainerID(accountId.SubscriptionId, accountId.ResourceGroupName, accountId.StorageAccountName, containerName)

props, err := containersDataPlaneClient.Get(ctx, containerName)
container, err := containerClient.Get(ctx, id)
if err != nil {
return fmt.Errorf("retrieving %s: %v", id, err)
}
if props == nil {
return fmt.Errorf("retrieving %s: result was nil", id)
}

d.SetId(id.ID())
if model := container.Model; model != nil {
if props := model.Properties; props != nil {
d.Set("name", containerName)
d.Set("container_access_type", containerAccessTypeConversionMap[string(pointer.From(props.PublicAccess))])

d.Set("name", containerName)
d.Set("storage_account_name", accountName)
d.Set("container_access_type", flattenStorageContainerAccessLevel(props.AccessLevel))
d.Set("default_encryption_scope", props.DefaultEncryptionScope)
d.Set("encryption_scope_override_enabled", !pointer.From(props.DenyEncryptionScopeOverride))

d.Set("default_encryption_scope", props.DefaultEncryptionScope)
d.Set("encryption_scope_override_enabled", !props.EncryptionScopeOverrideDisabled)
if err = d.Set("metadata", FlattenMetaData(pointer.From(props.Metadata))); err != nil {
return fmt.Errorf("setting `metadata`: %v", err)
}

if err = d.Set("metadata", FlattenMetaData(props.MetaData)); err != nil {
return fmt.Errorf("setting `metadata`: %v", err)
}
d.Set("has_immutability_policy", props.HasImmutabilityPolicy)
d.Set("has_legal_hold", props.HasLegalHold)

d.Set("has_immutability_policy", props.HasImmutabilityPolicy)
d.Set("has_legal_hold", props.HasLegalHold)
if !features.FivePointOhBeta() {
d.Set("resource_manager_id", id.ID())
}
}
}

resourceManagerId := commonids.NewStorageContainerID(account.StorageAccountId.SubscriptionId, account.StorageAccountId.ResourceGroupName, account.StorageAccountId.StorageAccountName, containerName)
d.Set("resource_manager_id", resourceManagerId.ID())
d.SetId(id.ID())

return nil
}
38 changes: 37 additions & 1 deletion internal/services/storage/storage_container_data_source_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@ import (

"github.com/hashicorp/terraform-provider-azurerm/internal/acceptance"
"github.com/hashicorp/terraform-provider-azurerm/internal/acceptance/check"
"github.com/hashicorp/terraform-provider-azurerm/internal/features"
)

type StorageContainerDataSource struct{}

func TestAccDataSourceStorageContainer_basic(t *testing.T) {
func TestAccStorageContainerDataSource_basic(t *testing.T) {
data := acceptance.BuildTestData(t, "data.azurerm_storage_container", "test")

data.DataSourceTest(t, []acceptance.TestStep{
Expand All @@ -34,6 +35,41 @@ func TestAccDataSourceStorageContainer_basic(t *testing.T) {

func (d StorageContainerDataSource) basic(data acceptance.TestData) string {
return fmt.Sprintf(`
%s
data "azurerm_storage_container" "test" {
name = azurerm_storage_container.test.name
storage_account_id = azurerm_storage_account.test.id
}
`, StorageContainerResource{}.complete(data))
}

func TestAccStorageContainerDataSource_basicDeprecated(t *testing.T) {
if features.FivePointOhBeta() {
t.Skip("skipping as test is not valid in 5.0")
}

data := acceptance.BuildTestData(t, "data.azurerm_storage_container", "test")

data.DataSourceTest(t, []acceptance.TestStep{
{
Config: StorageContainerDataSource{}.basicDeprecated(data),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).Key("container_access_type").HasValue("private"),
check.That(data.ResourceName).Key("has_immutability_policy").HasValue("false"),
check.That(data.ResourceName).Key("default_encryption_scope").HasValue(fmt.Sprintf("acctestEScontainer%d", data.RandomInteger)),
check.That(data.ResourceName).Key("encryption_scope_override_enabled").HasValue("true"),
check.That(data.ResourceName).Key("metadata.%").HasValue("2"),
check.That(data.ResourceName).Key("metadata.k1").HasValue("v1"),
check.That(data.ResourceName).Key("metadata.k2").HasValue("v2"),
),
},
})
}

func (d StorageContainerDataSource) basicDeprecated(data acceptance.TestData) string {
return fmt.Sprintf(`
provider "azurerm" {
features {}
}
Expand Down
Loading

0 comments on commit 8f3f582

Please sign in to comment.