Skip to content

Commit e055e09

Browse files
authored
Add data source for keyhandle list (#12708)
1 parent 4e0eecd commit e055e09

File tree

4 files changed

+298
-0
lines changed

4 files changed

+298
-0
lines changed

mmv1/third_party/terraform/provider/provider_mmv1_resources.go.tmpl

+1
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ var handwrittenDatasources = map[string]*schema.Resource{
150150
{{- if ne $.TargetVersionName "ga" }}
151151
"google_kms_key_handle": kms.DataSourceGoogleKmsKeyHandle(),
152152
"google_kms_autokey_config": kms.DataSourceGoogleKmsAutokeyConfig(),
153+
"google_kms_key_handles": kms.DataSourceGoogleKmsKeyHandles(),
153154
{{- end }}
154155
"google_kms_secret": kms.DataSourceGoogleKmsSecret(),
155156
"google_kms_secret_ciphertext": kms.DataSourceGoogleKmsSecretCiphertext(),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
package kms
4+
5+
import (
6+
"fmt"
7+
"log"
8+
"strings"
9+
10+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
11+
"github.com/hashicorp/terraform-provider-google/google/tpgresource"
12+
transport_tpg "github.com/hashicorp/terraform-provider-google/google/transport"
13+
)
14+
15+
func DataSourceGoogleKmsKeyHandles() *schema.Resource {
16+
return &schema.Resource{
17+
Read: dataSourceGoogleKmsKeyHandlesRead,
18+
Schema: map[string]*schema.Schema{
19+
"project": {
20+
Type: schema.TypeString,
21+
Optional: true,
22+
Description: `Project ID of the project.`,
23+
},
24+
"location": {
25+
Type: schema.TypeString,
26+
Required: true,
27+
Description: `The canonical id for the location. For example: "us-east1".`,
28+
},
29+
"resource_type_selector": {
30+
Type: schema.TypeString,
31+
Required: true,
32+
Description: `
33+
The resource_type_selector argument is used to add a filter query parameter that limits which key handles are retrieved by the data source: ?filter=resource_type_selector="{{resource_type_selector}}".
34+
Example values:
35+
* resource_type_selector="{SERVICE}.googleapis.com/{TYPE}".
36+
[See the documentation about using filters](https://cloud.google.com/kms/docs/reference/rest/v1/projects.locations.keyHandles/list)
37+
`,
38+
},
39+
"key_handles": {
40+
Type: schema.TypeList,
41+
Computed: true,
42+
Description: "A list of all the retrieved key handles",
43+
Elem: &schema.Resource{
44+
Schema: map[string]*schema.Schema{
45+
"name": {
46+
Type: schema.TypeString,
47+
Computed: true,
48+
},
49+
"kms_key": {
50+
Type: schema.TypeString,
51+
Computed: true,
52+
},
53+
"resource_type_selector": {
54+
Type: schema.TypeString,
55+
Computed: true,
56+
},
57+
},
58+
},
59+
},
60+
},
61+
}
62+
63+
}
64+
65+
func dataSourceGoogleKmsKeyHandlesRead(d *schema.ResourceData, meta interface{}) error {
66+
config := meta.(*transport_tpg.Config)
67+
project, err := tpgresource.GetProject(d, config)
68+
if err != nil {
69+
return err
70+
}
71+
resourceTypeSelector := ""
72+
if fl, ok := d.GetOk("resource_type_selector"); ok {
73+
resourceTypeSelector = strings.Replace(fl.(string), "\"", "%22", -1)
74+
}
75+
76+
billingProject := project
77+
if bp, err := tpgresource.GetBillingProject(d, config); err == nil {
78+
billingProject = bp
79+
}
80+
81+
url, err := tpgresource.ReplaceVars(d, config, "{{KMSBasePath}}projects/{{project}}/locations/{{location}}/keyHandles")
82+
if err != nil {
83+
return err
84+
}
85+
userAgent, err := tpgresource.GenerateUserAgentString(d, config.UserAgent)
86+
params := make(map[string]string)
87+
var keyHandles []interface{}
88+
for {
89+
newUrl, err := addQueryParams(url, resourceTypeSelector, params)
90+
if err != nil {
91+
return err
92+
}
93+
res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
94+
Config: config,
95+
Method: "GET",
96+
Project: billingProject,
97+
RawURL: newUrl,
98+
UserAgent: userAgent,
99+
ErrorRetryPredicates: []transport_tpg.RetryErrorPredicateFunc{transport_tpg.Is429RetryableQuotaError},
100+
})
101+
if err != nil {
102+
return fmt.Errorf("Error retrieving keyhandles: %s", err)
103+
}
104+
105+
if res["keyHandles"] == nil {
106+
break
107+
}
108+
pageKeyHandles, err := flattenKMSKeyHandlesList(config, res["keyHandles"])
109+
if err != nil {
110+
return fmt.Errorf("error flattening key handle list: %s", err)
111+
}
112+
keyHandles = append(keyHandles, pageKeyHandles...)
113+
114+
pToken, ok := res["nextPageToken"]
115+
if ok && pToken != nil && pToken.(string) != "" {
116+
params["pageToken"] = pToken.(string)
117+
} else {
118+
break
119+
}
120+
}
121+
log.Printf("[DEBUG] Found %d key handles", len(keyHandles))
122+
if err := d.Set("key_handles", keyHandles); err != nil {
123+
return fmt.Errorf("error setting key handles: %s", err)
124+
}
125+
d.SetId(fmt.Sprintf("projects/%s/locations/%s/keyHandles?filter=resource_type_selector=%s", project, d.Get("location"), resourceTypeSelector))
126+
return nil
127+
}
128+
129+
// transport_tpg.AddQueryParams() encodes the filter=resource_type_selector="value" into
130+
// filter=resource_type_selector%3D%22value%22
131+
// The encoding of '=' into %3D is currently causing issue with ListKeyHandle api.
132+
// To to handle this case currently, as part of this function,
133+
// we are manually adding filter as a query param to the url
134+
func addQueryParams(url string, resourceTypeSelector string, params map[string]string) (string, error) {
135+
quoteEncoding := "%22"
136+
if len(params) == 0 {
137+
return fmt.Sprintf("%s?filter=resource_type_selector=%s%s%s", url, quoteEncoding, resourceTypeSelector, quoteEncoding), nil
138+
} else {
139+
url, err := transport_tpg.AddQueryParams(url, params)
140+
if err != nil {
141+
return "", nil
142+
}
143+
return fmt.Sprintf("%s&filter=resource_type_selector=%s%s%s", url, quoteEncoding, resourceTypeSelector, quoteEncoding), nil
144+
}
145+
}
146+
147+
// flattenKMSKeyHandlesList flattens a list of key handles
148+
func flattenKMSKeyHandlesList(config *transport_tpg.Config, keyHandlesList interface{}) ([]interface{}, error) {
149+
var keyHandles []interface{}
150+
for _, k := range keyHandlesList.([]interface{}) {
151+
keyHandle := k.(map[string]interface{})
152+
153+
data := map[string]interface{}{}
154+
data["name"] = keyHandle["name"]
155+
data["kms_key"] = keyHandle["kmsKey"]
156+
data["resource_type_selector"] = keyHandle["resourceTypeSelector"]
157+
158+
keyHandles = append(keyHandles, data)
159+
}
160+
161+
return keyHandles, nil
162+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
package kms_test
4+
5+
{{ if ne $.TargetVersionName `ga` -}}
6+
7+
import (
8+
"errors"
9+
"fmt"
10+
"strconv"
11+
"strings"
12+
"testing"
13+
14+
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
15+
"github.com/hashicorp/terraform-plugin-testing/terraform"
16+
"github.com/hashicorp/terraform-provider-google/google/acctest"
17+
)
18+
19+
func TestAccDataSourceGoogleKmsKeyHandles_basic(t *testing.T) {
20+
kmsAutokey := acctest.BootstrapKMSAutokeyKeyHandle(t)
21+
keyParts := strings.Split(kmsAutokey.KeyHandle.Name, "/")
22+
project := keyParts[1]
23+
location := keyParts[3]
24+
diskFilter := fmt.Sprintf("compute.googleapis.com/Disk")
25+
26+
acctest.VcrTest(t, resource.TestCase{
27+
PreCheck: func() { acctest.AccTestPreCheck(t) },
28+
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
29+
Steps: []resource.TestStep{
30+
{
31+
Config: testAccDataSourceGoogleKmsKeyHandles_basic(project, location, diskFilter),
32+
Check: resource.ComposeTestCheckFunc(
33+
validateKeyHandleName(
34+
"data.google_kms_key_handles.mykeyhandles", kmsAutokey.KeyHandle.Name,
35+
),
36+
),
37+
},
38+
},
39+
})
40+
}
41+
func validateKeyHandleName(dataSourceName string, expectedKeyHandleName string) func(*terraform.State) error {
42+
return func(s *terraform.State) error {
43+
ds, ok := s.RootModule().Resources[dataSourceName]
44+
if !ok {
45+
return fmt.Errorf("can't find %s in state", dataSourceName)
46+
}
47+
48+
var dsAttr map[string]string
49+
dsAttr = ds.Primary.Attributes
50+
51+
totalKeyHandles, err := strconv.Atoi(dsAttr["key_handles.#"])
52+
if err != nil {
53+
return errors.New("Couldn't convert length of key_handles list to integer")
54+
}
55+
if totalKeyHandles != 1 {
56+
return errors.New(fmt.Sprintf("want 1 keyhandle, found %d", totalKeyHandles))
57+
}
58+
actualKeyHandleName := dsAttr["key_handles.0.name"]
59+
if actualKeyHandleName != expectedKeyHandleName {
60+
return errors.New(fmt.Sprintf("want keyhandle name %s, got: %s", expectedKeyHandleName, actualKeyHandleName))
61+
}
62+
return nil
63+
}
64+
}
65+
func testAccDataSourceGoogleKmsKeyHandles_basic(project string, location string, filter string) string {
66+
str := fmt.Sprintf(`
67+
data "google_kms_key_handles" "mykeyhandles" {
68+
project = "%s"
69+
location = "%s"
70+
resource_type_selector = "%s"
71+
}
72+
`, project, location, filter)
73+
return str
74+
}
75+
76+
{{ end }}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
---
2+
subcategory: "Cloud Key Management Service"
3+
description: |-
4+
Provides access to KMS key handle data with Google Cloud KMS.
5+
---
6+
7+
# google_kms_key_handles
8+
9+
Provides access to Google Cloud Platform KMS KeyHandle. A key handle is a Cloud KMS resource that helps you safely span the separation of duties to create new Cloud KMS keys for CMEK using Autokey.
10+
11+
~> **Warning:** This resource is in beta, and should be used with the terraform-provider-google-beta provider.
12+
See [Provider Versions](https://terraform.io/docs/providers/google/guides/provider_versions.html) for more details on beta resources.
13+
14+
For more information see
15+
[the official documentation](https://cloud.google.com/kms/docs/resource-hierarchy#key_handles)
16+
and
17+
[API](https://cloud.google.com/kms/docs/reference/rest/v1/projects.locations.keyHandles/list).
18+
19+
20+
## Example Usage
21+
22+
```hcl
23+
data "google_kms_key_handles" "my_key_handles" {
24+
project = "resource-project-id"
25+
location = "us-central1"
26+
resource_type_selector = "storage.googleapis.com/Bucket"
27+
}
28+
```
29+
30+
## Argument Reference
31+
32+
The following arguments are supported:
33+
34+
* `location` - (Required) The Google Cloud Platform location for the KeyHandle.
35+
A full list of valid locations can be found by running `gcloud kms locations list`.
36+
37+
* `resource_type_selector` - (Required) The resource type by which to filter KeyHandle e.g. {SERVICE}.googleapis.com/{TYPE}. See documentation for supported resource types.
38+
39+
- - -
40+
41+
* `project` - (Optional) The project in which the resource belongs. If it
42+
is not provided, the provider project is used.
43+
44+
## Attributes Reference
45+
46+
In addition to the arguments listed above, the following computed attributes are
47+
exported:
48+
49+
* `name` - The name of the KeyHandle. Its format is `projects/{projectId}/locations/{location}/keyHandles/{keyHandleName}`.
50+
51+
* `kms_key` - The identifier of the KMS Key created for the KeyHandle. Its format is `projects/{projectId}/locations/{location}/keyRings/{keyRingName}/cryptoKeys/{cryptoKeyName}`.
52+
53+
* `location` - The location of the KMS Key and KeyHandle.
54+
55+
* `project` - The identifier of the project where KMS KeyHandle is created.
56+
57+
* `resource_type_selector` - Indicates the resource type that the resulting CryptoKey is meant to protect, e.g. {SERVICE}.googleapis.com/{TYPE}. See documentation for supported resource types.
58+
59+

0 commit comments

Comments
 (0)