Skip to content

Commit a8ded77

Browse files
nick4fakekopachevsky
authored andcommitted
Code review fixes for: Add example and tests for project-related events
Example and tests for automatic project labeling. Changed setup tf fixtures, sub folder creation added. Added roles/owner to cloud function sa for subfolder. Relevant issues: terraform-google-modules/terraform-google-vpc-service-controls#11 #27 Fixes #30
1 parent 6d48307 commit a8ded77

File tree

27 files changed

+592
-17
lines changed

27 files changed

+592
-17
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,5 +48,9 @@ crash.log
4848

4949
credentials.json
5050

51+
examples/automatic-labelling-folder/function_source.zip
5152
examples/automatic-labelling-from-localhost/function_source.zip
5253
examples/automatic-labelling-from-repository/function_source_copy
54+
55+
node_modules
56+
yarn.lock

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,4 +86,4 @@ following APIs enabled:
8686
- Cloud Storage API: `storage-component.googleapis.com`
8787

8888
The [Project Factory module][project-factory-module-site] can be used to
89-
provision projects with specific APIs activated.
89+
provision projects with specific APIs activated.

build/int.cloudbuild.yaml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,6 @@ steps:
2121
- 'TF_VAR_org_id=$_ORG_ID'
2222
- 'TF_VAR_folder_id=$_FOLDER_ID'
2323
- 'TF_VAR_billing_account=$_BILLING_ACCOUNT'
24-
- id: echo
25-
name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS'
26-
args: ['/bin/bash', '-c', 'cat test/source.sh']
2724
- id: create
2825
name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS'
2926
args: ['/bin/bash', '-c', 'source /usr/local/bin/task_helper_functions.sh && kitchen_do create']
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# Automatic Labelling for folder projects
2+
3+
This example demonstrates how to use the
4+
[root module][root-module] and the
5+
[event-folder-log-entry submodule][event-folder-log-entry-submodule]
6+
to configure a system
7+
which responds to project creation events by labelling them with test label.
8+
9+
## Usage
10+
11+
To provision this example, populate `terraform.tfvars` with the [required variables](#inputs) and run the following commands within
12+
this directory:
13+
14+
- `terraform init` to initialize the directory
15+
- `terraform plan` to generate the execution plan
16+
- `terraform apply` to apply the execution plan
17+
- `terraform destroy` to destroy the infrastructure
18+
19+
<!-- BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
20+
## Inputs
21+
22+
| Name | Description | Type | Default | Required |
23+
|------|-------------|:----:|:-----:|:-----:|
24+
| folder\_id | The ID of the folder to look for changes. | string | n/a | yes |
25+
| project\_id | The ID of the project to which resources will be applied. | string | n/a | yes |
26+
| region | The region in which resources will be applied. | string | n/a | yes |
27+
28+
<!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
29+
30+
## Requirements
31+
32+
The following sections describe the requirements which must be met in
33+
order to invoke this module. The requirements of the
34+
[root module][root-module-requirements] and the
35+
[event-folder-log-entry submodule][event-folder-log-entry-submodule-requirements]
36+
must also be met.
37+
38+
### Software Dependencies
39+
40+
The following software dependencies must be installed on the system
41+
from which this module will be invoked:
42+
43+
- [Terraform][terraform-site] v0.12.Z
44+
45+
### IAM Roles
46+
47+
The Service Account which will be used to invoke this module must have
48+
the following IAM roles:
49+
50+
- Logs Configuration Writer: `roles/logging.configWriter`
51+
- Pub/Sub Admin: `roles/pubsub.admin`
52+
- Service Account User: `roles/iam.serviceAccountUser`
53+
54+
- Default AppSpot user: `roles/owner`
55+
- Your user: `roles/resourcemanager.projectCreator`
56+
57+
### APIs
58+
59+
The project against which this module will be invoked must have the
60+
following APIs enabled:
61+
62+
- Cloud Pub/Sub API: `pubsub.googleapis.com`
63+
- Stackdriver Logging API: `logging.googleapis.com`
64+
65+
[event-folder-log-entry-submodule-requirements]: ../../modules/event-folder-log-entry/README.md#requirements
66+
[event-folder-log-entry-submodule]: ../../modules/event-folder-log-entry
67+
[root-module-requirements]: ../../README.md#requirements
68+
[root-module]: ../..
69+
[terraform-site]: https://terraform.io/
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
/**
2+
* Copyright 2019 Google Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
const { google } = require("googleapis");
18+
const { auth } = google;
19+
const crm = google.cloudresourcemanager("v1");
20+
21+
/**
22+
* Authenticates with Google Cloud Platform.
23+
*
24+
* @param {!Function} callback A callback function to signal completion.
25+
*/
26+
authenticate = callback => {
27+
console.log("Authenticating");
28+
auth.getApplicationDefault((error, authClient) => {
29+
if (error) {
30+
console.error("Error while authenticating");
31+
32+
return callback(error);
33+
}
34+
console.log("Authenticated");
35+
36+
return callback(null, authClient);
37+
});
38+
};
39+
40+
/**
41+
* Fetches fields from a given project.
42+
*
43+
* @param {!Object} authClient An authenticated client for GCP.
44+
* label.
45+
* @param {!String} projectId The identity of the project to fetch fields for.
46+
* @param {!Function} callback A callback function to signal completion.
47+
*/
48+
fetchFields = ({ authClient, projectId }, callback) => {
49+
console.log("Fetching fields for " + projectId);
50+
crm.projects.get(
51+
{ auth: authClient, projectId }, (error, response) => {
52+
if (error) {
53+
console.error("Error while fetching fields");
54+
55+
return callback(error);
56+
}
57+
58+
const fields = response.data || {};
59+
60+
console.log("Fetched labels:", fields);
61+
62+
return callback(null, fields);
63+
});
64+
};
65+
66+
/**
67+
* Stores fields on a given project.
68+
*
69+
* @param {!Object} authClient An authenticated client for GCP.
70+
* @param {!Object} fields Fields to be stored on the project.
71+
* @param {!String} projectId The identity of the project to save fields to.
72+
* @param {!Function} callback A callback function to signal completion.
73+
*/
74+
storeFields =
75+
({ authClient, fields, projectId },
76+
callback) => {
77+
console.log("Storing fields for " + projectId);
78+
crm.projects.update(
79+
{
80+
auth: authClient,
81+
projectId,
82+
resource: fields
83+
},
84+
error => {
85+
if (error) {
86+
console.error("Error while storing fields");
87+
88+
return callback(error);
89+
}
90+
console.log("Stored fields:", fields);
91+
92+
return callback(null);
93+
});
94+
};
95+
96+
/**
97+
* Triggered from a message on a Cloud Pub/Sub topic.
98+
*
99+
* @param {!Object} event Event payload and metadata.
100+
* @param {!Function} callback Callback function to signal completion.
101+
*/
102+
exports.labelResource = (data, context, callback)=> {
103+
const eventData =
104+
JSON.parse(Buffer.from(data.data, "base64").toString());
105+
106+
console.log("Received event");
107+
console.log(eventData);
108+
authenticate((error, authClient) => {
109+
if (error) {
110+
return callback(error);
111+
}
112+
113+
const projectId = eventData.resource.labels.project_id;
114+
115+
fetchFields(
116+
{ authClient, projectId },
117+
(error, fields, labelFingerprint) => {
118+
if (error) {
119+
return callback(error);
120+
}
121+
122+
const labelKey = process.env.LABEL_KEY;
123+
const labelValue = process.env.LABEL_VALUE;
124+
125+
storeFields(
126+
{
127+
authClient,
128+
fields: {
129+
name: fields.name,
130+
parent: fields.parent,
131+
labels:
132+
Object.assign(fields.labels || {}, { [labelKey]: labelValue })
133+
},
134+
projectId
135+
},
136+
callback);
137+
});
138+
});
139+
};
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"name": "label-resource",
3+
"version": "0.0.1",
4+
"dependencies": {
5+
"googleapis": "^36.0"
6+
}
7+
}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
/**
2+
* Copyright 2019 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
terraform {
18+
required_version = "~> 0.12.0"
19+
}
20+
21+
provider "archive" {
22+
version = "~> 1.0"
23+
}
24+
25+
provider "google" {
26+
version = "~> 2.1"
27+
}
28+
29+
provider "random" {
30+
version = "~> 2.0"
31+
}
32+
33+
provider "null" {
34+
version = "~> 2.1"
35+
}
36+
37+
resource "random_pet" "main" {
38+
length = 2
39+
separator = "-"
40+
}
41+
42+
module "event_folder_log_entry" {
43+
source = "../../modules/event-folder-log-entry"
44+
45+
filter = <<EOF
46+
resource.type="project" AND
47+
protoPayload.serviceName="cloudresourcemanager.googleapis.com" AND
48+
protoPayload.methodName="CreateProject"
49+
EOF
50+
name = random_pet.main.id
51+
project_id = var.project_id
52+
folder_id = var.folder_id
53+
}
54+
55+
module "localhost_function" {
56+
source = "../.."
57+
58+
description = "Labels resource with owner information."
59+
entry_point = "labelResource"
60+
61+
environment_variables = {
62+
FOLDER_ID = var.folder_id
63+
LABEL_KEY = "test"
64+
LABEL_VALUE = "foobar"
65+
}
66+
67+
event_trigger = module.event_folder_log_entry.function_event_trigger
68+
name = random_pet.main.id
69+
project_id = var.project_id
70+
region = var.region
71+
source_directory = "${path.module}/function_source"
72+
runtime = "nodejs8"
73+
}
74+
75+
resource "null_resource" "wait_for_function" {
76+
provisioner "local-exec" {
77+
command = "sleep 60"
78+
}
79+
80+
depends_on = [module.localhost_function]
81+
}
82+
83+
resource "random_pet" "project_id" {
84+
length = 1
85+
separator = "-"
86+
prefix = random_pet.main.id
87+
}
88+
89+
resource "google_project" "test" {
90+
name = random_pet.project_id.id
91+
project_id = random_pet.project_id.id
92+
folder_id = var.folder_id
93+
94+
lifecycle {
95+
ignore_changes = [
96+
labels,
97+
]
98+
}
99+
100+
depends_on = [null_resource.wait_for_function]
101+
}
102+
103+
resource "google_project_iam_member" "test_project_iam" {
104+
project = google_project.test.project_id
105+
role = "roles/owner"
106+
member = "serviceAccount:${var.project_id}@appspot.gserviceaccount.com"
107+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/**
2+
* Copyright 2019 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
output "project_id" {
18+
value = var.project_id
19+
description = "The ID of the project to which resources are applied."
20+
}
21+
22+
output "region" {
23+
value = var.region
24+
description = "The region in which resources are applied."
25+
}
26+
27+
output "test_project_id" {
28+
value = google_project.test.project_id
29+
description = "The ID of the project to test."
30+
}

0 commit comments

Comments
 (0)