Skip to content

Commit ebfbd8f

Browse files
authored
Add iceberg header on table calls (#17912)
1 parent de81034 commit ebfbd8f

7 files changed

Lines changed: 128 additions & 0 deletions

File tree

mmv1/products/biglakeiceberg/IcebergTable.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ custom_code:
4343
update_encoder: templates/terraform/encoders/biglake_iceberg_table.go.tmpl
4444
constants: templates/terraform/constants/biglake_iceberg_table.go.tmpl
4545
post_read: templates/terraform/post_read/biglake_iceberg_table.go.tmpl
46+
pre_create: templates/terraform/pre_create/biglake_iceberg_table.go.tmpl
47+
pre_read: templates/terraform/pre_read/biglake_iceberg_table.go.tmpl
48+
pre_update: templates/terraform/pre_update/biglake_iceberg_table.go.tmpl
49+
pre_delete: templates/terraform/pre_delete/biglake_iceberg_table.go.tmpl
4650
samples:
4751
- name: biglake_iceberg_table_basic
4852
primary_resource_id: my_iceberg_table

mmv1/templates/terraform/constants/biglake_iceberg_table.go.tmpl

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,34 @@ func expandIcebergTableSortOrderForCommit(v interface{}) map[string]interface{}
5151
"fields": fields,
5252
}
5353
}
54+
55+
{{- if not (contains $.ProductMetadata.Compiler "terraformgoogleconversion") }}
56+
// addIcebergTableAccessDelegationHeader sets the X-Iceberg-Access-Delegation
57+
// header that the Iceberg REST catalog requires for every table operation when
58+
// the parent catalog uses credential_mode = CREDENTIAL_MODE_VENDED_CREDENTIALS.
59+
// The credential mode is a property of the catalog, not the table, so it is
60+
// looked up at request time. Catalogs in the default end-user mode reject the
61+
// header, so it is only added when vending is actually enabled.
62+
func addIcebergTableAccessDelegationHeader(d *schema.ResourceData, config *transport_tpg.Config, billingProject, userAgent string, headers http.Header) error {
63+
url, err := tpgresource.ReplaceVars(d, config, "{{"{{"}}BiglakeIcebergBasePath{{"}}"}}iceberg/v1/restcatalog/extensions/projects/{{"{{"}}project{{"}}"}}/catalogs/{{"{{"}}catalog{{"}}"}}")
64+
if err != nil {
65+
return err
66+
}
67+
68+
catalog, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
69+
Config: config,
70+
Method: "GET",
71+
Project: billingProject,
72+
RawURL: url,
73+
UserAgent: userAgent,
74+
})
75+
if err != nil {
76+
return fmt.Errorf("Error reading IcebergCatalog to determine credential mode: %s", err)
77+
}
78+
79+
if mode, ok := catalog["credential-mode"].(string); ok && mode == "CREDENTIAL_MODE_VENDED_CREDENTIALS" {
80+
headers.Add("X-Iceberg-Access-Delegation", "vended-credentials")
81+
}
82+
return nil
83+
}
84+
{{- end }}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
if err := addIcebergTableAccessDelegationHeader(d, config, billingProject, userAgent, headers); err != nil {
2+
return err
3+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
if err := addIcebergTableAccessDelegationHeader(d, config, billingProject, userAgent, headers); err != nil {
2+
return err
3+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
if err := addIcebergTableAccessDelegationHeader(d, config, billingProject, userAgent, headers); err != nil {
2+
return err
3+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
if err := addIcebergTableAccessDelegationHeader(d, config, billingProject, userAgent, headers); err != nil {
2+
return err
3+
}

mmv1/third_party/terraform/services/biglakeiceberg/resource_biglake_iceberg_table_test.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,87 @@ func TestAccBiglakeIcebergIcebergTable_update(t *testing.T) {
5050
})
5151
}
5252

53+
// TestAccBiglakeIcebergIcebergTable_vendedCredentials verifies that a table can
54+
// be created in a catalog configured with
55+
// credential_mode = CREDENTIAL_MODE_VENDED_CREDENTIALS. In this mode every table
56+
// operation must send the X-Iceberg-Access-Delegation: vended-credentials header;
57+
// without it the REST catalog rejects the create call.
58+
func TestAccBiglakeIcebergIcebergTable_vendedCredentials(t *testing.T) {
59+
t.Parallel()
60+
61+
context := map[string]interface{}{
62+
"random_suffix": acctest.RandString(t, 10),
63+
}
64+
65+
acctest.VcrTest(t, resource.TestCase{
66+
PreCheck: func() { acctest.AccTestPreCheck(t) },
67+
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
68+
CheckDestroy: testAccCheckBiglakeIcebergIcebergTableDestroyProducer(t),
69+
Steps: []resource.TestStep{
70+
{
71+
Config: testAccBiglakeIcebergIcebergTable_vendedCredentials(context),
72+
},
73+
{
74+
ResourceName: "google_biglake_iceberg_table.my_iceberg_table",
75+
ImportState: true,
76+
ImportStateVerify: true,
77+
ImportStateVerifyIgnore: []string{"catalog", "namespace"},
78+
},
79+
},
80+
})
81+
}
82+
83+
func testAccBiglakeIcebergIcebergTable_vendedCredentials(context map[string]interface{}) string {
84+
return acctest.Nprintf(`
85+
resource "google_storage_bucket" "bucket" {
86+
name = "tf-test-my-bucket-%{random_suffix}"
87+
location = "us-central1"
88+
force_destroy = true
89+
uniform_bucket_level_access = true
90+
}
91+
92+
resource "google_biglake_iceberg_catalog" "catalog" {
93+
name = google_storage_bucket.bucket.name
94+
catalog_type = "CATALOG_TYPE_GCS_BUCKET"
95+
credential_mode = "CREDENTIAL_MODE_VENDED_CREDENTIALS"
96+
}
97+
98+
# Credential vending downscopes a generated service account to access the GCS
99+
# bucket, so it must be granted access before any table operation runs.
100+
resource "google_storage_bucket_iam_member" "cv_sa_storage_admin" {
101+
bucket = google_storage_bucket.bucket.name
102+
role = "roles/storage.admin"
103+
member = "serviceAccount:${google_biglake_iceberg_catalog.catalog.biglake_service_account}"
104+
}
105+
106+
resource "google_biglake_iceberg_namespace" "namespace" {
107+
catalog = google_biglake_iceberg_catalog.catalog.name
108+
namespace_id = "my_namespace_%{random_suffix}"
109+
}
110+
111+
resource "google_biglake_iceberg_table" "my_iceberg_table" {
112+
catalog = google_biglake_iceberg_catalog.catalog.name
113+
namespace = google_biglake_iceberg_namespace.namespace.namespace_id
114+
name = "tf-test-my_table_%{random_suffix}"
115+
schema {
116+
type = "struct"
117+
fields {
118+
id = 1
119+
name = "id"
120+
type = "long"
121+
required = true
122+
}
123+
}
124+
125+
properties = {
126+
key = "initial"
127+
}
128+
129+
depends_on = [google_storage_bucket_iam_member.cv_sa_storage_admin]
130+
}
131+
`, context)
132+
}
133+
53134
func testAccBiglakeIcebergIcebergTable_updateInitial(context map[string]interface{}) string {
54135
return acctest.Nprintf(`
55136
resource "google_storage_bucket" "bucket" {

0 commit comments

Comments
 (0)