Skip to content

Commit a30b07e

Browse files
committed
Add workload identity pool namespace.
1 parent 1ffca3b commit a30b07e

8 files changed

+456
-0
lines changed
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
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: 'WorkloadIdentityPoolNamespace'
16+
description: |
17+
Represents a namespace for a workload identity pool. Namespaces are used to segment identities
18+
within the pool.
19+
references:
20+
guides:
21+
'Configure managed workload identity authentication for Compute Engine': 'https://cloud.google.com/iam/docs/create-managed-workload-identities'
22+
'Configure managed workload identity authentication for GKE': 'https://cloud.google.com/iam/docs/create-managed-workload-identities-gke'
23+
api: 'https://cloud.google.com/iam/docs/reference/rest/v1/projects.locations.workloadIdentityPools.namespaces'
24+
min_version: beta
25+
base_url: 'projects/{{project}}/locations/global/workloadIdentityPools/{{workload_identity_pool_id}}/namespaces'
26+
self_link: 'projects/{{project}}/locations/global/workloadIdentityPools/{{workload_identity_pool_id}}/namespaces/{{workload_identity_pool_namespace_id}}'
27+
create_url: 'projects/{{project}}/locations/global/workloadIdentityPools/{{workload_identity_pool_id}}/namespaces?workloadIdentityPoolNamespaceId={{workload_identity_pool_namespace_id}}'
28+
create_verb: 'POST'
29+
update_url: 'projects/{{project}}/locations/global/workloadIdentityPools/{{workload_identity_pool_id}}/namespaces/{{workload_identity_pool_namespace_id}}'
30+
update_verb: 'PATCH'
31+
update_mask: true
32+
delete_url: 'projects/{{project}}/locations/global/workloadIdentityPools/{{workload_identity_pool_id}}/namespaces/{{workload_identity_pool_namespace_id}}'
33+
delete_verb: 'DELETE'
34+
import_format:
35+
- 'projects/{{project}}/locations/global/workloadIdentityPools/{{workload_identity_pool_id}}/namespaces/{{workload_identity_pool_namespace_id}}'
36+
timeouts:
37+
insert_minutes: 20
38+
update_minutes: 20
39+
delete_minutes: 20
40+
autogen_async: true
41+
async:
42+
actions: ['create', 'delete', 'update']
43+
type: 'OpAsync'
44+
operation:
45+
base_url: '{{op_id}}'
46+
result:
47+
resource_inside_response: false
48+
custom_code:
49+
constants: 'templates/terraform/constants/iam_workload_identity_pool_namespace.go.tmpl'
50+
decoder: 'templates/terraform/decoders/treat_deleted_state_as_gone.go.tmpl'
51+
test_check_destroy: 'templates/terraform/custom_check_destroy/iam_workload_identity_pool_namespace.go.tmpl'
52+
examples:
53+
- name: 'iam_workload_identity_pool_namespace_basic'
54+
primary_resource_id: 'example'
55+
vars:
56+
workload_identity_pool_id: 'example-pool'
57+
workload_identity_pool_namespace_id: 'example-nmspc'
58+
- name: 'iam_workload_identity_pool_namespace_full'
59+
primary_resource_id: 'example'
60+
vars:
61+
workload_identity_pool_id: 'example-pool'
62+
workload_identity_pool_namespace_id: 'example-nmspc'
63+
parameters:
64+
- name: 'workload_identity_pool_id'
65+
type: String
66+
required: true
67+
immutable: true
68+
url_param_only: true
69+
description: |
70+
The ID to use for the pool, which becomes the final component of the resource name. This
71+
value should be 4-32 characters, and may contain the characters [a-z0-9-]. The prefix
72+
`gcp-` is reserved for use by Google, and may not be specified.
73+
- name: 'workload_identity_pool_namespace_id'
74+
type: String
75+
required: true
76+
immutable: true
77+
url_param_only: true
78+
description: |
79+
The ID to use for the namespace. This value must:
80+
* contain at most 63 characters
81+
* contain only lowercase alphanumeric characters or `-`
82+
* start with an alphanumeric character
83+
* end with an alphanumeric character
84+
85+
86+
The prefix `gcp-` will be reserved for future uses.
87+
validation:
88+
function: 'ValidateWorkloadIdentityPoolNamespaceId'
89+
properties:
90+
- name: 'name'
91+
type: String
92+
description: |
93+
The resource name of the namespace as
94+
`projects/{project_number}/locations/global/workloadIdentityPools/{workload_identity_pool_id}/namespaces/{workload_identity_pool_namespace_id}`.
95+
output: true
96+
- name: 'description'
97+
type: String
98+
description: |
99+
A description of the namespace. Cannot exceed 256 characters.
100+
validation:
101+
function: 'ValidateWorkloadIdentityPoolNamespaceDescription'
102+
- name: 'state'
103+
type: Enum
104+
description: |
105+
The current state of the namespace.
106+
* `STATE_UNSPECIFIED`: State unspecified.
107+
* `ACTIVE`: The namespace is active.
108+
* `DELETED`: The namespace is soft-deleted. Soft-deleted namespaces are permanently deleted
109+
after approximately 30 days. You can restore a soft-deleted namespace using
110+
UndeleteWorkloadIdentityPoolNamespace. You cannot reuse the ID of a soft-deleted namespace
111+
until it is permanently deleted.
112+
output: true
113+
enum_values:
114+
- 'STATE_UNSPECIFIED'
115+
- 'ACTIVE'
116+
- 'DELETED'
117+
- name: 'disabled'
118+
type: Boolean
119+
description: |
120+
Whether the namespace is disabled. If disabled, credentials may no longer be issued for
121+
identities within this namespace, however existing credentials will still be accepted until
122+
they expire.
123+
- name: 'ownerService'
124+
type: NestedObject
125+
description: |
126+
Defines the owner that is allowed to mutate this resource. If present, this resource can only
127+
be mutated by the owner.
128+
output: true
129+
properties:
130+
- name: 'principalSubject'
131+
type: String
132+
description: |
133+
The service agent principal subject, e.g.
134+
`serviceAccount:[email protected]`.
135+
required: true
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
const workloadIdentityPoolNamespaceIdRegexp = `^[0-9a-z-]+$`
2+
3+
func ValidateWorkloadIdentityPoolNamespaceId(v interface{}, k string) (ws []string, errors []error) {
4+
value := v.(string)
5+
6+
if !regexp.MustCompile(workloadIdentityPoolNamespaceIdRegexp).MatchString(value) {
7+
errors = append(errors, fmt.Errorf(
8+
"%q must contain only lowercase letters (a-z), numbers (0-9), or dashes (-)", k))
9+
}
10+
11+
if len(value) < 2 {
12+
errors = append(errors, fmt.Errorf(
13+
"%q cannot be less than 2 characters", k))
14+
return
15+
}
16+
17+
if len(value) > 63 {
18+
errors = append(errors, fmt.Errorf(
19+
"%q cannot be greater than 63 characters", k))
20+
}
21+
22+
isLowerAlphaNumeric := func(r byte) bool {
23+
return (r >= '0' && r <= '9') || (r >= 'a' && r <= 'z')
24+
}
25+
26+
firstChar := value[0]
27+
if !isLowerAlphaNumeric(firstChar) {
28+
errors = append(errors, fmt.Errorf(
29+
"%q must start with an alphanumeric character", k))
30+
}
31+
32+
lastChar := value[len(value) - 1]
33+
if !isLowerAlphaNumeric(lastChar) {
34+
errors = append(errors, fmt.Errorf(
35+
"%q must end with an alphanumeric character", k))
36+
}
37+
38+
if strings.HasPrefix(value, "gcp-") {
39+
errors = append(errors, fmt.Errorf(
40+
"%q (%q) can not start with \"gcp-\"", k, value))
41+
}
42+
43+
return
44+
}
45+
46+
func ValidateWorkloadIdentityPoolNamespaceDescription(v interface{}, k string) (ws []string, errors []error) {
47+
value := v.(string)
48+
49+
if len(value) > 256 {
50+
errors = append(errors, fmt.Errorf(
51+
"%q cannot be greater than 256 characters", k))
52+
}
53+
54+
return
55+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
config := acctest.GoogleProviderConfig(t)
2+
3+
url, err := tpgresource.ReplaceVarsForTest(config, rs, "{{"{{"}}IAMBetaBasePath{{"}}"}}projects/{{"{{"}}project{{"}}"}}/locations/global/workloadIdentityPools/{{"{{"}}workload_identity_pool_id{{"}}"}}/namespaces/{{"{{"}}workload_identity_pool_namespace_id{{"}}"}}")
4+
if err != nil {
5+
return err
6+
}
7+
8+
res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
9+
Config: config,
10+
Method: "GET",
11+
RawURL: url,
12+
UserAgent: config.UserAgent,
13+
})
14+
if err != nil {
15+
return nil
16+
}
17+
18+
if v := res["state"]; v == "DELETED" {
19+
return nil
20+
}
21+
22+
return fmt.Errorf("IAMBetaWorkloadIdentityPoolNamespace still exists at %s", url)
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
resource "google_iam_workload_identity_pool" "pool" {
2+
provider = google-beta
3+
4+
workload_identity_pool_id = "{{index $.Vars "workload_identity_pool_id"}}"
5+
mode = "TRUST_DOMAIN"
6+
}
7+
8+
resource "google_iam_workload_identity_pool_namespace" "{{$.PrimaryResourceId}}" {
9+
provider = google-beta
10+
11+
workload_identity_pool_id = google_iam_workload_identity_pool.pool.workload_identity_pool_id
12+
workload_identity_pool_namespace_id = "{{index $.Vars "workload_identity_pool_namespace_id"}}"
13+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
resource "google_iam_workload_identity_pool" "pool" {
2+
provider = google-beta
3+
4+
workload_identity_pool_id = "{{index $.Vars "workload_identity_pool_id"}}"
5+
mode = "TRUST_DOMAIN"
6+
}
7+
8+
resource "google_iam_workload_identity_pool_namespace" "{{$.PrimaryResourceId}}" {
9+
provider = google-beta
10+
11+
workload_identity_pool_id = google_iam_workload_identity_pool.pool.workload_identity_pool_id
12+
workload_identity_pool_namespace_id = "{{index $.Vars "workload_identity_pool_namespace_id"}}"
13+
description = "Example Namespace in a Workload Identity Pool"
14+
disabled = false
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{{- if ne $.TargetVersionName "ga" -}}
2+
package iambeta_test
3+
4+
import (
5+
"strings"
6+
"testing"
7+
8+
"github.com/hashicorp/terraform-provider-google/google/services/iambeta"
9+
"github.com/hashicorp/terraform-provider-google/google/verify"
10+
)
11+
12+
func TestValidateWorkloadIdentityPoolNamespaceDescription(t *testing.T) {
13+
x := []verify.StringValidationTestCase{
14+
// No errors
15+
{TestName: "basic", Value: "foobar"},
16+
{TestName: "with numbers", Value: "foobar123"},
17+
{TestName: "short", Value: "foos"},
18+
{TestName: "long", Value: "12345678901234567890123456789012"},
19+
{TestName: "has a hyphen", Value: "foo-bar"},
20+
21+
// With errors
22+
{TestName: "too long", Value: strings.Repeat("f", 257), ExpectError: true},
23+
}
24+
25+
es := verify.TestStringValidationCases(x, iambeta.ValidateWorkloadIdentityPoolNamespaceDescription)
26+
if len(es) > 0 {
27+
t.Errorf("Failed to validate WorkloadIdentityPool Namespace descriptions: %v", es)
28+
}
29+
}
30+
{{- end -}}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
{{- if ne $.TargetVersionName "ga" -}}
2+
package iambeta_test
3+
4+
import (
5+
"strings"
6+
"testing"
7+
8+
"github.com/hashicorp/terraform-provider-google/google/services/iambeta"
9+
"github.com/hashicorp/terraform-provider-google/google/verify"
10+
)
11+
12+
func TestValidateWorkloadIdentityPoolNamespaceId(t *testing.T) {
13+
x := []verify.StringValidationTestCase{
14+
// No errors
15+
{TestName: "basic", Value: "foobar"},
16+
{TestName: "with numbers", Value: "foobar123"},
17+
{TestName: "short", Value: "foos"},
18+
{TestName: "long", Value: "12345678901234567890123456789012"},
19+
{TestName: "has a hyphen", Value: "foo-bar"},
20+
21+
// With errors
22+
{TestName: "empty", Value: "", ExpectError: true},
23+
{TestName: "starts with a gcp-", Value: "gcp-foobar", ExpectError: true},
24+
{TestName: "with uppercase", Value: "fooBar", ExpectError: true},
25+
{TestName: "has an slash", Value: "foo/bar", ExpectError: true},
26+
{TestName: "has an backslash", Value: "foo\bar", ExpectError: true},
27+
{TestName: "too short", Value: "f", ExpectError: true},
28+
{TestName: "too long", Value: strings.Repeat("f", 64), ExpectError: true},
29+
{TestName: "starts with non-alphanumeric", Value: "-foobar", ExpectError: true},
30+
{TestName: "ends with non-alphanumeric", Value: "foobar-", ExpectError: true},
31+
}
32+
33+
es := verify.TestStringValidationCases(x, iambeta.ValidateWorkloadIdentityPoolNamespaceId)
34+
if len(es) > 0 {
35+
t.Errorf("Failed to validate WorkloadIdentityPoolNamespace names: %v", es)
36+
}
37+
}
38+
{{- end -}}

0 commit comments

Comments
 (0)