diff --git a/.gitignore b/.gitignore index c5fb326..7dbe3bc 100644 --- a/.gitignore +++ b/.gitignore @@ -48,5 +48,9 @@ crash.log credentials.json +examples/automatic-labelling-folder/function_source.zip examples/automatic-labelling-from-localhost/function_source.zip examples/automatic-labelling-from-repository/function_source_copy + +node_modules +yarn.lock diff --git a/README.md b/README.md index d183e38..e447bd2 100644 --- a/README.md +++ b/README.md @@ -86,4 +86,4 @@ following APIs enabled: - Cloud Storage API: `storage-component.googleapis.com` The [Project Factory module][project-factory-module-site] can be used to -provision projects with specific APIs activated. \ No newline at end of file +provision projects with specific APIs activated. diff --git a/build/int.cloudbuild.yaml b/build/int.cloudbuild.yaml index 998da2b..acdecf8 100644 --- a/build/int.cloudbuild.yaml +++ b/build/int.cloudbuild.yaml @@ -21,9 +21,6 @@ steps: - 'TF_VAR_org_id=$_ORG_ID' - 'TF_VAR_folder_id=$_FOLDER_ID' - 'TF_VAR_billing_account=$_BILLING_ACCOUNT' -- id: echo - name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS' - args: ['/bin/bash', '-c', 'cat test/source.sh'] - id: create name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS' args: ['/bin/bash', '-c', 'source /usr/local/bin/task_helper_functions.sh && kitchen_do create'] diff --git a/examples/automatic-labelling-folder/README.md b/examples/automatic-labelling-folder/README.md new file mode 100644 index 0000000..a5aa92a --- /dev/null +++ b/examples/automatic-labelling-folder/README.md @@ -0,0 +1,69 @@ +# Automatic Labelling for folder projects + +This example demonstrates how to use the +[root module][root-module] and the +[event-folder-log-entry submodule][event-folder-log-entry-submodule] +to configure a system +which responds to project creation events by labelling them with test label. + +## Usage + +To provision this example, populate `terraform.tfvars` with the [required variables](#inputs) and run the following commands within +this directory: + +- `terraform init` to initialize the directory +- `terraform plan` to generate the execution plan +- `terraform apply` to apply the execution plan +- `terraform destroy` to destroy the infrastructure + + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|:----:|:-----:|:-----:| +| folder\_id | The ID of the folder to look for changes. | string | n/a | yes | +| project\_id | The ID of the project to which resources will be applied. | string | n/a | yes | +| region | The region in which resources will be applied. | string | n/a | yes | + + + +## Requirements + +The following sections describe the requirements which must be met in +order to invoke this module. The requirements of the +[root module][root-module-requirements] and the +[event-folder-log-entry submodule][event-folder-log-entry-submodule-requirements] +must also be met. + +### Software Dependencies + +The following software dependencies must be installed on the system +from which this module will be invoked: + +- [Terraform][terraform-site] v0.12.Z + +### IAM Roles + +The Service Account which will be used to invoke this module must have +the following IAM roles: + +- Logs Configuration Writer: `roles/logging.configWriter` +- Pub/Sub Admin: `roles/pubsub.admin` +- Service Account User: `roles/iam.serviceAccountUser` + +- Default AppSpot user: `roles/owner` +- Your user: `roles/resourcemanager.projectCreator` + +### APIs + +The project against which this module will be invoked must have the +following APIs enabled: + +- Cloud Pub/Sub API: `pubsub.googleapis.com` +- Stackdriver Logging API: `logging.googleapis.com` + +[event-folder-log-entry-submodule-requirements]: ../../modules/event-folder-log-entry/README.md#requirements +[event-folder-log-entry-submodule]: ../../modules/event-folder-log-entry +[root-module-requirements]: ../../README.md#requirements +[root-module]: ../.. +[terraform-site]: https://terraform.io/ diff --git a/examples/automatic-labelling-folder/function_source/index.js b/examples/automatic-labelling-folder/function_source/index.js new file mode 100644 index 0000000..39d2381 --- /dev/null +++ b/examples/automatic-labelling-folder/function_source/index.js @@ -0,0 +1,139 @@ +/** + * Copyright 2019 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const { google } = require("googleapis"); +const { auth } = google; +const crm = google.cloudresourcemanager("v1"); + +/** + * Authenticates with Google Cloud Platform. + * + * @param {!Function} callback A callback function to signal completion. + */ +authenticate = callback => { + console.log("Authenticating"); + auth.getApplicationDefault((error, authClient) => { + if (error) { + console.error("Error while authenticating"); + + return callback(error); + } + console.log("Authenticated"); + + return callback(null, authClient); + }); +}; + +/** + * Fetches fields from a given project. + * + * @param {!Object} authClient An authenticated client for GCP. + * label. + * @param {!String} projectId The identity of the project to fetch fields for. + * @param {!Function} callback A callback function to signal completion. + */ +fetchFields = ({ authClient, projectId }, callback) => { + console.log("Fetching fields for " + projectId); + crm.projects.get( + { auth: authClient, projectId }, (error, response) => { + if (error) { + console.error("Error while fetching fields"); + + return callback(error); + } + + const fields = response.data || {}; + + console.log("Fetched labels:", fields); + + return callback(null, fields); + }); +}; + +/** + * Stores fields on a given project. + * + * @param {!Object} authClient An authenticated client for GCP. + * @param {!Object} fields Fields to be stored on the project. + * @param {!String} projectId The identity of the project to save fields to. + * @param {!Function} callback A callback function to signal completion. + */ +storeFields = + ({ authClient, fields, projectId }, + callback) => { + console.log("Storing fields for " + projectId); + crm.projects.update( + { + auth: authClient, + projectId, + resource: fields + }, + error => { + if (error) { + console.error("Error while storing fields"); + + return callback(error); + } + console.log("Stored fields:", fields); + + return callback(null); + }); + }; + +/** + * Triggered from a message on a Cloud Pub/Sub topic. + * + * @param {!Object} event Event payload and metadata. + * @param {!Function} callback Callback function to signal completion. + */ +exports.labelResource = (data, context, callback)=> { + const eventData = + JSON.parse(Buffer.from(data.data, "base64").toString()); + + console.log("Received event"); + console.log(eventData); + authenticate((error, authClient) => { + if (error) { + return callback(error); + } + + const projectId = eventData.resource.labels.project_id; + + fetchFields( + { authClient, projectId }, + (error, fields, labelFingerprint) => { + if (error) { + return callback(error); + } + + const labelKey = process.env.LABEL_KEY; + const labelValue = process.env.LABEL_VALUE; + + storeFields( + { + authClient, + fields: { + name: fields.name, + parent: fields.parent, + labels: + Object.assign(fields.labels || {}, { [labelKey]: labelValue }) + }, + projectId + }, + callback); + }); + }); +}; diff --git a/examples/automatic-labelling-folder/function_source/package.json b/examples/automatic-labelling-folder/function_source/package.json new file mode 100644 index 0000000..3993eb1 --- /dev/null +++ b/examples/automatic-labelling-folder/function_source/package.json @@ -0,0 +1,7 @@ +{ + "name": "label-resource", + "version": "0.0.1", + "dependencies": { + "googleapis": "^36.0" + } +} diff --git a/examples/automatic-labelling-folder/main.tf b/examples/automatic-labelling-folder/main.tf new file mode 100644 index 0000000..4ea2532 --- /dev/null +++ b/examples/automatic-labelling-folder/main.tf @@ -0,0 +1,107 @@ +/** + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +terraform { + required_version = "~> 0.12.0" +} + +provider "archive" { + version = "~> 1.0" +} + +provider "google" { + version = "~> 2.1" +} + +provider "random" { + version = "~> 2.0" +} + +provider "null" { + version = "~> 2.1" +} + +resource "random_pet" "main" { + length = 2 + separator = "-" +} + +module "event_folder_log_entry" { + source = "../../modules/event-folder-log-entry" + + filter = < ../source.sh sa_json=$(terraform output sa_key) diff --git a/test/setup/outputs.tf b/test/setup/outputs.tf index 628b854..1384ba4 100644 --- a/test/setup/outputs.tf +++ b/test/setup/outputs.tf @@ -18,6 +18,10 @@ output "project_id" { value = module.project.project_id } +output "sub_folder_id" { + value = google_folder.ci_event_func_subfolder.id +} + output "sa_key" { value = google_service_account_key.int_test.private_key sensitive = true