Naming convention module (#318)

* naming convention module

* tfdoc

* lint fixture

* add optional separator, variable descriptions

* add output descriptions

* fix example tests
This commit is contained in:
Ludovico Magnocavallo 2021-10-05 12:21:12 +02:00 committed by GitHub
parent 127e090511
commit a45814f41c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 354 additions and 1 deletions

View File

@ -34,7 +34,7 @@ The current list of modules supports most of the core foundational and networkin
Currently available modules:
- **foundational** - [folder](./modules/folder), [organization](./modules/organization), [project](./modules/project), [service accounts](./modules/iam-service-account), [logging bucket](./modules/logging-bucket), [billing budget](./modules/billing-budget)
- **foundational** - [folder](./modules/folder), [organization](./modules/organization), [project](./modules/project), [service accounts](./modules/iam-service-account), [logging bucket](./modules/logging-bucket), [billing budget](./modules/billing-budget), [naming convention](./modules/naming-convention)
- **networking** - [VPC](./modules/net-vpc), [VPC firewall](./modules/net-vpc-firewall), [VPC peering](./modules/net-vpc-peering), [VPN static](./modules/net-vpn-static), [VPN dynamic](./modules/net-vpn-dynamic), [VPN HA](./modules/net-vpn-ha), [NAT](./modules/net-cloudnat), [address reservation](./modules/net-address), [DNS](./modules/dns), [L4 ILB](./modules/net-ilb), [Service Directory](./modules/service-directory), [Cloud Endpoints](./modules/cloudenpoints)
- **compute** - [VM/VM group](./modules/compute-vm), [MIG](./modules/compute-mig), [GKE cluster](./modules/gke-cluster), [GKE nodepool](./modules/gke-nodepool), [COS container](./modules/cos-container) (coredns, mysql, onprem, squid)
- **data** - [GCS](./modules/gcs), [BigQuery dataset](./modules/bigquery-dataset), [Pub/Sub](./modules/pubsub), [Datafusion](./modules/datafusion), [Bigtable instance](./modules/bigtable-instance)

View File

@ -13,6 +13,7 @@ Specific modules also offer support for non-authoritative bindings (e.g. `google
- [billing budget](./billing-budget)
- [folder](./folder)
- [logging bucket](./logging-bucket)
- [naming convention](./naming-convention)
- [organization](./organization)
- [project](./project)
- [service account](./iam-service-account)

View File

@ -0,0 +1,88 @@
# Naming Convention Module
This module allows defining a naming convention in a single place, and enforcing it by pre-creating resource names based on a set of tokens (environment, team name, etc.).
It implements a fairly common naming convention with optional prefix or suffix, but is really meant to be forked, and modified to adapt to individual use cases: just replace the `environment` and `team` variables with whatever makes sense for you, and edit the few lines in `main.tf` marked by comments.
The module also supports labels, generating sets of per-resource labels that combine the passed in tokens with optional resource-level labels.
It's completely static, using no provider resources, so its outputs are safe to use where dynamic values are not supported, like in `for_each` statements.
## Example
In its default configuration, the module supports an option prefix and suffix, and two tokens: one for the environment, and one for the team name.
```hcl
module "names-org" {
source = "./modules/naming-convention"
prefix = "myco"
environment = "dev"
team = "cloud"
resources = {
bucket = ["tf-org", "tf-sec", "tf-log"]
project = ["tf", "sec", "log"]
}
labels = {
project = {
tf = {scope = "global"}
}
}
}
module "project-tf" {
source = "./modules/project"
# myco-cloud-dev-tf
name = module.names-org.names.project.tf
# { environment = "dev", scope = "global", team = "cloud" }
labels = module.names-org.labels.project.tf
}
```
You can also enable resource type naming, useful with some legacy CMDB setups. When doing this, resource type names become part of the final resource names and are usually shorted (e.g. `prj` instead of `project`):
```hcl
module "names-org" {
source = "./modules/naming-convention"
prefix = "myco"
environment = "dev"
team = "cloud"
resources = {
bkt = ["tf-org", "tf-sec", "tf-log"]
prj = ["tf", "sec", "log"]
}
labels = {
prj = {
tf = {scope = "global"}
}
}
use_resource_prefixes = true
}
module "project-tf" {
source = "./modules/project"
# prj-myco-cloud-dev-tf
name = module.names-org.names.prj.tf
}
```
<!-- BEGIN TFDOC -->
## Variables
| name | description | type | required | default |
|---|---|:---: |:---:|:---:|
| environment | Environment abbreviation used in names and labels. | <code title="">string</code> | ✓ | |
| resources | Short resource names by type. | <code title="map&#40;list&#40;string&#41;&#41;">map(list(string))</code> | ✓ | |
| team | Optional name suffix. | <code title="">string</code> | ✓ | |
| *labels* | Per-resource labels. | <code title="map&#40;map&#40;map&#40;string&#41;&#41;&#41;">map(map(map(string)))</code> | | <code title="">{}</code> |
| *prefix* | Optional name prefix. | <code title="">string</code> | | <code title="">null</code> |
| *separator_override* | Optional separator override for specific resource types. | <code title="map&#40;string&#41;">map(string)</code> | | <code title="">{}</code> |
| *suffix* | None | <code title="">string</code> | | <code title="">null</code> |
| *use_resource_prefixes* | Prefix names with the resource type. | <code title="">bool</code> | | <code title="">false</code> |
## Outputs
| name | description | sensitive |
|---|---|:---:|
| labels | Per resource labels. | |
| names | Per resource names. | |
<!-- END TFDOC -->

View File

@ -0,0 +1,46 @@
/**
* Copyright 2021 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 {
prefix = var.prefix == null ? "" : "${var.prefix}-"
suffix = var.suffix == null ? "" : "-${var.suffix}"
// merge common and per-resource labels
labels = {
for resource_type, resources in var.resources : resource_type => {
for name in resources : name => merge(
// change this line if you need different tokens
{ environment = var.environment, team = var.team },
try(var.labels[resource_type][name], {})
)
}
}
// create per-resource names by assembling tokens
names = {
for resource_type, resources in var.resources : resource_type => {
for name in resources : name => join(
try(var.separator_override[resource_type], "-"),
compact([
var.use_resource_prefixes ? resource_type : "",
var.prefix,
// change these lines if you need different tokens
var.team,
var.environment,
name,
var.suffix
]))
}
}
}

View File

@ -0,0 +1,25 @@
/**
* Copyright 2021 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.
*/
output "labels" {
description = "Per resource labels."
value = local.labels
}
output "names" {
description = "Per resource names."
value = local.names
}

View File

@ -0,0 +1,59 @@
/**
* Copyright 2021 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.
*/
variable "environment" {
description = "Environment abbreviation used in names and labels."
type = string
}
variable "prefix" {
description = "Optional name prefix."
type = string
default = null
}
variable "labels" {
description = "Per-resource labels."
type = map(map(map(string)))
default = {}
}
variable "resources" {
description = "Short resource names by type."
type = map(list(string))
}
variable "separator_override" {
description = "Optional separator override for specific resource types."
type = map(string)
default = {}
}
variable "suffix" {
type = string
default = null
}
variable "team" {
description = "Optional name suffix."
type = string
}
variable "use_resource_prefixes" {
description = "Prefix names with the resource type."
type = bool
default = false
}

View File

@ -0,0 +1,13 @@
# Copyright 2021 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.

View File

@ -0,0 +1,65 @@
/**
* Copyright 2021 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.
*/
module "test" {
source = "../../../../modules/naming-convention"
prefix = var.prefix
suffix = var.suffix
use_resource_prefixes = var.use_resource_prefixes
environment = "dev"
team = "cloud"
resources = {
bucket = ["tf-org", "tf-sec", "tf-log"]
dataset = ["foobar", "frobniz"]
project = ["tf", "sec", "log"]
}
labels = {
project = {
tf = { scope = "global" }
}
}
separator_override = {
dataset = "_"
}
}
output "labels" {
value = module.test.labels
}
output "names" {
value = module.test.names
}
variable "prefix" {
type = string
default = null
}
variable "separator_override" {
type = map(string)
default = {}
}
variable "suffix" {
type = string
default = null
}
variable "use_resource_prefixes" {
type = bool
default = false
}

View File

@ -0,0 +1,56 @@
# Copyright 2021 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.
import os
import pytest
FIXTURES_DIR = os.path.join(os.path.dirname(__file__), 'fixture')
def test_no_prefix_suffix(apply_runner):
_, output = apply_runner(FIXTURES_DIR)
assert output['names']['project']['tf'] == 'cloud-dev-tf'
assert output['names']['bucket']['tf-org'] == 'cloud-dev-tf-org'
assert output['labels']['project']['tf'] == {
'environment': 'dev', 'scope': 'global', 'team': 'cloud'}
assert output['labels']['bucket']['tf-org'] == {
'environment': 'dev', 'team': 'cloud'}
def test_prefix(apply_runner):
_, output = apply_runner(FIXTURES_DIR, prefix='myco')
assert output['names']['project']['tf'] == 'myco-cloud-dev-tf'
assert output['names']['bucket']['tf-org'] == 'myco-cloud-dev-tf-org'
def test_suffix(apply_runner):
_, output = apply_runner(FIXTURES_DIR, suffix='myco')
assert output['names']['project']['tf'] == 'cloud-dev-tf-myco'
assert output['names']['bucket']['tf-org'] == 'cloud-dev-tf-org-myco'
def test_resource_prefix(apply_runner):
_, output = apply_runner(FIXTURES_DIR, prefix='myco',
use_resource_prefixes='true')
assert output['names']['project']['tf'] == 'project-myco-cloud-dev-tf'
assert output['names']['bucket']['tf-org'] == 'bucket-myco-cloud-dev-tf-org'
def test_separator(apply_runner):
_, output = apply_runner(
FIXTURES_DIR, separator_override='{ dataset = "_" }')
assert output['names']['dataset'] == {
'foobar': 'cloud_dev_foobar', 'frobniz': 'cloud_dev_frobniz'}