Skip to content

Commit 4a69edf

Browse files
Biglake namespace resource (#16280)
Co-authored-by: Stephen Lewis (Burrows) <stephen.r.burrows@gmail.com>
1 parent 9240e1a commit 4a69edf

File tree

9 files changed

+259
-3
lines changed

9 files changed

+259
-3
lines changed
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# Copyright 2025 Google Inc.
2+
# Licensed under the Apache License, Version 2.0 (the "License");
3+
# you may not use this file except in compliance with the License.
4+
# You may obtain a copy of the License at
5+
#
6+
# http://www.apache.org/licenses/LICENSE-2.0
7+
#
8+
# Unless required by applicable law or agreed to in writing, software
9+
# distributed under the License is distributed on an "AS IS" BASIS,
10+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
# See the License for the specific language governing permissions and
12+
# limitations under the License.
13+
14+
---
15+
name: 'IcebergNamespace'
16+
description: |
17+
IcebergNamespaces are containers for Apache Iceberg Tables within an IcebergCatalog.
18+
supports_indirect_user_project_override: true
19+
base_url: 'iceberg/v1/restcatalog/v1/projects/{{project}}/catalogs/{{catalog}}/namespaces'
20+
self_link: 'iceberg/v1/restcatalog/v1/projects/{{project}}/catalogs/{{catalog}}/namespaces/{{namespace_id}}'
21+
id_format: 'projects/{{project}}/catalogs/{{catalog}}/namespaces/{{namespace_id}}'
22+
import_format:
23+
- 'projects/{{project}}/catalogs/{{catalog}}/namespaces/{{namespace_id}}'
24+
- '{{project}}/{{catalog}}/{{namespace_id}}'
25+
- '{{catalog}}/{{namespace_id}}'
26+
immutable: false
27+
custom_code:
28+
constants: 'templates/terraform/constants/biglake_iceberg_namespace.go.tmpl'
29+
update_encoder: 'templates/terraform/encoders/biglake_iceberg_namespace.go.tmpl'
30+
parameters:
31+
- name: 'catalog'
32+
type: String
33+
required: true
34+
immutable: true
35+
url_param_only: true
36+
description: |
37+
The name of the IcebergCatalog.
38+
properties:
39+
- name: 'namespace_id'
40+
api_name: 'namespace'
41+
type: String
42+
description: |
43+
The unique identifier of the namespace.
44+
required: true
45+
immutable: true
46+
custom_expand: 'templates/terraform/custom_expand/biglake_iceberg_namespace_id.go.tmpl'
47+
custom_flatten: 'templates/terraform/custom_flatten/biglake_iceberg_namespace_id.go.tmpl'
48+
- name: 'properties'
49+
type: KeyValuePairs
50+
description: |
51+
User-defined properties for the namespace.
52+
required: false
53+
default_from_api: true
54+
diff_suppress_func: 'icebergNamespacePropertiesDiffSuppress'
55+
update_verb: 'POST'
56+
update_url: 'iceberg/v1/restcatalog/v1/projects/{{project}}/catalogs/{{catalog}}/namespaces/{{namespace_id}}/properties'
57+
iam_policy:
58+
base_url: 'v1/projects/{{project}}/catalogs/{{catalog}}/namespaces/{{namespace_id}}'
59+
parent_resource_attribute: 'namespace_id'
60+
method_name_separator: ":"
61+
fetch_iam_policy_verb: 'GET'
62+
import_format:
63+
- 'projects/{{project}}/catalogs/{{catalog}}/namespaces/{{namespace_id}}'
64+
- '{{namespace_id}}'
65+
allowed_iam_role: 'roles/biglake.editor'
66+
sample_config_body: 'templates/terraform/iam/example_config_body/biglake_iceberg_namespace.tf.tmpl'
67+
examples:
68+
- name: 'biglake_iceberg_namespace'
69+
primary_resource_id: 'my_iceberg_namespace'
70+
vars:
71+
bucket_name: 'example-bucket'
72+
test_env_vars:
73+
GOOGLE_BILLING_PROJECT: 'PROJECT_NAME'
74+
USER_PROJECT_OVERRIDE: 'true'
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
var icebergNamespaceIgnoredProperties = map[string]bool{
2+
"location": true,
3+
}
4+
5+
func icebergNamespacePropertiesDiffSuppress(k, old, new string, d *schema.ResourceData) bool {
6+
// properties.KEY
7+
parts := strings.Split(k, ".")
8+
if len(parts) == 2 && icebergNamespaceIgnoredProperties[parts[1]] {
9+
return true
10+
}
11+
return false
12+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
func expand{{$.GetPrefix}}{{$.TitlelizeProperty}}(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) {
2+
if v == nil {
3+
return nil, nil
4+
}
5+
return strings.Split(v.(string), "\x1f"), nil
6+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
func flatten{{$.GetPrefix}}{{$.TitlelizeProperty}}(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} {
2+
if v == nil {
3+
return nil
4+
}
5+
parts := v.([]interface{})
6+
s := make([]string, len(parts))
7+
for i, p := range parts {
8+
s[i] = p.(string)
9+
}
10+
return strings.Join(s, "\x1f")
11+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
if d.HasChange("properties") {
2+
oldProp, newProp := d.GetChange("properties")
3+
oldMap := oldProp.(map[string]interface{})
4+
newMap := newProp.(map[string]interface{})
5+
6+
removals := []string{}
7+
for k := range oldMap {
8+
if icebergNamespaceIgnoredProperties[k] {
9+
continue
10+
}
11+
if _, ok := newMap[k]; !ok {
12+
removals = append(removals, k)
13+
}
14+
}
15+
16+
updates := map[string]string{}
17+
for k, v := range newMap {
18+
if icebergNamespaceIgnoredProperties[k] {
19+
continue
20+
}
21+
updates[k] = v.(string)
22+
}
23+
24+
return map[string]interface{}{
25+
"removals": removals,
26+
"updates": updates,
27+
}, nil
28+
}
29+
return nil, nil
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
resource "google_storage_bucket" "bucket" {
2+
name = "{{index $.Vars "bucket_name"}}"
3+
location = "us-central1"
4+
force_destroy = true
5+
uniform_bucket_level_access = true
6+
}
7+
8+
resource "google_biglake_iceberg_catalog" "catalog" {
9+
name = google_storage_bucket.bucket.name
10+
catalog_type = "CATALOG_TYPE_GCS_BUCKET"
11+
}
12+
13+
resource "google_biglake_iceberg_namespace" "{{$.PrimaryResourceId}}" {
14+
catalog = google_biglake_iceberg_catalog.catalog.name
15+
namespace_id = "{{$.PrimaryResourceId}}"
16+
properties = {
17+
key = "value"
18+
}
19+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
project = google_biglake_iceberg_namespace.my_iceberg_namespace.project
2+
catalog = google_biglake_iceberg_namespace.my_iceberg_namespace.catalog
3+
namespace_id = google_biglake_iceberg_namespace.my_iceberg_namespace.id

mmv1/third_party/terraform/acctest/resource_inventory_test.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -272,9 +272,11 @@ func TestValidateResourceMetadata(t *testing.T) {
272272
if r.ApiServiceName == "" {
273273
t.Errorf("%s: `api_service_name` is required and not set", r.FileName)
274274
}
275-
// Allowlist google_biglake_iceberg_catalog as a pre-existing case. I believe
276-
// that's a mistake which should be corrected at some point in the future.
277-
if r.ApiVersion == "" && resourceName != "google_biglake_iceberg_catalog" {
275+
276+
// Allowlist google_biglake_iceberg resources as a pre-existing case.
277+
// This product doesn't have a version in the base_url because resources & IAM have different base_urls. (Resources include an `iceberg` prefix that isn't present for IAM URLs.)
278+
ignoredResources := []string{"google_biglake_iceberg_catalog", "google_biglake_iceberg_namespace"}
279+
if r.ApiVersion == "" && !slices.Contains(ignoredResources, resourceName) {
278280
t.Errorf("%s: `api_version` is required and not set", r.FileName)
279281
}
280282
if r.ApiResourceTypeKind == "" {
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package biglakeiceberg_test
5+
6+
import (
7+
"testing"
8+
9+
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
10+
"github.com/hashicorp/terraform-plugin-testing/plancheck"
11+
12+
"github.com/hashicorp/terraform-provider-google/google/acctest"
13+
)
14+
15+
func TestAccBiglakeIcebergIcebergNamespace_update(t *testing.T) {
16+
t.Parallel()
17+
18+
context := map[string]interface{}{
19+
"bucket_suffix": acctest.RandString(t, 10),
20+
}
21+
22+
acctest.VcrTest(t, resource.TestCase{
23+
PreCheck: func() { acctest.AccTestPreCheck(t) },
24+
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
25+
CheckDestroy: testAccCheckBiglakeIcebergIcebergNamespaceDestroyProducer(t),
26+
Steps: []resource.TestStep{
27+
{
28+
Config: testAccBiglakeIcebergIcebergNamespace_updateInitial(context),
29+
},
30+
{
31+
ResourceName: "google_biglake_iceberg_namespace.my_iceberg_namespace",
32+
ImportState: true,
33+
ImportStateVerify: true,
34+
ImportStateVerifyIgnore: []string{"catalog"},
35+
},
36+
{
37+
Config: testAccBiglakeIcebergIcebergNamespace_updateUpdated(context),
38+
ConfigPlanChecks: resource.ConfigPlanChecks{
39+
PreApply: []plancheck.PlanCheck{
40+
plancheck.ExpectResourceAction("google_biglake_iceberg_namespace.my_iceberg_namespace", plancheck.ResourceActionUpdate),
41+
},
42+
},
43+
},
44+
{
45+
ResourceName: "google_biglake_iceberg_namespace.my_iceberg_namespace",
46+
ImportState: true,
47+
ImportStateVerify: true,
48+
ImportStateVerifyIgnore: []string{"catalog"},
49+
},
50+
},
51+
})
52+
}
53+
54+
func testAccBiglakeIcebergIcebergNamespace_updateInitial(context map[string]interface{}) string {
55+
return acctest.Nprintf(`
56+
resource "google_storage_bucket" "bucket" {
57+
name = "my-bucket-%{bucket_suffix}"
58+
location = "us-central1"
59+
force_destroy = true
60+
uniform_bucket_level_access = true
61+
}
62+
63+
resource "google_biglake_iceberg_catalog" "catalog" {
64+
name = google_storage_bucket.bucket.name
65+
catalog_type = "CATALOG_TYPE_GCS_BUCKET"
66+
}
67+
68+
resource "google_biglake_iceberg_namespace" "my_iceberg_namespace" {
69+
catalog = google_biglake_iceberg_catalog.catalog.name
70+
namespace_id = "my-namespace-%{bucket_suffix}"
71+
properties = {
72+
key = "initial"
73+
}
74+
}
75+
`, context)
76+
}
77+
78+
func testAccBiglakeIcebergIcebergNamespace_updateUpdated(context map[string]interface{}) string {
79+
return acctest.Nprintf(`
80+
resource "google_storage_bucket" "bucket" {
81+
name = "my-bucket-%{bucket_suffix}"
82+
location = "us-central1"
83+
force_destroy = true
84+
uniform_bucket_level_access = true
85+
}
86+
87+
resource "google_biglake_iceberg_catalog" "catalog" {
88+
name = google_storage_bucket.bucket.name
89+
catalog_type = "CATALOG_TYPE_GCS_BUCKET"
90+
}
91+
92+
resource "google_biglake_iceberg_namespace" "my_iceberg_namespace" {
93+
catalog = google_biglake_iceberg_catalog.catalog.name
94+
namespace_id = "my-namespace-%{bucket_suffix}"
95+
properties = {
96+
key = "updated"
97+
}
98+
}
99+
`, context)
100+
}

0 commit comments

Comments
 (0)