Skip to content

Commit a5fb1d3

Browse files
KMS: add google_kms_crypto_key_versions data source (#11455)
[upstream:4e9aa4fd7d591cb006f340641b1bf0f95e0a91db] Signed-off-by: Modular Magician <[email protected]>
1 parent 43a3abe commit a5fb1d3

File tree

5 files changed

+414
-0
lines changed

5 files changed

+414
-0
lines changed

.changelog/11455.txt

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:new-datasource
2+
`google_kms_crypto_key_versions`
3+
```

google-beta/provider/provider_mmv1_resources.go

+1
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,7 @@ var handwrittenDatasources = map[string]*schema.Resource{
260260
"google_kms_crypto_key": kms.DataSourceGoogleKmsCryptoKey(),
261261
"google_kms_crypto_keys": kms.DataSourceGoogleKmsCryptoKeys(),
262262
"google_kms_crypto_key_version": kms.DataSourceGoogleKmsCryptoKeyVersion(),
263+
"google_kms_crypto_key_versions": kms.DataSourceGoogleKmsCryptoKeyVersions(),
263264
"google_kms_key_ring": kms.DataSourceGoogleKmsKeyRing(),
264265
"google_kms_key_rings": kms.DataSourceGoogleKmsKeyRings(),
265266
"google_kms_secret": kms.DataSourceGoogleKmsSecret(),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
// Copyright (c) HashiCorp, Inc.
4+
// SPDX-License-Identifier: MPL-2.0
5+
// Copyright (c) HashiCorp, Inc.
6+
// SPDX-License-Identifier: MPL-2.0
7+
package kms
8+
9+
import (
10+
"fmt"
11+
"log"
12+
"net/http"
13+
"regexp"
14+
15+
"github.com/hashicorp/terraform-provider-google-beta/google-beta/tpgresource"
16+
transport_tpg "github.com/hashicorp/terraform-provider-google-beta/google-beta/transport"
17+
18+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
19+
)
20+
21+
func DataSourceGoogleKmsCryptoKeyVersions() *schema.Resource {
22+
dsSchema := tpgresource.DatasourceSchemaFromResourceSchema(DataSourceGoogleKmsCryptoKeyVersion().Schema)
23+
24+
dsSchema["id"] = &schema.Schema{
25+
Type: schema.TypeString,
26+
Computed: true,
27+
}
28+
29+
return &schema.Resource{
30+
Read: dataSourceGoogleKmsCryptoKeyVersionsRead,
31+
Schema: map[string]*schema.Schema{
32+
"crypto_key": {
33+
Type: schema.TypeString,
34+
Required: true,
35+
ForceNew: true,
36+
},
37+
"versions": {
38+
Type: schema.TypeList,
39+
Computed: true,
40+
Description: "A list of all the retrieved cryptoKeyVersions from the provided crypto key",
41+
Elem: &schema.Resource{
42+
Schema: dsSchema,
43+
},
44+
},
45+
"filter": {
46+
Type: schema.TypeString,
47+
Optional: true,
48+
Description: `
49+
The filter argument is used to add a filter query parameter that limits which cryptoKeyVersions are retrieved by the data source: ?filter={{filter}}.
50+
Example values:
51+
52+
* "name:my-cryptokey-version-" will retrieve cryptoKeyVersions that contain "my-key-" anywhere in their name. Note: names take the form projects/{{project}}/locations/{{location}}/keyRings/{{keyRing}}/cryptoKeys/{{cryptoKey}}/cryptoKeyVersions/{{cryptoKeyVersion}}.
53+
* "name=projects/my-project/locations/global/keyRings/my-key-ring/cryptoKeys/my-key-1/cryptoKeyVersions/1" will only retrieve a key with that exact name.
54+
55+
[See the documentation about using filters](https://cloud.google.com/kms/docs/sorting-and-filtering)
56+
`,
57+
},
58+
"public_key": {
59+
Type: schema.TypeList,
60+
Computed: true,
61+
Elem: &schema.Resource{
62+
Schema: map[string]*schema.Schema{
63+
"algorithm": {
64+
Type: schema.TypeString,
65+
Computed: true,
66+
},
67+
"pem": {
68+
Type: schema.TypeString,
69+
Computed: true,
70+
},
71+
},
72+
},
73+
},
74+
},
75+
}
76+
}
77+
78+
func dataSourceGoogleKmsCryptoKeyVersionsRead(d *schema.ResourceData, meta interface{}) error {
79+
config := meta.(*transport_tpg.Config)
80+
81+
userAgent, err := tpgresource.GenerateUserAgentString(d, config.UserAgent)
82+
if err != nil {
83+
return err
84+
}
85+
86+
cryptoKeyId, err := ParseKmsCryptoKeyId(d.Get("crypto_key").(string), config)
87+
if err != nil {
88+
return err
89+
}
90+
91+
id := fmt.Sprintf("%s/cryptoKeyVersions", cryptoKeyId.CryptoKeyId())
92+
if filter, ok := d.GetOk("filter"); ok {
93+
id += "/filter=" + filter.(string)
94+
}
95+
d.SetId(id)
96+
97+
log.Printf("[DEBUG] Searching for cryptoKeyVersions in crypto key %s", cryptoKeyId.CryptoKeyId())
98+
versions, err := dataSourceKMSCryptoKeyVersionsList(d, meta, cryptoKeyId.CryptoKeyId(), userAgent)
99+
if err != nil {
100+
return err
101+
}
102+
103+
log.Printf("[DEBUG] Found %d cryptoKeyVersions in crypto key %s", len(versions), cryptoKeyId.CryptoKeyId())
104+
value, err := flattenKMSCryptoKeyVersionsList(d, config, versions, cryptoKeyId.CryptoKeyId())
105+
if err != nil {
106+
return fmt.Errorf("error flattening cryptoKeyVersions list: %s", err)
107+
}
108+
if err := d.Set("versions", value); err != nil {
109+
return fmt.Errorf("error setting versions: %s", err)
110+
}
111+
112+
if len(value) == 0 {
113+
return nil
114+
}
115+
116+
url, err := tpgresource.ReplaceVars(d, config, "{{KMSBasePath}}{{crypto_key}}")
117+
if err != nil {
118+
return err
119+
}
120+
121+
log.Printf("[DEBUG] Getting purpose of CryptoKey: %#v", url)
122+
res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
123+
Config: config,
124+
Method: "GET",
125+
Project: cryptoKeyId.KeyRingId.Project,
126+
RawURL: url,
127+
UserAgent: userAgent,
128+
})
129+
if err != nil {
130+
return transport_tpg.HandleDataSourceNotFoundError(err, d, fmt.Sprintf("KmsCryptoKey %q", d.Id()), url)
131+
}
132+
133+
if res["purpose"] == "ASYMMETRIC_SIGN" || res["purpose"] == "ASYMMETRIC_DECRYPT" {
134+
url, err = tpgresource.ReplaceVars(d, config, fmt.Sprintf("{{KMSBasePath}}{{crypto_key}}/cryptoKeyVersions/%d/publicKey", d.Get("versions.0.version")))
135+
if err != nil {
136+
return err
137+
}
138+
log.Printf("[DEBUG] Getting public key of CryptoKeyVersion: %#v", url)
139+
140+
res, err = transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
141+
Config: config,
142+
Method: "GET",
143+
Project: cryptoKeyId.KeyRingId.Project,
144+
RawURL: url,
145+
UserAgent: userAgent,
146+
Timeout: d.Timeout(schema.TimeoutRead),
147+
ErrorRetryPredicates: []transport_tpg.RetryErrorPredicateFunc{transport_tpg.IsCryptoKeyVersionsPendingGeneration},
148+
})
149+
150+
if err != nil {
151+
log.Printf("Error generating public key: %s", err)
152+
return err
153+
}
154+
155+
if err := d.Set("public_key", flattenKmsCryptoKeyVersionPublicKey(res, d)); err != nil {
156+
return fmt.Errorf("Error setting CryptoKeyVersion public key: %s", err)
157+
}
158+
}
159+
160+
return nil
161+
}
162+
163+
func dataSourceKMSCryptoKeyVersionsList(d *schema.ResourceData, meta interface{}, cryptoKeyId string, userAgent string) ([]interface{}, error) {
164+
config := meta.(*transport_tpg.Config)
165+
166+
url, err := tpgresource.ReplaceVars(d, config, "{{KMSBasePath}}{{crypto_key}}/cryptoKeyVersions?filter=state=ENABLED")
167+
if err != nil {
168+
return nil, err
169+
}
170+
171+
billingProject := ""
172+
173+
if parts := regexp.MustCompile(`projects\/([^\/]+)\/`).FindStringSubmatch(url); parts != nil {
174+
billingProject = parts[1]
175+
}
176+
177+
// err == nil indicates that the billing_project value was found
178+
if bp, err := tpgresource.GetBillingProject(d, config); err == nil {
179+
billingProject = bp
180+
}
181+
182+
// Always include the filter param, and optionally include the pageToken parameter for subsequent requests
183+
var params = make(map[string]string, 0)
184+
if filter, ok := d.GetOk("filter"); ok {
185+
log.Printf("[DEBUG] Search for cryptoKeyVersions in crypto key %s is using filter ?filter=%s", cryptoKeyId, filter.(string))
186+
params["filter"] = filter.(string)
187+
}
188+
189+
cryptoKeyVersions := make([]interface{}, 0)
190+
for {
191+
// Depending on previous iterations, params might contain a pageToken param
192+
url, err = transport_tpg.AddQueryParams(url, params)
193+
if err != nil {
194+
return nil, err
195+
}
196+
197+
headers := make(http.Header)
198+
res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
199+
Config: config,
200+
Method: "GET",
201+
Project: billingProject,
202+
RawURL: url,
203+
UserAgent: userAgent,
204+
Headers: headers,
205+
// ErrorRetryPredicates used to allow retrying if rate limits are hit when requesting multiple pages in a row
206+
ErrorRetryPredicates: []transport_tpg.RetryErrorPredicateFunc{transport_tpg.Is429RetryableQuotaError},
207+
})
208+
if err != nil {
209+
return nil, transport_tpg.HandleNotFoundError(err, d, fmt.Sprintf("KMSCryptoKeyVersions %q", d.Id()))
210+
}
211+
212+
if res == nil {
213+
// Decoding the object has resulted in it being gone. It may be marked deleted
214+
log.Printf("[DEBUG] Removing KMSCryptoKeyVersion because it no longer exists.")
215+
d.SetId("")
216+
return nil, nil
217+
}
218+
219+
// Store info from this page
220+
if v, ok := res["cryptoKeyVersions"].([]interface{}); ok {
221+
cryptoKeyVersions = append(cryptoKeyVersions, v...)
222+
}
223+
224+
// Handle pagination for next loop, or break loop
225+
v, ok := res["nextPageToken"]
226+
if ok {
227+
params["pageToken"] = v.(string)
228+
}
229+
if !ok {
230+
break
231+
}
232+
}
233+
234+
return cryptoKeyVersions, nil
235+
}
236+
237+
func flattenKMSCryptoKeyVersionsList(d *schema.ResourceData, meta interface{}, versionsList []interface{}, cryptoKeyId string) ([]interface{}, error) {
238+
var versions []interface{}
239+
for _, v := range versionsList {
240+
version := v.(map[string]interface{})
241+
242+
data := map[string]interface{}{}
243+
// The google_kms_crypto_key resource and dataset set
244+
// id as the value of name (projects/{{project}}/locations/{{location}}/keyRings/{{keyRing}}/cryptoKeys/{{name}})
245+
// and set name is set as just {{name}}.
246+
data["id"] = version["name"]
247+
data["name"] = flattenKmsCryptoKeyVersionName(version["name"], d)
248+
data["crypto_key"] = cryptoKeyId
249+
data["version"] = flattenKmsCryptoKeyVersionVersion(version["name"], d)
250+
251+
data["state"] = flattenKmsCryptoKeyVersionState(version["state"], d)
252+
data["protection_level"] = flattenKmsCryptoKeyVersionProtectionLevel(version["protectionLevel"], d)
253+
data["algorithm"] = flattenKmsCryptoKeyVersionAlgorithm(version["algorithm"], d)
254+
255+
versions = append(versions, data)
256+
}
257+
258+
return versions, nil
259+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
// Copyright (c) HashiCorp, Inc.
4+
// SPDX-License-Identifier: MPL-2.0
5+
package kms_test
6+
7+
import (
8+
"fmt"
9+
"regexp"
10+
"testing"
11+
12+
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
13+
"github.com/hashicorp/terraform-provider-google-beta/google-beta/acctest"
14+
)
15+
16+
func TestAccDataSourceGoogleKmsCryptoKeyVersions_basic(t *testing.T) {
17+
asymSignKey := acctest.BootstrapKMSKeyWithPurpose(t, "ASYMMETRIC_SIGN")
18+
19+
id := asymSignKey.CryptoKey.Name + "/cryptoKeyVersions"
20+
21+
randomString := acctest.RandString(t, 10)
22+
filterNameFindSharedCryptoKeyVersions := "name:tftest-shared-"
23+
filterNameFindsNoCryptoKeyVersions := fmt.Sprintf("name:%s", randomString)
24+
filterNameFindEnabledCryptoKeyVersions := "state:enabled"
25+
filterNameFindDisabledCryptoKeyVersions := "state:disabled"
26+
27+
findSharedCryptoKeyVersionsId := fmt.Sprintf("%s/filter=%s", id, filterNameFindSharedCryptoKeyVersions)
28+
findsNoCryptoKeyVersionsId := fmt.Sprintf("%s/filter=%s", id, filterNameFindsNoCryptoKeyVersions)
29+
findsEnabledCryptoKeyVersionsId := fmt.Sprintf("%s/filter=%s", id, filterNameFindEnabledCryptoKeyVersions)
30+
findsDisabledCryptoKeyVersionsId := fmt.Sprintf("%s/filter=%s", id, filterNameFindDisabledCryptoKeyVersions)
31+
32+
context := map[string]interface{}{
33+
"crypto_key": asymSignKey.CryptoKey.Name,
34+
"filter": "", // Can be overridden using 2nd argument to config funcs
35+
}
36+
37+
acctest.VcrTest(t, resource.TestCase{
38+
PreCheck: func() { acctest.AccTestPreCheck(t) },
39+
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
40+
Steps: []resource.TestStep{
41+
{
42+
Config: testAccDataSourceGoogleKmsCryptoKeyVersions_basic(context, ""),
43+
Check: resource.ComposeTestCheckFunc(
44+
resource.TestCheckResourceAttr("data.google_kms_crypto_key_versions.all_versions_in_key", "id", id),
45+
resource.TestCheckResourceAttr("data.google_kms_crypto_key_versions.all_versions_in_key", "crypto_key", asymSignKey.CryptoKey.Name),
46+
resource.TestMatchResourceAttr("data.google_kms_crypto_key_versions.all_versions_in_key", "versions.#", regexp.MustCompile("[1-9]+[0-9]*")),
47+
),
48+
},
49+
{
50+
Config: testAccDataSourceGoogleKmsCryptoKeyVersions_basic(context, fmt.Sprintf("filter = \"%s\"", filterNameFindSharedCryptoKeyVersions)),
51+
Check: resource.ComposeTestCheckFunc(
52+
// This filter should retrieve cryptoKeyVersions in the bootstrapped KMS crypto key used by the test
53+
resource.TestCheckResourceAttr("data.google_kms_crypto_key_versions.all_versions_in_key", "id", findSharedCryptoKeyVersionsId),
54+
resource.TestCheckResourceAttr("data.google_kms_crypto_key_versions.all_versions_in_key", "crypto_key", asymSignKey.CryptoKey.Name),
55+
resource.TestMatchResourceAttr("data.google_kms_crypto_key_versions.all_versions_in_key", "versions.#", regexp.MustCompile("[1-9]+[0-9]*")),
56+
),
57+
},
58+
{
59+
Config: testAccDataSourceGoogleKmsCryptoKeyVersions_basic(context, fmt.Sprintf("filter = \"%s\"", filterNameFindsNoCryptoKeyVersions)),
60+
Check: resource.ComposeTestCheckFunc(
61+
// This filter should retrieve no cryptoKeyVersions
62+
resource.TestCheckResourceAttr("data.google_kms_crypto_key_versions.all_versions_in_key", "id", findsNoCryptoKeyVersionsId),
63+
resource.TestCheckResourceAttr("data.google_kms_crypto_key_versions.all_versions_in_key", "crypto_key", asymSignKey.CryptoKey.Name),
64+
resource.TestCheckResourceAttr("data.google_kms_crypto_key_versions.all_versions_in_key", "versions.#", "0"),
65+
),
66+
},
67+
{
68+
Config: testAccDataSourceGoogleKmsCryptoKeyVersions_basic(context, fmt.Sprintf("filter = \"%s\"", filterNameFindEnabledCryptoKeyVersions)),
69+
Check: resource.ComposeTestCheckFunc(
70+
// This filter should retrieve versions that are enabled
71+
resource.TestCheckResourceAttr("data.google_kms_crypto_key_versions.all_versions_in_key", "id", findsEnabledCryptoKeyVersionsId),
72+
resource.TestCheckResourceAttr("data.google_kms_crypto_key_versions.all_versions_in_key", "crypto_key", asymSignKey.CryptoKey.Name),
73+
resource.TestMatchResourceAttr("data.google_kms_crypto_key_versions.all_versions_in_key", "versions.#", regexp.MustCompile("[1-9]+[0-9]*")),
74+
),
75+
},
76+
{
77+
Config: testAccDataSourceGoogleKmsCryptoKeyVersions_basic(context, fmt.Sprintf("filter = \"%s\"", filterNameFindDisabledCryptoKeyVersions)),
78+
Check: resource.ComposeTestCheckFunc(
79+
// This filter should retrieve versions that are disabled
80+
resource.TestCheckResourceAttr("data.google_kms_crypto_key_versions.all_versions_in_key", "id", findsDisabledCryptoKeyVersionsId),
81+
resource.TestCheckResourceAttr("data.google_kms_crypto_key_versions.all_versions_in_key", "crypto_key", asymSignKey.CryptoKey.Name),
82+
resource.TestCheckResourceAttr("data.google_kms_crypto_key_versions.all_versions_in_key", "versions.#", "0"),
83+
),
84+
},
85+
},
86+
})
87+
}
88+
89+
func testAccDataSourceGoogleKmsCryptoKeyVersions_basic(context map[string]interface{}, filter string) string {
90+
context["filter"] = filter
91+
92+
return acctest.Nprintf(`
93+
data "google_kms_crypto_key_versions" "all_versions_in_key" {
94+
crypto_key = "%{crypto_key}"
95+
%{filter}
96+
}
97+
`, context)
98+
}

0 commit comments

Comments
 (0)