Skip to content

Commit 695ab82

Browse files
melinathanoopkverma-google
authored andcommitted
Iam bootstrapping generation (GoogleCloudPlatform#12797)
1 parent 4c08543 commit 695ab82

File tree

10 files changed

+172
-65
lines changed

10 files changed

+172
-65
lines changed

docs/content/reference/resource.md

+65
Original file line numberDiff line numberDiff line change
@@ -350,3 +350,68 @@ properties:
350350
- name: 'fieldOne'
351351
type: String
352352
```
353+
354+
## Examples
355+
356+
### `examples`
357+
358+
A list of configurations that are used to generate documentation and tests. Each example supports the following common
359+
attributes – for a full reference, see
360+
[examples.go ↗](https://github.com/GoogleCloudPlatform/magic-modules/blob/main/mmv1/api/resource/examples.go):
361+
362+
- `name`: snake_case name of the example. This corresponds to the configuration file in
363+
[mmv1/templates/terraform/examples](https://github.com/GoogleCloudPlatform/magic-modules/tree/main/mmv1/templates/terraform/examples) (excluding the `.go.tmpl` suffix) and is used to generate the test name and the documentation header.
364+
- `primary_resource_id`: The id of the resource under test. This is used by tests to automatically run additional checks.
365+
Configuration files should reference this to avoid getting out of sync. For example:
366+
`resource "google_compute_address" ""{{$.PrimaryResourceId}}" {`
367+
- `bootstrap_iam`: specify member/role pairs that should always exist. `{project_number}` will be replaced with the
368+
default project's project number. This avoids race conditions when modifying the IAM permissions for the default test project.
369+
Permissions attached to resources created _in_ a test should instead be provisioned with standard terraform resources.
370+
- `vars`: Key/value pairs of variables to inject into the configuration file. These can be referenced in the configuration file
371+
with `{{index $.Vars "key"}}`. All resource IDs (even for resources not under test) should be declared with variables that
372+
contain a `-` or `_`; this will ensure that, in tests, the resources are created with a `tf-test` prefix to allow automatic cleanup of dangling resources and a random suffix to avoid name collisions.
373+
- `test_env_vars`: Key/value pairs of variable names and special values indicating variables that should be pulled from the
374+
environment during tests. These will receive a neutral default value in documentation. Common special values include:
375+
`PROJECT_NAME`, `REGION`, `ORG_ID`, `BILLING_ACCT`, `SERVICE_ACCT` (the test runner service account).
376+
- `test_vars_overrides`: Key/value pairs of literal overrides for variables used in tests. This can be used to call functions to
377+
generate or determine a variable's value.
378+
- `min_version`: Set this to `beta` if the resource is in the `google` provider but the example will only work with the
379+
`google-beta` provider (for example, because it includes a beta-only field.)
380+
- `ignore_read_extra`: Properties to not check on import. This should be used in cases where a property will not be set on import,
381+
for example write-only fields.
382+
- `exclude_test`: If set to `true`, no test will be generated based on this example.
383+
- `exclude_docs`: If set to `true`, no documentation will be generated based on this example.
384+
- `exclude_import_test`: If set to `true`, no import test will be generated for this example.
385+
- `skip_vcr`: See [Skip tests in VCR replaying mode]({{< ref "/test/test#skip-vcr" >}}) for more information about this flag.
386+
- `skip_test`: If not empty, the test generated based on this example will always be skipped. In most cases, the value should be a
387+
link to a ticket explaining the issue that needs to be resolved before the test can be unskipped.
388+
- `external_providers`: A list of external providers that are needed for the testcase. This does add some latency to the testcase,
389+
so only use if necessary. Common external providers: `random`, `time`.
390+
391+
Example:
392+
393+
```yaml
394+
examples:
395+
- name: service_resource_basic
396+
primary_resource_id: example
397+
bootstrap_iam:
398+
- member: "serviceAccount:service-{project_number}@gcp-sa-healthcare.iam.gserviceaccount.com"
399+
role: "roles/bigquery.dataEditor"
400+
vars:
401+
dataset_id: "my-dataset"
402+
network_name: "my-network"
403+
test_env_vars:
404+
org_id: "ORG_ID"
405+
test_vars_overrides:
406+
network_name: 'acctest.BootstrapSharedServiceNetworkingConnection(t, "service-resource-network-config")'
407+
min_version: "beta"
408+
ignore_read_extra:
409+
- 'foo'
410+
exclude_test: true
411+
exclude_docs: true
412+
exclude_import_test: true
413+
skip_vcr: true
414+
skip_test: "https://github.com/hashicorp/terraform-provider-google/issues/20574"
415+
external_providers:
416+
- "time"
417+
```

docs/content/test/test.md

+33-35
Original file line numberDiff line numberDiff line change
@@ -46,27 +46,37 @@ A create test is a test that creates the target resource and immediately destroy
4646
4747
{{< tabs "create" >}}
4848
{{< tab "MMv1" >}}
49-
1. Using an editor of your choice, create a `*.tf.tmpl` file in [`mmv1/templates/terraform/examples/`](https://github.com/GoogleCloudPlatform/magic-modules/tree/main/mmv1/templates/terraform/examples). The name of the file should include the service name, resource name, and a descriptor. For example, `compute_subnetwork_basic.tf.tmpl`.
50-
2. Write the Terraform configuration for your test. This should include all of the required dependencies. For example, `google_compute_subnetwork` has a dependency on `google_compute_network`:
51-
```tf
52-
resource "google_compute_subnetwork" "primary" {
53-
name = "my-subnet"
54-
ip_cidr_range = "10.1.0.0/16"
55-
region = "us-central1"
56-
network = google_compute_network.network.name
57-
}
58-
59-
resource "google_compute_network" "network" {
60-
name = "my-network"
61-
auto_create_subnetworks = false
62-
}
49+
1. Add an entry to your `RESOURCE_NAME.yaml` file's `examples`. The fields listed here are the most commonly-used. For a comprehensive reference, see [MMv1 resource reference: `examples`]({{<ref "/reference/resource#examples" >}}).
50+
```yaml
51+
examples:
52+
# name must correspond to a configuration file that you'll create in the next step.
53+
# The name should include the product name, resource name, and a basic description
54+
# of the test. This will be used to generate the test name and the documentation
55+
# header.
56+
- name: "PRODUCT_RESOURCE_basic"
57+
# primary_resource_id will be used for the Terraform resource id in the configuration file.
58+
primary_resource_id: "example"
59+
# vars contains key/value pairs of variables to inject into the configuration file.
60+
# These can be referenced in the configuration file as a key inside `{{$.Vars}}`.
61+
# All resource IDs (even for resources not under test) should be declared
62+
# with variables that contain a `-` or `_`; this will ensure that, in tests,
63+
# the resources are created with a `tf-test` prefix to allow automatic cleanup
64+
# of dangling resources and a random suffix to avoid name collisions.
65+
vars:
66+
network_name: "example-network"
67+
# test_vars_overrides contains key/value pairs of literal overrides for
68+
# variables used in tests. This can be used to call functions to
69+
# generate or determine a variable's value – for example, bootstrapping
70+
# a shared network for your product to avoid test failures due to limits
71+
# on the default network.
72+
test_vars_overrides:
73+
network_name: 'acctest.BootstrapSharedServiceNetworkingConnection(t, "PRODUCT-RESOURCE-network-config")'
74+
# Set min_version: beta if the resource is not beta-only and any beta-only fields are being tested.
75+
min_version: beta
6376
```
64-
3. If beta-only fields are being tested:
65-
- Add `provider = google-beta` to every resource in the file.
66-
4. Modify the configuration to use templated values.
67-
- Replace the id of the primary resource you are testing with `{{$.PrimaryResourceId}}`.
68-
- Replace fields that are identifiers, like `id` or `name`, with an appropriately named variable. For example, `{{index $.Vars "subnetwork_name"}}`.
69-
- The resulting configuration for the above example would look like this:
77+
78+
2. Create a `.tf.tmpl` file in [`mmv1/templates/terraform/examples/`](https://github.com/GoogleCloudPlatform/magic-modules/tree/main/mmv1/templates/terraform/examples). The name of the file should match the name of the example created in the previous step. For example, `PRODUCT_RESOURCE_basic.tf.tmpl`.
79+
3. In that file, write the Terraform configuration for your test. This should include all of the required dependencies. For example, `google_compute_subnetwork` has a dependency on `google_compute_network`:
7080
```tf
7181
resource "google_compute_subnetwork" "{{$.PrimaryResourceId}}" {
7282
name = "{{index $.Vars "subnetwork_name"}}"
@@ -80,20 +90,8 @@ A create test is a test that creates the target resource and immediately destroy
8090
auto_create_subnetworks = false
8191
}
8292
```
83-
5. Modify the relevant `RESOURCE_NAME.yaml` file under [magic-modules/mmv1/products](https://github.com/GoogleCloudPlatform/magic-modules/tree/main/mmv1/products) to include an [`examples`](https://github.com/GoogleCloudPlatform/magic-modules/blob/main/mmv1/api/resource/examples.go) block with your test. The `name` must match the name of your `*.tf.tmpl` file. For example:
84-
```yaml
85-
examples:
86-
- name: "compute_subnetwork_basic"
87-
primary_resource_id: "example"
88-
vars:
89-
subnetwork_name: "example-subnet"
90-
network_name: "example-network"
91-
```
92-
{{< hint warning >}}
93-
**Warning:** Values in `vars` must include a `-` (or `_`). They [trigger the addition of a `tf-test` prefix](https://github.com/GoogleCloudPlatform/magic-modules/blob/main/mmv1/api/resource/examples.go#L224), which the sweeper uses to clean them up after tests run.
94-
{{< /hint >}}
95-
6. If beta-only fields are being tested:
96-
- Add `min_version: 'beta'` to the `examples` block in `RESOURCE_NAME.yaml`.
93+
4. If the resource or the example is beta-only:
94+
- Add `provider = google-beta` to every resource in the file.
9795
{{< /tab >}}
9896
{{< tab "Handwritten" >}}
9997
This section assumes you've used the [Add a resource]({{< ref "/develop/add-resource" >}}) guide to create your handwritten resource, and you have a working MMv1 config.
@@ -273,7 +271,7 @@ func TestSignatureAlgorithmDiffSuppress(t *testing.T) {
273271
}
274272
```
275273

276-
## Skip tests in VCR replaying mode
274+
## Skip tests in VCR replaying mode {#skip-vcr}
277275

278276
Acceptance tests are run in VCR replaying mode on PRs (using pre-recorded HTTP requests and responses) to reduce the time it takes to present results to contributors. However, not all resources or tests are possible to run in replaying mode. Incompatible tests should be skipped during VCR replaying mode. They will still run in our nightly test suite.
279277

mmv1/api/resource/examples.go

+23-12
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ import (
2828
"github.com/golang/glog"
2929
)
3030

31+
type IamMember struct {
32+
Member, Role string
33+
}
34+
3135
// Generates configs to be shown as examples in docs and outputted as tests
3236
// from a shared template
3337
type Examples struct {
@@ -49,6 +53,13 @@ type Examples struct {
4953
// object parent
5054
PrimaryResourceType string `yaml:"primary_resource_type,omitempty"`
5155

56+
// BootstrapIam will automatically bootstrap the given member/role pairs.
57+
// This should be used in cases where specific IAM permissions must be
58+
// present on the default test project, to avoid race conditions between
59+
// tests. Permissions attached to resources created in a test should instead
60+
// be provisioned with standard terraform resources.
61+
BootstrapIam []IamMember `yaml:"bootstrap_iam,omitempty"`
62+
5263
// Vars is a Hash from template variable names to output variable names.
5364
// It will use the provided value as a prefix for generated tests, and
5465
// insert it into the docs verbatim.
@@ -62,18 +73,18 @@ type Examples struct {
6273
//
6374
// test_env_vars is a Hash from template variable names to one of the
6475
// following symbols:
65-
// - :PROJECT_NAME
66-
// - :CREDENTIALS
67-
// - :REGION
68-
// - :ORG_ID
69-
// - :ORG_TARGET
70-
// - :BILLING_ACCT
71-
// - :MASTER_BILLING_ACCT
72-
// - :SERVICE_ACCT
73-
// - :CUST_ID
74-
// - :IDENTITY_USER
75-
// - :CHRONICLE_ID
76-
// - :VMWAREENGINE_PROJECT
76+
// - PROJECT_NAME
77+
// - CREDENTIALS
78+
// - REGION
79+
// - ORG_ID
80+
// - ORG_TARGET
81+
// - BILLING_ACCT
82+
// - MASTER_BILLING_ACCT
83+
// - SERVICE_ACCT
84+
// - CUST_ID
85+
// - IDENTITY_USER
86+
// - CHRONICLE_ID
87+
// - VMWAREENGINE_PROJECT
7788
// This list corresponds to the `get*FromEnv` methods in provider_test.go.
7889
TestEnvVars map[string]string `yaml:"test_env_vars,omitempty"`
7990

mmv1/products/healthcare/DicomStore.yaml

+5-2
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,11 @@ examples:
5353
pubsub_topic: 'dicom-notifications'
5454
bq_dataset_name: 'dicom_bq_ds'
5555
bq_table_name: 'dicom_bq_tb'
56-
test_vars_overrides:
57-
'policyChanged': ' acctest.BootstrapPSARoles(t, "service-", "gcp-sa-healthcare", []string{"roles/bigquery.dataEditor", "roles/bigquery.jobUser"})'
56+
bootstrap_iam:
57+
- member: "serviceAccount:service-{project_number}@gcp-sa-healthcare.iam.gserviceaccount.com"
58+
role: "roles/bigquery.dataEditor"
59+
- member: "serviceAccount:service-{project_number}@gcp-sa-healthcare.iam.gserviceaccount.com"
60+
role: "roles/bigquery.jobUser"
5861
parameters:
5962
- name: 'dataset'
6063
type: ResourceRef

mmv1/products/healthcare/FhirStore.yaml

+5-2
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,11 @@ examples:
5151
fhir_store_name: 'example-fhir-store'
5252
pubsub_topic: 'fhir-notifications'
5353
bq_dataset_name: 'bq_example_dataset'
54-
test_vars_overrides:
55-
'policyChanged': ' acctest.BootstrapPSARoles(t, "service-", "gcp-sa-healthcare", []string{"roles/bigquery.dataEditor", "roles/bigquery.jobUser"})'
54+
bootstrap_iam:
55+
- member: "serviceAccount:service-{project_number}@gcp-sa-healthcare.iam.gserviceaccount.com"
56+
role: "roles/bigquery.dataEditor"
57+
- member: "serviceAccount:service-{project_number}@gcp-sa-healthcare.iam.gserviceaccount.com"
58+
role: "roles/bigquery.jobUser"
5659
- name: 'healthcare_fhir_store_notification_config'
5760
primary_resource_id: 'default'
5861
vars:

mmv1/products/pubsub/Subscription.yaml

+10-4
Original file line numberDiff line numberDiff line change
@@ -72,17 +72,23 @@ examples:
7272
subscription_name: 'example-subscription'
7373
dataset_id: 'example_dataset'
7474
table_id: 'example_table'
75-
test_vars_overrides:
76-
policy_changed: 'acctest.BootstrapPSARoles(t, "service-", "gcp-sa-pubsub", []string{"roles/bigquery.dataEditor", "roles/bigquery.metadataViewer"})'
75+
bootstrap_iam:
76+
- member: "serviceAccount:service-{project_number}@gcp-sa-pubsub.iam.gserviceaccount.com"
77+
role: "roles/bigquery.dataEditor"
78+
- member: "serviceAccount:service-{project_number}@gcp-sa-pubsub.iam.gserviceaccount.com"
79+
role: "roles/bigquery.metadataViewer"
7780
- name: 'pubsub_subscription_push_bq_table_schema'
7881
primary_resource_id: 'example'
7982
vars:
8083
topic_name: 'example-topic'
8184
subscription_name: 'example-subscription'
8285
dataset_id: 'example_dataset'
8386
table_id: 'example_table'
84-
test_vars_overrides:
85-
policy_changed: 'acctest.BootstrapPSARoles(t, "service-", "gcp-sa-pubsub", []string{"roles/bigquery.dataEditor", "roles/bigquery.metadataViewer"})'
87+
bootstrap_iam:
88+
- member: "serviceAccount:service-{project_number}@gcp-sa-pubsub.iam.gserviceaccount.com"
89+
role: "roles/bigquery.dataEditor"
90+
- member: "serviceAccount:service-{project_number}@gcp-sa-pubsub.iam.gserviceaccount.com"
91+
role: "roles/bigquery.metadataViewer"
8692
- name: 'pubsub_subscription_push_bq_service_account'
8793
primary_resource_id: 'example'
8894
vars:

mmv1/provider/template_data.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ func (td *TemplateData) GenerateIamPolicyTestFile(filePath string, resource api.
170170
templates := []string{
171171
templatePath,
172172
"templates/terraform/env_var_context.go.tmpl",
173-
"templates/terraform/iam/iam_context.go.tmpl",
173+
"templates/terraform/iam/iam_test_setup.go.tmpl",
174174
}
175175
td.GenerateFile(filePath, templatePath, resource, true, templates...)
176176
}

mmv1/templates/terraform/examples/base_configs/iam_test_file.go.tmpl

+8-8
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import (
3232
{{ $example := $.FirstTestExample }}
3333
func TestAcc{{ $.ResourceName }}IamBindingGenerated(t *testing.T) {
3434
t.Parallel()
35-
{{ template "IamContext" $ }}
35+
{{ template "IamTestSetup" $ }}
3636

3737
acctest.VcrTest(t, resource.TestCase{
3838
PreCheck: func() { acctest.AccTestPreCheck(t) },
@@ -78,7 +78,7 @@ func TestAcc{{ $.ResourceName }}IamBindingGenerated(t *testing.T) {
7878

7979
func TestAcc{{ $.ResourceName }}IamMemberGenerated(t *testing.T) {
8080
t.Parallel()
81-
{{ template "IamContext" $ }}
81+
{{ template "IamTestSetup" $ }}
8282

8383
acctest.VcrTest(t, resource.TestCase{
8484
PreCheck: func() { acctest.AccTestPreCheck(t) },
@@ -116,7 +116,7 @@ func TestAcc{{ $.ResourceName }}IamPolicyGenerated(t *testing.T) {
116116
{{ if $.IamPolicy.AdminIamRole }}
117117
// This may skip test, so do it first
118118
sa := envvar.GetTestServiceAccountFromEnv(t)
119-
{{- end }}{{ template "IamContext" $ }}
119+
{{- end }}{{ template "IamTestSetup" $ }}
120120
{{- if $.IamPolicy.AdminIamRole }}
121121
context["service_account"] = sa
122122
{{- end }}
@@ -165,7 +165,7 @@ func TestAcc{{ $.ResourceName }}IamPolicyGenerated(t *testing.T) {
165165
{{ if $.IamPolicy.IamConditionsRequestType }}
166166
func TestAcc{{ $.ResourceName }}IamBindingGenerated_withCondition(t *testing.T) {
167167
t.Parallel()
168-
{{ template "IamContext" $ }}
168+
{{ template "IamTestSetup" $ }}
169169

170170
acctest.VcrTest(t, resource.TestCase{
171171
PreCheck: func() { acctest.AccTestPreCheck(t) },
@@ -201,7 +201,7 @@ func TestAcc{{ $.ResourceName }}IamBindingGenerated_withAndWithoutCondition(t *t
201201
// Multiple fine-grained resources
202202
acctest.SkipIfVcr(t)
203203
t.Parallel()
204-
{{ template "IamContext" $ }}
204+
{{ template "IamTestSetup" $ }}
205205

206206
acctest.VcrTest(t, resource.TestCase{
207207
PreCheck: func() { acctest.AccTestPreCheck(t) },
@@ -248,7 +248,7 @@ func TestAcc{{ $.ResourceName }}IamBindingGenerated_withAndWithoutCondition(t *t
248248
func TestAcc{{ $.ResourceName }}IamMemberGenerated_withCondition(t *testing.T) {
249249
t.Parallel()
250250

251-
{{ template "IamContext" $ }}
251+
{{ template "IamTestSetup" $ }}
252252

253253
acctest.VcrTest(t, resource.TestCase{
254254
PreCheck: func() { acctest.AccTestPreCheck(t) },
@@ -284,7 +284,7 @@ func TestAcc{{ $.ResourceName }}IamMemberGenerated_withAndWithoutCondition(t *te
284284
// Multiple fine-grained resources
285285
acctest.SkipIfVcr(t)
286286
t.Parallel()
287-
{{ template "IamContext" $ }}
287+
{{ template "IamTestSetup" $ }}
288288

289289
acctest.VcrTest(t, resource.TestCase{
290290
PreCheck: func() { acctest.AccTestPreCheck(t) },
@@ -334,7 +334,7 @@ func TestAcc{{ $.ResourceName }}IamPolicyGenerated_withCondition(t *testing.T) {
334334
// This may skip test, so do it first
335335
sa := envvar.GetTestServiceAccountFromEnv(t)
336336
{{- end }}
337-
{{- template "IamContext" $ }}
337+
{{- template "IamTestSetup" $ }}
338338
{{- if $.IamPolicy.AdminIamRole }}
339339
context["service_account"] = sa
340340
{{- end }}

mmv1/templates/terraform/examples/base_configs/test_file.go.tmpl

+11
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,17 @@ func TestAcc{{ $e.TestSlug $.Res.ProductMetadata.Name $.Res.Name }}(t *testing.T
4747
{{- end }}
4848
t.Parallel()
4949

50+
{{- if $e.BootstrapIam }}
51+
acctest.BootstrapIamMembers(t, []acctest.IamMember{
52+
{{- range $iam := $e.BootstrapIam }}
53+
{
54+
Member: "{{$iam.Member}}",
55+
Role: "{{$iam.Role}}",
56+
},
57+
{{- end}}
58+
})
59+
{{- end }}
60+
5061
context := map[string]interface{}{
5162
{{- template "EnvVarContext" dict "TestEnvVars" $e.TestEnvVars "HasNewLine" false}}
5263
{{- range $varKey, $varVal := $e.TestVarsOverrides }}

mmv1/templates/terraform/iam/iam_context.go.tmpl renamed to mmv1/templates/terraform/iam/iam_test_setup.go.tmpl

+11-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,14 @@
1-
{{- define "IamContext" }}
1+
{{- define "IamTestSetup" }}
2+
{{- if $.FirstTestExample.BootstrapIam }}
3+
acctest.BootstrapIamMembers(t, []acctest.IamMember{
4+
{{- range $iam := $.FirstTestExample.BootstrapIam }}
5+
{
6+
Member: "{{$iam.Member}}",
7+
Role: "{{$iam.Role}}",
8+
},
9+
{{- end}}
10+
})
11+
{{- end }}
212
context := map[string]interface{}{
313
"random_suffix": acctest.RandString(t, 10),
414
"role": "{{ $.IamPolicy.AllowedIamRole }}",

0 commit comments

Comments
 (0)