Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Avoid map-related casting errors in project factory #1836

Merged
merged 8 commits into from
Nov 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 69 additions & 14 deletions blueprints/factories/project-factory/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,9 @@ module "project-factory" {
prefix = "test-pf"
}
# location where the yaml files are read from
factory_data = {
data_path = "data"
}
factory_data_path = "data"
}
# tftest modules=6 resources=17 files=prj-app-1,prj-app-2
# tftest modules=7 resources=26 files=prj-app-1,prj-app-2,prj-app-3 inventory=example.yaml
```

```yaml
Expand All @@ -72,7 +70,8 @@ service_encryption_key_ids:
compute:
- projects/kms-central-prj/locations/europe-west3/keyRings/my-keyring/cryptoKeys/europe3-gce
services:
- storage.googleapis.com
- container.googleapis.com
- storage.googleapis.com
service_accounts:
app-1-be:
iam_project_roles:
Expand All @@ -86,29 +85,35 @@ service_accounts:

```yaml
labels:
app: app-1
app: app-2
team: foo
parent: folders/12345678
service_accounts:
app-2-be: {}
org_policies:
compute.disableGuestAttributesAccess:
rules:
- enforce: false
iam.disableServiceAccountKeyCreation:
rules:
- enforce: false
services:
- compute.googleapis.com
- run.googleapis.com
- storage.googleapis.com
shared_vpc_service_config:
host_project: foo-host

# tftest-file id=prj-app-2 path=data/prj-app-2.yaml
```

```yaml
parent: folders/12345678
services:
- run.googleapis.com
- storage.googleapis.com

# tftest-file id=prj-app-3 path=data/prj-app-3.yaml
```
<!-- BEGIN TFDOC -->
## Variables

| name | description | type | required | default |
|---|---|:---:|:---:|:---:|
| [factory_data](variables.tf#L88) | Project data from either YAML files or externally parsed data. | <code title="object&#40;&#123;&#10; data &#61; optional&#40;map&#40;any&#41;&#41;&#10; data_path &#61; optional&#40;string&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | ✓ | |
| [factory_data_path](variables.tf#L88) | Path to folder with YAML project description data files. | <code>string</code> | ✓ | |
| [data_defaults](variables.tf#L17) | Optional default values used when corresponding project data from files are missing. | <code title="object&#40;&#123;&#10; billing_account &#61; optional&#40;string&#41;&#10; contacts &#61; optional&#40;map&#40;list&#40;string&#41;&#41;, &#123;&#125;&#41;&#10; labels &#61; optional&#40;map&#40;string&#41;, &#123;&#125;&#41;&#10; metric_scopes &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; parent &#61; optional&#40;string&#41;&#10; prefix &#61; optional&#40;string&#41;&#10; service_encryption_key_ids &#61; optional&#40;map&#40;list&#40;string&#41;&#41;, &#123;&#125;&#41;&#10; service_perimeter_bridges &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; service_perimeter_standard &#61; optional&#40;string&#41;&#10; services &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; shared_vpc_service_config &#61; optional&#40;object&#40;&#123;&#10; host_project &#61; string&#10; service_identity_iam &#61; optional&#40;map&#40;list&#40;string&#41;&#41;, &#123;&#125;&#41;&#10; service_iam_grants &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; &#125;&#41;, &#123; host_project &#61; null &#125;&#41;&#10; tag_bindings &#61; optional&#40;map&#40;string&#41;, &#123;&#125;&#41;&#10; service_accounts &#61; optional&#40;map&#40;object&#40;&#123;&#10; display_name &#61; optional&#40;string, &#34;Terraform-managed.&#34;&#41;&#10; iam_project_roles &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;, &#123;&#125;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>&#123;&#125;</code> |
| [data_merges](variables.tf#L46) | Optional values that will be merged with corresponding data from files. Combines with `data_defaults`, file data, and `data_overrides`. | <code title="object&#40;&#123;&#10; contacts &#61; optional&#40;map&#40;list&#40;string&#41;&#41;, &#123;&#125;&#41;&#10; labels &#61; optional&#40;map&#40;string&#41;, &#123;&#125;&#41;&#10; metric_scopes &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; service_encryption_key_ids &#61; optional&#40;map&#40;list&#40;string&#41;&#41;, &#123;&#125;&#41;&#10; service_perimeter_bridges &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; services &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; tag_bindings &#61; optional&#40;map&#40;string&#41;, &#123;&#125;&#41;&#10; service_accounts &#61; optional&#40;map&#40;object&#40;&#123;&#10; display_name &#61; optional&#40;string, &#34;Terraform-managed.&#34;&#41;&#10; iam_project_roles &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;, &#123;&#125;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>&#123;&#125;</code> |
| [data_overrides](variables.tf#L66) | Optional values that override corresponding data from files. Takes precedence over file data and `data_defaults`. | <code title="object&#40;&#123;&#10; billing_account &#61; optional&#40;string&#41;&#10; contacts &#61; optional&#40;map&#40;list&#40;string&#41;&#41;&#41;&#10; parent &#61; optional&#40;string&#41;&#10; prefix &#61; optional&#40;string&#41;&#10; service_encryption_key_ids &#61; optional&#40;map&#40;list&#40;string&#41;&#41;&#41;&#10; service_perimeter_bridges &#61; optional&#40;list&#40;string&#41;&#41;&#10; service_perimeter_standard &#61; optional&#40;string&#41;&#10; tag_bindings &#61; optional&#40;map&#40;string&#41;&#41;&#10; services &#61; optional&#40;list&#40;string&#41;&#41;&#10; service_accounts &#61; optional&#40;map&#40;object&#40;&#123;&#10; display_name &#61; optional&#40;string, &#34;Terraform-managed.&#34;&#41;&#10; iam_project_roles &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>&#123;&#125;</code> |
Expand All @@ -120,3 +125,53 @@ shared_vpc_service_config:
| [projects](outputs.tf#L17) | Project module outputs. | |
| [service_accounts](outputs.tf#L22) | Service account emails. | |
<!-- END TFDOC -->
## Tests

These tests validate fixes to the project factory.

```hcl
module "project-factory" {
source = "./fabric/blueprints/factories/project-factory"
data_defaults = {
billing_account = "012345-67890A-ABCDEF"
}
data_merges = {
labels = {
owner = "foo"
}
services = [
"compute.googleapis.com"
]
}
data_overrides = {
prefix = "foo"
}
factory_data_path = "data"
}
# tftest modules=4 resources=14 files=test-0,test-1,test-2
```

```yaml
parent: folders/1234567890
services:
- iam.googleapis.com
- contactcenteraiplatform.googleapis.com
- container.googleapis.com
# tftest-file id=test-0 path=data/test-0.yaml
```

```yaml
parent: folders/1234567890
services:
- iam.googleapis.com
- contactcenteraiplatform.googleapis.com
# tftest-file id=test-1 path=data/test-1.yaml
```

```yaml
parent: folders/1234567890
services:
- iam.googleapis.com
- storage.googleapis.com
# tftest-file id=test-2 path=data/test-2.yaml
```
25 changes: 12 additions & 13 deletions blueprints/factories/project-factory/factory.tf
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,18 @@
*/

locals {
_data = (
var.factory_data.data != null
? var.factory_data.data
: {
for f in fileset("${local._data_path}", "**/*.yaml") :
trimsuffix(f, ".yaml") => yamldecode(file("${local._data_path}/${f}"))
}
)
_data_path = var.factory_data.data_path == null ? null : pathexpand(
var.factory_data.data_path
)
# turn the set of file names into a list for index-based access later on
_factory_files = [
for f in fileset("${pathexpand(var.factory_data_path)}", "**/*.yaml") : f
]
# use a list to store data to avoid map enforcing the same type for all
_factory_data = [
for f in local._factory_files :
yamldecode(file("${pathexpand(var.factory_data_path)}/${f}"))
]
# assemble final data, emulating optionals and using defaults and merges
projects = {
for k, v in local._data : k => merge(v, {
for i, v in local._factory_data : trimsuffix(local._factory_files[i], ".yaml") => {
billing_account = try(coalesce(
var.data_overrides.billing_account,
try(v.billing_account, null),
Expand Down Expand Up @@ -96,7 +95,7 @@ locals {
try(v.service_accounts, null),
var.data_defaults.service_accounts
)
})
}
}
service_accounts = flatten([
for k, v in local.projects : [
Expand Down
18 changes: 4 additions & 14 deletions blueprints/factories/project-factory/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -85,18 +85,8 @@ variable "data_overrides" {
default = {}
}

variable "factory_data" {
description = "Project data from either YAML files or externally parsed data."
type = object({
data = optional(map(any))
data_path = optional(string)
})
nullable = false
validation {
condition = (
(var.factory_data.data != null ? 1 : 0) +
(var.factory_data.data_path != null ? 1 : 0)
) == 1
error_message = "One of data or data_path needs to be set."
}
variable "factory_data_path" {
description = "Path to folder with YAML project description data files."
type = string
nullable = false
}
4 changes: 2 additions & 2 deletions fast/stages/3-project-factory/dev/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,8 @@ terraform apply
| name | description | type | required | default | producer |
|---|---|:---:|:---:|:---:|:---:|
| [billing_account](variables.tf#L19) | Billing account id. If billing account is not part of the same org set `is_org_level` to false. | <code title="object&#40;&#123;&#10; id &#61; string&#10; is_org_level &#61; optional&#40;bool, true&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | ✓ | | <code>0-bootstrap</code> |
| [prefix](variables.tf#L51) | Prefix used for resources that need unique names. Use 9 characters or less. | <code>string</code> | ✓ | | <code>0-bootstrap</code> |
| [factory_data](variables.tf#L32) | Project data from either YAML files or externally parsed data. | <code title="object&#40;&#123;&#10; data &#61; optional&#40;map&#40;any&#41;&#41;&#10; data_path &#61; optional&#40;string&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code title="&#123;&#10; data_path &#61; &#34;data&#47;projects&#34;&#10;&#125;">&#123;&#8230;&#125;</code> | |
| [prefix](variables.tf#L39) | Prefix used for resources that need unique names. Use 9 characters or less. | <code>string</code> | ✓ | | <code>0-bootstrap</code> |
| [factory_data_path](variables.tf#L32) | Path to folder containing YAML project data files. | <code>string</code> | | <code>&#34;data&#47;projects&#34;</code> | |

## Outputs

Expand Down
2 changes: 1 addition & 1 deletion fast/stages/3-project-factory/dev/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ module "projects" {
data_overrides = {
prefix = "${var.prefix}-dev"
}
factory_data = var.factory_data
factory_data_path = var.factory_data_path
}


22 changes: 5 additions & 17 deletions fast/stages/3-project-factory/dev/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -29,23 +29,11 @@ variable "billing_account" {
}
}

variable "factory_data" {
description = "Project data from either YAML files or externally parsed data."
type = object({
data = optional(map(any))
data_path = optional(string)
})
nullable = false
default = {
data_path = "data/projects"
}
validation {
condition = (
(var.factory_data.data != null ? 1 : 0) +
(var.factory_data.data_path != null ? 1 : 0)
) == 1
error_message = "One of data or data_path needs to be set."
}
variable "factory_data_path" {
description = "Path to folder containing YAML project data files."
type = string
nullable = false
default = "data/projects"
}

variable "prefix" {
Expand Down
Loading
Loading