Skip to content

Commit 0e080ae

Browse files
feat: add HNS support for storage bucket (#11852) (#8428)
[upstream:785073334719f0657f515f93283715c86ce999da] Signed-off-by: Modular Magician <[email protected]>
1 parent d010b64 commit 0e080ae

File tree

4 files changed

+235
-0
lines changed

4 files changed

+235
-0
lines changed

.changelog/11852.txt

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:
2+
storage: added 'hierarchical_namespace' to 'google_storage_bucket' resource
3+
```

google-beta/services/storage/resource_storage_bucket.go

+68
Original file line numberDiff line numberDiff line change
@@ -551,6 +551,24 @@ func ResourceStorageBucket() *schema.Resource {
551551
},
552552
},
553553
},
554+
"hierarchical_namespace": {
555+
Type: schema.TypeList,
556+
MaxItems: 1,
557+
Optional: true,
558+
ForceNew: true,
559+
DiffSuppressFunc: hierachicalNamespaceDiffSuppress,
560+
Description: `The bucket's HNS support, which defines bucket can organize folders in logical file system structure`,
561+
Elem: &schema.Resource{
562+
Schema: map[string]*schema.Schema{
563+
"enabled": {
564+
Type: schema.TypeBool,
565+
Required: true,
566+
ForceNew: true,
567+
Description: `Set this enabled flag to true when folders with logical files structure. Default value is false.`,
568+
},
569+
},
570+
},
571+
},
554572
},
555573
UseJSONNumber: true,
556574
}
@@ -698,6 +716,10 @@ func resourceStorageBucketCreate(d *schema.ResourceData, meta interface{}) error
698716
sb.SoftDeletePolicy = expandBucketSoftDeletePolicy(v.([]interface{}))
699717
}
700718

719+
if v, ok := d.GetOk("hierarchical_namespace"); ok {
720+
sb.HierarchicalNamespace = expandBucketHierachicalNamespace(v.([]interface{}))
721+
}
722+
701723
var res *storage.Bucket
702724

703725
err = transport_tpg.Retry(transport_tpg.RetryOptions{
@@ -1296,6 +1318,38 @@ func flattenBucketSoftDeletePolicy(softDeletePolicy *storage.BucketSoftDeletePol
12961318
return policies
12971319
}
12981320

1321+
func expandBucketHierachicalNamespace(configured interface{}) *storage.BucketHierarchicalNamespace {
1322+
configuredHierachicalNamespace := configured.([]interface{})
1323+
if len(configuredHierachicalNamespace) == 0 {
1324+
return nil
1325+
}
1326+
configuredHierachicalNamespacePolicy := configuredHierachicalNamespace[0].(map[string]interface{})
1327+
hierachicalNamespacePolicy := &storage.BucketHierarchicalNamespace{
1328+
Enabled: (configuredHierachicalNamespacePolicy["enabled"].(bool)),
1329+
}
1330+
hierachicalNamespacePolicy.ForceSendFields = append(hierachicalNamespacePolicy.ForceSendFields, "Enabled")
1331+
return hierachicalNamespacePolicy
1332+
}
1333+
1334+
func flattenBucketHierarchicalNamespacePolicy(hierachicalNamespacePolicy *storage.BucketHierarchicalNamespace) []map[string]interface{} {
1335+
policies := make([]map[string]interface{}, 0, 1)
1336+
if hierachicalNamespacePolicy == nil {
1337+
// a null object returned from the API is equivalent to a block with enabled = false
1338+
// to handle this consistently, always write a null response as a hydrated block with false
1339+
defaultPolicy := map[string]interface{}{
1340+
"enabled": false,
1341+
}
1342+
1343+
policies = append(policies, defaultPolicy)
1344+
return policies
1345+
}
1346+
policy := map[string]interface{}{
1347+
"enabled": hierachicalNamespacePolicy.Enabled,
1348+
}
1349+
policies = append(policies, policy)
1350+
return policies
1351+
}
1352+
12991353
func expandBucketVersioning(configured interface{}) *storage.BucketVersioning {
13001354
versionings := configured.([]interface{})
13011355
if len(versionings) == 0 {
@@ -1887,6 +1941,9 @@ func setStorageBucket(d *schema.ResourceData, config *transport_tpg.Config, res
18871941
if err := d.Set("soft_delete_policy", flattenBucketSoftDeletePolicy(res.SoftDeletePolicy)); err != nil {
18881942
return fmt.Errorf("Error setting soft_delete_policy: %s", err)
18891943
}
1944+
if err := d.Set("hierarchical_namespace", flattenBucketHierarchicalNamespacePolicy(res.HierarchicalNamespace)); err != nil {
1945+
return fmt.Errorf("Error setting hierarchical namespace: %s", err)
1946+
}
18901947
if res.IamConfiguration != nil && res.IamConfiguration.UniformBucketLevelAccess != nil {
18911948
if err := d.Set("uniform_bucket_level_access", res.IamConfiguration.UniformBucketLevelAccess.Enabled); err != nil {
18921949
return fmt.Errorf("Error setting uniform_bucket_level_access: %s", err)
@@ -1916,3 +1973,14 @@ func setStorageBucket(d *schema.ResourceData, config *transport_tpg.Config, res
19161973
d.SetId(res.Id)
19171974
return nil
19181975
}
1976+
1977+
func hierachicalNamespaceDiffSuppress(k, old, new string, r *schema.ResourceData) bool {
1978+
if k == "hierarchical_namespace.#" && old == "1" && new == "0" {
1979+
o, _ := r.GetChange("hierarchical_namespace.0.enabled")
1980+
if !o.(bool) {
1981+
return true
1982+
}
1983+
}
1984+
1985+
return false
1986+
}

google-beta/services/storage/resource_storage_bucket_test.go

+142
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"time"
1212

1313
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
14+
"github.com/hashicorp/terraform-plugin-testing/plancheck"
1415
"github.com/hashicorp/terraform-plugin-testing/terraform"
1516

1617
"github.com/hashicorp/terraform-provider-google-beta/google-beta/acctest"
@@ -1547,6 +1548,147 @@ func TestAccStorageBucket_SoftDeletePolicy(t *testing.T) {
15471548
})
15481549
}
15491550

1551+
// testcase to create HNS bucket and
1552+
// forcenew to recreate the bucket if HNS set to false
1553+
func TestAccStorageBucket_basic_hns(t *testing.T) {
1554+
t.Parallel()
1555+
1556+
bucketName := acctest.TestBucketName(t)
1557+
acctest.VcrTest(t, resource.TestCase{
1558+
PreCheck: func() { acctest.AccTestPreCheck(t) },
1559+
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
1560+
CheckDestroy: testAccStorageBucketDestroyProducer(t),
1561+
Steps: []resource.TestStep{
1562+
{
1563+
Config: testAccStorageBucket_basic_hns(bucketName, true),
1564+
Check: resource.ComposeTestCheckFunc(
1565+
resource.TestCheckResourceAttr(
1566+
"google_storage_bucket.bucket", "hierarchical_namespace.0.enabled", "true"),
1567+
),
1568+
},
1569+
{
1570+
ResourceName: "google_storage_bucket.bucket",
1571+
ImportState: true,
1572+
ImportStateVerify: true,
1573+
ImportStateVerifyIgnore: []string{"force_destroy"},
1574+
},
1575+
{
1576+
Config: testAccStorageBucket_basic_hns_with_data(bucketName, false),
1577+
},
1578+
{
1579+
ResourceName: "google_storage_bucket.bucket",
1580+
ImportState: true,
1581+
ImportStateVerify: true,
1582+
ImportStateVerifyIgnore: []string{"force_destroy"},
1583+
},
1584+
{
1585+
Config: testAccStorageBucket_uniformBucketAccessOnly(bucketName, true),
1586+
ConfigPlanChecks: resource.ConfigPlanChecks{
1587+
PreApply: []plancheck.PlanCheck{
1588+
plancheck.ExpectEmptyPlan(),
1589+
},
1590+
},
1591+
},
1592+
{
1593+
ResourceName: "google_storage_bucket.bucket",
1594+
ImportState: true,
1595+
ImportStateVerify: true,
1596+
ImportStateVerifyIgnore: []string{"force_destroy"},
1597+
},
1598+
},
1599+
})
1600+
}
1601+
1602+
func TestAccStorageBucket_hns_force_destroy(t *testing.T) {
1603+
t.Parallel()
1604+
1605+
bucketName := acctest.TestBucketName(t)
1606+
1607+
acctest.VcrTest(t, resource.TestCase{
1608+
PreCheck: func() { acctest.AccTestPreCheck(t) },
1609+
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
1610+
CheckDestroy: testAccStorageBucketDestroyProducer(t),
1611+
Steps: []resource.TestStep{
1612+
{
1613+
Config: testAccStorageBucket_basic_hns_with_data(bucketName, true),
1614+
Check: resource.ComposeTestCheckFunc(
1615+
testAccCheckStorageBucketPutFolderItem(t, bucketName),
1616+
),
1617+
},
1618+
},
1619+
})
1620+
}
1621+
1622+
func testAccCheckStorageBucketPutFolderItem(t *testing.T, bucketName string) resource.TestCheckFunc {
1623+
return func(s *terraform.State) error {
1624+
config := acctest.GoogleProviderConfig(t)
1625+
1626+
data := bytes.NewBufferString("test")
1627+
dataReader := bytes.NewReader(data.Bytes())
1628+
folderName := fmt.Sprintf("tf-test/tf-test-folder-%d/", acctest.RandInt(t))
1629+
emptyfolderName := fmt.Sprintf("tf-test/tf-test-folder-%d/", acctest.RandInt(t))
1630+
object := &storage.Object{Name: folderName + "bucketDestroyTestFile"}
1631+
1632+
folder := storage.Folder{
1633+
Bucket: bucketName,
1634+
Name: folderName,
1635+
}
1636+
1637+
emptyFolder := storage.Folder{
1638+
Bucket: bucketName,
1639+
Name: emptyfolderName,
1640+
}
1641+
1642+
if res, err := config.NewStorageClient(config.UserAgent).Folders.Insert(bucketName, &folder).Recursive(true).Do(); err == nil {
1643+
log.Printf("[INFO] Created folder %v at location %v\n\n", res.Name, res.SelfLink)
1644+
} else {
1645+
return fmt.Errorf("Folders.Insert failed: %v", err)
1646+
}
1647+
1648+
// This needs to use Media(io.Reader) call, otherwise it does not go to /upload API and fails
1649+
if res, err := config.NewStorageClient(config.UserAgent).Objects.Insert(bucketName, object).Media(dataReader).Do(); err == nil {
1650+
log.Printf("[INFO] Created object %v at location %v\n\n", res.Name, res.SelfLink)
1651+
} else {
1652+
return fmt.Errorf("Objects.Insert failed: %v", err)
1653+
}
1654+
1655+
if res, err := config.NewStorageClient(config.UserAgent).Folders.Insert(bucketName, &emptyFolder).Recursive(true).Do(); err == nil {
1656+
log.Printf("[INFO] Created folder %v at location %v\n\n", res.Name, res.SelfLink)
1657+
} else {
1658+
return fmt.Errorf("Folders.Insert failed: %v", err)
1659+
}
1660+
1661+
return nil
1662+
}
1663+
}
1664+
1665+
func testAccStorageBucket_basic_hns(bucketName string, enabled bool) string {
1666+
return fmt.Sprintf(`
1667+
resource "google_storage_bucket" "bucket" {
1668+
name = "%s"
1669+
location = "US"
1670+
uniform_bucket_level_access = true
1671+
hierarchical_namespace {
1672+
enabled = %t
1673+
}
1674+
}
1675+
`, bucketName, enabled)
1676+
}
1677+
1678+
func testAccStorageBucket_basic_hns_with_data(bucketName string, enabled bool) string {
1679+
return fmt.Sprintf(`
1680+
resource "google_storage_bucket" "bucket" {
1681+
name = "%s"
1682+
location = "US"
1683+
uniform_bucket_level_access = true
1684+
hierarchical_namespace {
1685+
enabled = %t
1686+
}
1687+
force_destroy= true
1688+
}
1689+
`, bucketName, enabled)
1690+
}
1691+
15501692
func testAccCheckStorageBucketExists(t *testing.T, n string, bucketName string, bucket *storage.Bucket) resource.TestCheckFunc {
15511693
return func(s *terraform.State) error {
15521694
rs, ok := s.RootModule().Resources[n]

website/docs/r/storage_bucket.html.markdown

+22
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,20 @@ resource "google_storage_bucket" "auto-expire" {
103103
}
104104
```
105105

106+
## Example Usage - Enabling hierarchical namespace
107+
108+
```hcl
109+
resource "google_storage_bucket" "auto-expire" {
110+
name = "hns-enabled-bucket"
111+
location = "US"
112+
force_destroy = true
113+
114+
hierarchical_namespace = {
115+
enabled = true
116+
}
117+
}
118+
```
119+
106120
## Argument Reference
107121

108122
The following arguments are supported:
@@ -157,6 +171,8 @@ The following arguments are supported:
157171

158172
* `soft_delete_policy` - (Optional, Computed) The bucket's soft delete policy, which defines the period of time that soft-deleted objects will be retained, and cannot be permanently deleted. If the block is not provided, Server side value will be kept which means removal of block won't generate any terraform change. Structure is [documented below](#nested_soft_delete_policy).
159173

174+
* `hierarchical_namespace` - (Optional, ForceNew) The bucket's hierarchical namespace policy, which defines the bucket capability to handle folders in logical structure. Structure is [documented below](#nested_hierarchical_namespace).
175+
160176
<a name="nested_lifecycle_rule"></a>The `lifecycle_rule` block supports:
161177

162178
* `action` - (Required) The Lifecycle Rule's action configuration. A single block of this type is supported. Structure is [documented below](#nested_action).
@@ -269,6 +285,12 @@ The following arguments are supported:
269285

270286
* `effective_time` - (Computed) Server-determined value that indicates the time from which the policy, or one with a greater retention, was effective. This value is in RFC 3339 format.
271287

288+
<a name="nested_hierarchical_namespace"></a>The `hierarchical_namespace` block supports:
289+
290+
* `enabled` - (Optional) Enable hierarchical namespace for the bucket.
291+
To use this flag, you must also use --uniform-bucket-level-access
292+
293+
272294
## Attributes Reference
273295

274296
In addition to the arguments listed above, the following computed attributes are

0 commit comments

Comments
 (0)