Skip to content

Commit

Permalink
feat: Add support for tags (#829)
Browse files Browse the repository at this point in the history
* Added tags feature

* Fix test getting bootstrap folder

* Test env folders tags

* Tags feature docs

* Improve docs text

* Added comments regarding tag bindings

* Improve test to check tag on each folder instead of check both

* Update 1-org/README.md

Co-authored-by: Bharath KKB <bharathkrishnakb@gmail.com>
  • Loading branch information
felipecrescencio-cit and bharathkkb authored Oct 6, 2022
1 parent 0d6be42 commit a0604b3
Show file tree
Hide file tree
Showing 12 changed files with 177 additions and 0 deletions.
3 changes: 3 additions & 0 deletions 0-bootstrap/sa.tf
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,11 @@ locals {
"roles/resourcemanager.organizationViewer",
"roles/accesscontextmanager.policyAdmin",
"roles/essentialcontacts.admin",
"roles/resourcemanager.tagAdmin",
"roles/resourcemanager.tagUser",
], local.common_roles)),
"env" = distinct(concat([
"roles/resourcemanager.tagUser",
], local.common_roles)),
"net" = distinct(concat([
"roles/accesscontextmanager.policyAdmin",
Expand Down
10 changes: 10 additions & 0 deletions 1-org/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,16 @@ gcloud scc notifications describe <scc_notification_name> --organization=<org_id
| gcp_scc_admin | Product Updates and Security | Org Admins |
| gcp_security_reviewer | Security and Technical | Org Admins |

**Note:** This module creates and applies [Tags](https://cloud.google.com/resource-manager/docs/tags/tags-overview) to common and bootstrap folders. These tags are also applied to environment folders of step [2-environments](../2-environments/README.md). You can create your own tags by editing `local.tags` map in `tags.tf` and following the commented template. The following table lists details about tags applied to resources:

| Resource | Type | Step | Tag Key | Tag Value |
|----------|------|------|---------|-----------|
| bootstrap | folder | 1-org | environment | bootstrap |
| common | folder | 1-org | environment | production |
| enviroment development | folder | [2-environments](../2-environments/README.md) | environment | development |
| enviroment non-production | folder | [2-environments](../2-environments/README.md) | environment | non-production |
| enviroment production | folder | [2-environments](../2-environments/README.md) | environment | production |

### Deploying with Cloud Build

1. Clone the policy repo based on the Terraform output from the previous section.
Expand Down
2 changes: 2 additions & 0 deletions 1-org/envs/shared/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
| billing\_data\_users | Google Workspace or Cloud Identity group that have access to billing data set. | `string` | n/a | yes |
| billing\_export\_dataset\_location | The location of the dataset for billing data export. | `string` | `"US"` | no |
| create\_access\_context\_manager\_access\_policy | Whether to create access context manager access policy. | `bool` | `true` | no |
| create\_unique\_tag\_key | Creates unique organization-wide tag keys by adding a random suffix to each key. | `bool` | `false` | no |
| data\_access\_logs\_enabled | Enable Data Access logs of types DATA\_READ, DATA\_WRITE for all GCP services. Enabling Data Access logs might result in your organization being charged for the additional logs usage. See https://cloud.google.com/logging/docs/audit#data-access The ADMIN\_READ logs are enabled by default. | `bool` | `false` | no |
| dns\_hub\_project\_alert\_pubsub\_topic | The name of the Cloud Pub/Sub topic where budget related messages will be published, in the form of `projects/{project_id}/topics/{topic_id}` for the DNS hub project. | `string` | `null` | no |
| dns\_hub\_project\_alert\_spent\_percents | A list of percentages of the budget to alert on when threshold is exceeded for the DNS hub project. | `list(number)` | <pre>[<br> 0.5,<br> 0.75,<br> 0.9,<br> 0.95<br>]</pre> | no |
Expand Down Expand Up @@ -79,5 +80,6 @@
| restricted\_net\_hub\_project\_number | The Restricted Network hub project number |
| scc\_notification\_name | Name of SCC Notification |
| scc\_notifications\_project\_id | The SCC notifications project ID |
| tags | Tag Values to be applied on next steps |

<!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
1 change: 1 addition & 0 deletions 1-org/envs/shared/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ locals {
group_billing_admins = data.terraform_remote_state.bootstrap.outputs.group_billing_admins
group_org_admins = data.terraform_remote_state.bootstrap.outputs.group_org_admins
networks_step_terraform_service_account_email = data.terraform_remote_state.bootstrap.outputs.networks_step_terraform_service_account_email
bootstrap_folder_name = data.terraform_remote_state.bootstrap.outputs.common_config.bootstrap_folder_name
}

data "terraform_remote_state" "bootstrap" {
Expand Down
5 changes: 5 additions & 0 deletions 1-org/envs/shared/outputs.tf
Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,8 @@ output "logs_export_bigquery_dataset_name" {
value = module.logs_export.bigquery_destination_name
description = "The log bucket for destination of log exports. See https://cloud.google.com/logging/docs/routing/overview#buckets"
}

output "tags" {
value = local.tags_output
description = "Tag Values to be applied on next steps"
}
82 changes: 82 additions & 0 deletions 1-org/envs/shared/tags.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/**
* Copyright 2022 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.
*/

locals {
tags = {
environment = {
shortname = "environment${local.key_suffix}"
description = "Environment identification"
values = ["bootstrap", "production", "non-production", "development"]
}

# Create your own Tags based on the following template.
# For more details follow https://cloud.google.com/resource-manager/docs/tags/tags-creating-and-managing#creating
# yourkeyname = {
# shortname = "yourkeyname${local.key_suffix}"
# description = "Your Tag Key Description"
# values = ["value_1", "value_2", ... , "value_x"]
# }
}

tags_obj_list = flatten([
for tag_key, tag_obj in local.tags : [
for value in tag_obj.values : {
shortkey = "${tag_key}"
key = "${tag_key}_${value}"
val = "${value}"
}
]
])

tags_obj_map = { for v in local.tags_obj_list : v.key => v }

tags_output = { for k, v in google_tags_tag_value.tag_values : k => v.id }
key_suffix = var.create_unique_tag_key ? "-${random_string.tag_key_suffix.result}" : ""
}

resource "random_string" "tag_key_suffix" {
length = 8
special = false
upper = false
}

resource "google_tags_tag_key" "tag_keys" {
for_each = local.tags

parent = "organizations/${local.org_id}"
short_name = each.value.shortname
description = each.value.description
}

resource "google_tags_tag_value" "tag_values" {
for_each = local.tags_obj_map

parent = "tagKeys/${google_tags_tag_key.tag_keys[each.value.shortkey].name}"
short_name = each.value.val
}

# The following code binds a tag to a resource.
# For more details about binding tags to resources see: https://cloud.google.com/resource-manager/docs/tags/tags-creating-and-managing#attaching
# For more details about how to use terraform binding resource see: https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/tags_tag_binding
resource "google_tags_tag_binding" "common_folder" {
parent = "//cloudresourcemanager.googleapis.com/${google_folder.common.id}"
tag_value = google_tags_tag_value.tag_values["environment_production"].id
}

resource "google_tags_tag_binding" "bootstrap_folder" {
parent = "//cloudresourcemanager.googleapis.com/${local.bootstrap_folder_name}"
tag_value = google_tags_tag_value.tag_values["environment_bootstrap"].id
}
6 changes: 6 additions & 0 deletions 1-org/envs/shared/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -322,3 +322,9 @@ variable "essential_contacts_domains_to_allow" {
description = "The list of domains that email addresses added to Essential Contacts can have."
type = list(string)
}

variable "create_unique_tag_key" {
description = "Creates unique organization-wide tag keys by adding a random suffix to each key."
type = bool
default = false
}
8 changes: 8 additions & 0 deletions 2-environments/modules/env_baseline/folders.tf
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,11 @@ resource "time_sleep" "wait_30_seconds" {

destroy_duration = "30s"
}

# The following code binds a tag to a resource.
# For more details about binding tags to resources see: https://cloud.google.com/resource-manager/docs/tags/tags-creating-and-managing#attaching
# For more details on how to use terraform binding resource see: https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/tags_tag_binding
resource "google_tags_tag_binding" "folder_env" {
parent = "//cloudresourcemanager.googleapis.com/${google_folder.env.id}"
tag_value = local.tags["environment_${var.env}"]
}
10 changes: 10 additions & 0 deletions 2-environments/modules/env_baseline/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ locals {
default_region = data.terraform_remote_state.bootstrap.outputs.common_config.default_region
project_prefix = data.terraform_remote_state.bootstrap.outputs.common_config.project_prefix
folder_prefix = data.terraform_remote_state.bootstrap.outputs.common_config.folder_prefix
tags = data.terraform_remote_state.org.outputs.tags
}

data "terraform_remote_state" "bootstrap" {
Expand All @@ -32,3 +33,12 @@ data "terraform_remote_state" "bootstrap" {
prefix = "terraform/bootstrap/state"
}
}

data "terraform_remote_state" "org" {
backend = "gcs"

config = {
bucket = var.backend_bucket
prefix = "terraform/org/state"
}
}
12 changes: 12 additions & 0 deletions test/integration/envs/envs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,18 @@ func TestEnvs(t *testing.T) {
displayName := fmt.Sprintf("fldr-%s", envName)
assert.Equal(displayName, folder.Get("displayName").String(), fmt.Sprintf("folder %s should have been created", displayName))

// check tags applied to environment folder
envFldrTags := gcloud.Runf(t, "resource-manager tags bindings list --parent=//cloudresourcemanager.googleapis.com/folders/%s", envFolder).Array()

fldrTagValueId := testutils.GetResultFieldStrSlice(envFldrTags, "tagValue")

var fldrTagValue []string
for _, tagValueId := range fldrTagValueId {
tagValueObj := gcloud.Runf(t, "resource-manager tags values describe %s", tagValueId)
fldrTagValue = append(fldrTagValue, tagValueObj.Get("shortName").String())
}
assert.Subset([]string{envName}, fldrTagValue, fmt.Sprintf("tag value should be %s for %s env folder", envName, envName))

for _, projectEnvOutput := range []struct {
projectOutput string
role string
Expand Down
33 changes: 33 additions & 0 deletions test/integration/org/org_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"github.com/GoogleCloudPlatform/cloud-foundation-toolkit/infra/blueprint-test/pkg/gcloud"
"github.com/GoogleCloudPlatform/cloud-foundation-toolkit/infra/blueprint-test/pkg/tft"
"github.com/GoogleCloudPlatform/cloud-foundation-toolkit/infra/blueprint-test/pkg/utils"
"github.com/gruntwork-io/terratest/modules/terraform"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

Expand Down Expand Up @@ -100,6 +101,38 @@ func TestOrg(t *testing.T) {
folder := gcloud.Runf(t, "resource-manager folders describe %s", commonFolder)
assert.Equal("fldr-common", folder.Get("displayName").String(), "folder fldr-common should have been created")

// check tags applied to common and bootstrap folder
bootstrapOutput := terraform.OutputMap(t, bootstrap.GetTFOptions(), "common_config")
bootstrapFolder := testutils.GetLastSplitElement(bootstrapOutput["bootstrap_folder_name"], "/")

for _, tags := range []struct {
folderId string
folderName string
value string
}{
{
folderId: commonFolder,
folderName: "common",
value: "production",
},
{
folderId: bootstrapFolder,
folderName: "bootstrap",
value: "bootstrap",
},
} {
fldrTags := gcloud.Runf(t, "resource-manager tags bindings list --parent=//cloudresourcemanager.googleapis.com/folders/%s", tags.folderId).Array()
fldrsTagValuesId := testutils.GetResultFieldStrSlice(fldrTags, "tagValue")

var fldrsTagValues []string
for _, tagValueId := range fldrsTagValuesId {
tagValueObj := gcloud.Runf(t, "resource-manager tags values describe %s", tagValueId)
fldrsTagValues = append(fldrsTagValues, tagValueObj.Get("shortName").String())
}

assert.Contains(fldrsTagValues, tags.value, fmt.Sprintf("folder %s (%s) should have tag %s", tags.folderName, tags.folderId, tags.value))
}

// boolean organization policies
for _, booleanConstraint := range []string{
"constraints/compute.disableNestedVirtualization",
Expand Down
5 changes: 5 additions & 0 deletions test/setup/outputs.tf
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,8 @@ output "enable_hub_and_spoke_transitivity" {
output "create_access_context_manager_access_policy" {
value = false
}

output "create_unique_tag_key" {
description = "Set to true to avoid tag key name colision during integrated tests. Tag keys are organization-wide unique names."
value = true
}

0 comments on commit a0604b3

Please sign in to comment.