Skip to content

Commit d624fe0

Browse files
KMS: add plural data source for resource_google_kms_key_ring (#11061) (#18611)
[upstream:b03e72f5f5f54fcfed067ed3e4fc8afa51a8f9e1] Signed-off-by: Modular Magician <magic-modules@google.com>
1 parent fe764b0 commit d624fe0

File tree

4 files changed

+251
-0
lines changed

4 files changed

+251
-0
lines changed

.changelog/11061.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:new-datasource
2+
`google_kms_key_rings`
3+
```

google/provider/provider_mmv1_resources.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,7 @@ var handwrittenDatasources = map[string]*schema.Resource{
235235
"google_kms_crypto_keys": kms.DataSourceGoogleKmsCryptoKeys(),
236236
"google_kms_crypto_key_version": kms.DataSourceGoogleKmsCryptoKeyVersion(),
237237
"google_kms_key_ring": kms.DataSourceGoogleKmsKeyRing(),
238+
"google_kms_key_rings": kms.DataSourceGoogleKmsKeyRings(),
238239
"google_kms_secret": kms.DataSourceGoogleKmsSecret(),
239240
"google_kms_secret_ciphertext": kms.DataSourceGoogleKmsSecretCiphertext(),
240241
"google_folder": resourcemanager.DataSourceGoogleFolder(),
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
package kms
4+
5+
import (
6+
"fmt"
7+
"log"
8+
9+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
10+
"github.com/hashicorp/terraform-provider-google/google/tpgresource"
11+
transport_tpg "github.com/hashicorp/terraform-provider-google/google/transport"
12+
)
13+
14+
func DataSourceGoogleKmsKeyRings() *schema.Resource {
15+
return &schema.Resource{
16+
Read: dataSourceGoogleKmsKeyRingsRead,
17+
Schema: map[string]*schema.Schema{
18+
"project": {
19+
Type: schema.TypeString,
20+
Optional: true,
21+
Description: `Project ID of the project.`,
22+
},
23+
"location": {
24+
Type: schema.TypeString,
25+
Required: true,
26+
Description: `The canonical id for the location. For example: "us-east1".`,
27+
},
28+
"filter": {
29+
Type: schema.TypeString,
30+
Optional: true,
31+
Description: `
32+
The filter argument is used to add a filter query parameter that limits which keys are retrieved by the data source: ?filter={{filter}}.
33+
Example values:
34+
35+
* "name:my-key-" will retrieve key rings that contain "my-key-" anywhere in their name. Note: names take the form projects/{{project}}/locations/{{location}}/keyRings/{{keyRing}}.
36+
* "name=projects/my-project/locations/global/keyRings/my-key-ring" will only retrieve a key ring with that exact name.
37+
38+
[See the documentation about using filters](https://cloud.google.com/kms/docs/sorting-and-filtering)
39+
`,
40+
},
41+
"key_rings": {
42+
Type: schema.TypeList,
43+
Computed: true,
44+
Description: "A list of all the retrieved key rings",
45+
Elem: &schema.Resource{
46+
// schema isn't used from resource_kms_key_ring due to having project and location fields which are empty when grabbed in a list.
47+
Schema: map[string]*schema.Schema{
48+
"id": {
49+
Type: schema.TypeString,
50+
Computed: true,
51+
},
52+
"name": {
53+
Type: schema.TypeString,
54+
Computed: true,
55+
},
56+
},
57+
},
58+
},
59+
},
60+
}
61+
}
62+
63+
func dataSourceGoogleKmsKeyRingsRead(d *schema.ResourceData, meta interface{}) error {
64+
config := meta.(*transport_tpg.Config)
65+
66+
id, err := tpgresource.ReplaceVars(d, config, "projects/{{project}}/locations/{{location}}/keyRings")
67+
if err != nil {
68+
return err
69+
}
70+
if filter, ok := d.GetOk("filter"); ok {
71+
id += "/filter=" + filter.(string)
72+
}
73+
d.SetId(id)
74+
75+
log.Printf("[DEBUG] Searching for keyrings")
76+
userAgent, err := tpgresource.GenerateUserAgentString(d, config.UserAgent)
77+
if err != nil {
78+
return err
79+
}
80+
81+
billingProject := ""
82+
83+
project, err := tpgresource.GetProject(d, config)
84+
if err != nil {
85+
return fmt.Errorf("Error fetching project for keyRings: %s", err)
86+
}
87+
billingProject = project
88+
89+
// err == nil indicates that the billing_project value was found
90+
if bp, err := tpgresource.GetBillingProject(d, config); err == nil {
91+
billingProject = bp
92+
}
93+
94+
var keyRings []interface{}
95+
96+
params := make(map[string]string)
97+
if filter, ok := d.GetOk("filter"); ok {
98+
log.Printf("[DEBUG] Search for key rings using filter ?filter=%s", filter.(string))
99+
params["filter"] = filter.(string)
100+
if err != nil {
101+
return err
102+
}
103+
}
104+
105+
url, err := tpgresource.ReplaceVars(d, config, "{{KMSBasePath}}projects/{{project}}/locations/{{location}}/keyRings")
106+
if err != nil {
107+
return err
108+
}
109+
110+
for {
111+
url, err = transport_tpg.AddQueryParams(url, params)
112+
if err != nil {
113+
return err
114+
}
115+
116+
res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
117+
Config: config,
118+
Method: "GET",
119+
Project: billingProject,
120+
RawURL: url,
121+
UserAgent: userAgent,
122+
ErrorRetryPredicates: []transport_tpg.RetryErrorPredicateFunc{transport_tpg.Is429RetryableQuotaError},
123+
})
124+
if err != nil {
125+
return fmt.Errorf("Error retrieving buckets: %s", err)
126+
}
127+
128+
if res["keyRings"] == nil {
129+
break
130+
}
131+
pageKeyRings, err := flattenKMSKeyRingsList(config, res["keyRings"])
132+
if err != nil {
133+
return fmt.Errorf("error flattening key rings list: %s", err)
134+
}
135+
keyRings = append(keyRings, pageKeyRings...)
136+
137+
pToken, ok := res["nextPageToken"]
138+
if ok && pToken != nil && pToken.(string) != "" {
139+
params["pageToken"] = pToken.(string)
140+
} else {
141+
break
142+
}
143+
}
144+
145+
log.Printf("[DEBUG] Found %d key rings", len(keyRings))
146+
if err := d.Set("key_rings", keyRings); err != nil {
147+
return fmt.Errorf("error setting key rings: %s", err)
148+
}
149+
150+
return nil
151+
}
152+
153+
// flattenKMSKeyRingsList flattens a list of key rings
154+
func flattenKMSKeyRingsList(config *transport_tpg.Config, keyRingsList interface{}) ([]interface{}, error) {
155+
var keyRings []interface{}
156+
for _, k := range keyRingsList.([]interface{}) {
157+
keyRing := k.(map[string]interface{})
158+
159+
parsedId, err := parseKmsKeyRingId(keyRing["name"].(string), config)
160+
if err != nil {
161+
return nil, err
162+
}
163+
164+
data := map[string]interface{}{}
165+
// The google_kms_key_rings resource and dataset set
166+
// id as the value of name (projects/{{project}}/locations/{{location}}/keyRings/{{name}})
167+
// and set name is set as just {{name}}.
168+
data["id"] = keyRing["name"]
169+
data["name"] = parsedId.Name
170+
171+
keyRings = append(keyRings, data)
172+
}
173+
174+
return keyRings, nil
175+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
package kms_test
4+
5+
import (
6+
"fmt"
7+
"regexp"
8+
"strings"
9+
"testing"
10+
11+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
12+
"github.com/hashicorp/terraform-provider-google/google/acctest"
13+
)
14+
15+
func TestAccDataSourceGoogleKmsKeyRings_basic(t *testing.T) {
16+
kms := acctest.BootstrapKMSKey(t)
17+
idPath := strings.Split(kms.KeyRing.Name, "/")
18+
location := idPath[3]
19+
randomString := acctest.RandString(t, 10)
20+
filterNameFindSharedKeys := "name:tftest-shared-"
21+
filterNameFindsNoKeys := fmt.Sprintf("name:%s", randomString)
22+
23+
keyRingsID := fmt.Sprintf("projects/%s/locations/%s/keyRings", idPath[1], location)
24+
findSharedKeysId := fmt.Sprintf("%s/filter=%s", keyRingsID, filterNameFindSharedKeys)
25+
findsNoKeysId := fmt.Sprintf("%s/filter=%s", keyRingsID, filterNameFindsNoKeys)
26+
27+
context := map[string]interface{}{
28+
"filter": "", // Can be overridden using 2nd argument to config funcs
29+
"location": location,
30+
}
31+
32+
acctest.VcrTest(t, resource.TestCase{
33+
PreCheck: func() { acctest.AccTestPreCheck(t) },
34+
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
35+
Steps: []resource.TestStep{
36+
{
37+
Config: testAccDataSourceGoogleKmsKeyRings_basic(context, ""),
38+
Check: resource.ComposeTestCheckFunc(
39+
resource.TestCheckResourceAttr("data.google_kms_key_rings.all_key_rings", "id", keyRingsID),
40+
resource.TestMatchResourceAttr("data.google_kms_key_rings.all_key_rings", "key_rings.#", regexp.MustCompile("[1-9]+[0-9]*")),
41+
),
42+
},
43+
{
44+
Config: testAccDataSourceGoogleKmsKeyRings_basic(context, fmt.Sprintf("filter = \"%s\"", filterNameFindSharedKeys)),
45+
Check: resource.ComposeTestCheckFunc(
46+
// This filter should retrieve the bootstrapped KMS key rings used by the test
47+
resource.TestCheckResourceAttr("data.google_kms_key_rings.all_key_rings", "id", findSharedKeysId),
48+
resource.TestMatchResourceAttr("data.google_kms_key_rings.all_key_rings", "key_rings.#", regexp.MustCompile("[1-9]+[0-9]*")),
49+
),
50+
},
51+
{
52+
Config: testAccDataSourceGoogleKmsKeyRings_basic(context, fmt.Sprintf("filter = \"%s\"", filterNameFindsNoKeys)),
53+
Check: resource.ComposeTestCheckFunc(
54+
// This filter should retrieve no keys
55+
resource.TestCheckResourceAttr("data.google_kms_key_rings.all_key_rings", "id", findsNoKeysId),
56+
resource.TestCheckResourceAttr("data.google_kms_key_rings.all_key_rings", "key_rings.#", "0"),
57+
),
58+
},
59+
},
60+
})
61+
}
62+
63+
func testAccDataSourceGoogleKmsKeyRings_basic(context map[string]interface{}, filter string) string {
64+
context["filter"] = filter
65+
66+
return acctest.Nprintf(`
67+
data "google_kms_key_rings" "all_key_rings" {
68+
location = "%{location}"
69+
%{filter}
70+
}
71+
`, context)
72+
}

0 commit comments

Comments
 (0)