diff --git a/modules/cloud-config-container/coredns/README.md b/modules/cloud-config-container/coredns/README.md
index 633a51345..5f8d57195 100644
--- a/modules/cloud-config-container/coredns/README.md
+++ b/modules/cloud-config-container/coredns/README.md
@@ -75,12 +75,12 @@ module "cos-coredns" {
| name | description | type | required | default |
|---|---|:---: |:---:|:---:|
| *cloud_config* | Cloud config template path. If null default will be used. | string | | null |
-| *config_variables* | Additional variables used to render the cloud-config template. | map(any) | | {} |
+| *config_variables* | Additional variables used to render the cloud-config and CoreDNS templates. | map(any) | | {} |
| *coredns_config* | CoreDNS configuration path, if null default will be used. | string | | null |
| *file_defaults* | Default owner and permissions for files. | object({...}) | | ... |
| *files* | Map of extra files to create on the instance, path as key. Owner and permissions will use defaults if null. | map(object({...})) | | {} |
| *test_instance* | Test/development instance attributes, leave null to skip creation. | object({...}) | | null |
-| *test_instance_defaults* | Test/development instance defaults used for optional configuration. | object({...}) | | ... |
+| *test_instance_defaults* | Test/development instance defaults used for optional configuration. If image is null, COS stable will be used. | object({...}) | | ... |
## Outputs
diff --git a/modules/cloud-config-container/mysql/README.md b/modules/cloud-config-container/mysql/README.md
index c960ccfb4..1ba160b01 100644
--- a/modules/cloud-config-container/mysql/README.md
+++ b/modules/cloud-config-container/mysql/README.md
@@ -87,7 +87,7 @@ module "cos-mysql" {
| *mysql_config* | MySQL configuration file content, if null container default will be used. | string | | null |
| *mysql_data_disk* | MySQL data disk name in /dev/disk/by-id/ including the google- prefix. If null the boot disk will be used for data. | string | | null |
| *test_instance* | Test/development instance attributes, leave null to skip creation. | object({...}) | | null |
-| *test_instance_defaults* | Test/development instance defaults used for optional configuration. | object({...}) | | ... |
+| *test_instance_defaults* | Test/development instance defaults used for optional configuration. If image is null, COS stable will be used. | object({...}) | | ... |
## Outputs
diff --git a/modules/cloud-config-container/onprem/README.md b/modules/cloud-config-container/onprem/README.md
index 334fe0728..260aad1bb 100644
--- a/modules/cloud-config-container/onprem/README.md
+++ b/modules/cloud-config-container/onprem/README.md
@@ -65,10 +65,11 @@ module "on-prem" {
| name | description | type | required | default |
|---|---|:---: |:---:|:---:|
| vpn_config | VPN configuration, type must be one of 'dynamic' or 'static'. | object({...}) | ✓ | |
+| *config_variables* | Additional variables used to render the cloud-config and CoreDNS templates. | map(any) | | {} |
| *coredns_config* | CoreDNS configuration path, if null default will be used. | string | | null |
| *local_ip_cidr_range* | IP CIDR range used for the Docker onprem network. | string | | 192.168.192.0/24 |
| *test_instance* | Test/development instance attributes, leave null to skip creation. | object({...}) | | null |
-| *test_instance_defaults* | Test/development instance defaults used for optional configuration. | object({...}) | | ... |
+| *test_instance_defaults* | Test/development instance defaults used for optional configuration. If image is null, COS stable will be used. | object({...}) | | ... |
| *vpn_dynamic_config* | BGP configuration for dynamic VPN, ignored if VPN type is 'static'. | object({...}) | | ... |
| *vpn_static_ranges* | Remote CIDR ranges for static VPN, ignored if VPN type is 'dynamic'. | list(string) | | ["10.0.0.0/8"] |
diff --git a/modules/folders/README.md b/modules/folders/README.md
index e12947a45..b1568711d 100644
--- a/modules/folders/README.md
+++ b/modules/folders/README.md
@@ -1,8 +1,10 @@
# Google Cloud Folder Module
-This module allow creation and management of sets of folders sharing a common parent, and their individual IAM bindings.
+This module allow creation and management of sets of folders sharing a common parent, and their individual IAM bindings. It also allows setting a common set of organization policies on all folders.
-## Example
+## Examples
+
+### IAM bindings
```hcl
module "folder" {
@@ -20,6 +22,28 @@ module "folder" {
}
```
+### Organization policies
+
+```hcl
+module "folder" {
+ source = "./modules/folder"
+ parent = "organizations/1234567890"
+ names = ["Folder one", "Folder two]
+ policy_boolean = {
+ "constraints/compute.disableGuestAttributesAccess" = true
+ "constraints/compute.skipDefaultNetworkCreation" = true
+ }
+ policy_list = {
+ "constraints/compute.trustedImageProjects" = {
+ inherit_from_parent = null
+ suggested_value = null
+ status = true
+ values = ["projects/my-project"]
+ }
+ }
+}
+```
+
## Variables
@@ -29,6 +53,8 @@ module "folder" {
| *iam_members* | List of IAM members keyed by folder name and role. | map(map(list(string))) | | null |
| *iam_roles* | List of IAM roles keyed by folder name. | map(list(string)) | | null |
| *names* | Folder names. | list(string) | | [] |
+| *policy_boolean* | Map of boolean org policies and enforcement value, set value to null for policy restore. | map(bool) | | {} |
+| *policy_list* | Map of list org policies, status is true for allow, false for deny, null for restore. Values can only be used for allow or deny. | map(object({...})) | | {} |
## Outputs
diff --git a/modules/folders/main.tf b/modules/folders/main.tf
index 228a195bf..e144726b8 100644
--- a/modules/folders/main.tf
+++ b/modules/folders/main.tf
@@ -31,6 +31,22 @@ locals {
"${pair.name}-${pair.role}" => pair
}
iam_members = var.iam_members == null ? {} : var.iam_members
+ policy_boolean_pairs = {
+ for pair in setproduct(var.names, keys(var.policy_boolean)) :
+ "${pair.0}-${pair.1}" => {
+ folder = pair.0,
+ policy = pair.1,
+ policy_data = var.policy_boolean[pair.1]
+ }
+ }
+ policy_list_pairs = {
+ for pair in setproduct(var.names, keys(var.policy_list)) :
+ "${pair.0}-${pair.1}" => {
+ folder = pair.0,
+ policy = pair.1,
+ policy_data = var.policy_list[pair.1]
+ }
+ }
}
resource "google_folder" "folders" {
@@ -48,3 +64,75 @@ resource "google_folder_iam_binding" "authoritative" {
)
}
+resource "google_folder_organization_policy" "boolean" {
+ for_each = local.policy_boolean_pairs
+ folder = google_folder.folders[each.value.folder].id
+ constraint = each.value.policy
+
+ dynamic boolean_policy {
+ for_each = each.value.policy_data == null ? [] : [each.value.policy_data]
+ iterator = policy
+ content {
+ enforced = policy.value
+ }
+ }
+
+ dynamic restore_policy {
+ for_each = each.value.policy_data == null ? [""] : []
+ content {
+ default = true
+ }
+ }
+}
+
+resource "google_folder_organization_policy" "list" {
+ for_each = local.policy_list_pairs
+ folder = google_folder.folders[each.value.folder].id
+ constraint = each.value.policy
+
+ dynamic list_policy {
+ for_each = each.value.policy_data.status == null ? [] : [each.value.policy_data]
+ iterator = policy
+ content {
+ inherit_from_parent = policy.value.inherit_from_parent
+ suggested_value = policy.value.suggested_value
+ dynamic allow {
+ for_each = policy.value.status ? [""] : []
+ content {
+ values = (
+ try(length(policy.value.values) > 0, false)
+ ? policy.value.values
+ : null
+ )
+ all = (
+ try(length(policy.value.values) > 0, false)
+ ? null
+ : true
+ )
+ }
+ }
+ dynamic deny {
+ for_each = policy.value.status ? [] : [""]
+ content {
+ values = (
+ try(length(policy.value.values) > 0, false)
+ ? policy.value.values
+ : null
+ )
+ all = (
+ try(length(policy.value.values) > 0, false)
+ ? null
+ : true
+ )
+ }
+ }
+ }
+ }
+
+ dynamic restore_policy {
+ for_each = each.value.policy_data.status == null ? [true] : []
+ content {
+ default = true
+ }
+ }
+}
diff --git a/modules/folders/outputs.tf b/modules/folders/outputs.tf
index c7d061500..cf7b54ca8 100644
--- a/modules/folders/outputs.tf
+++ b/modules/folders/outputs.tf
@@ -22,6 +22,11 @@ output "folder" {
output "id" {
description = "Folder id (for single use)."
value = local.has_folders ? local.folders[0].name : null
+ depends_on = [
+ google_folder_iam_binding.authoritative,
+ google_folder_organization_policy.boolean,
+ google_folder_organization_policy.list
+ ]
}
output "name" {
@@ -41,6 +46,11 @@ output "ids" {
? zipmap(var.names, [for f in local.folders : f.name])
: {}
)
+ depends_on = [
+ google_folder_iam_binding.authoritative,
+ google_folder_organization_policy.boolean,
+ google_folder_organization_policy.list
+ ]
}
output "names" {
@@ -55,6 +65,11 @@ output "names" {
output "ids_list" {
description = "List of folder ids."
value = [for f in local.folders : f.name]
+ depends_on = [
+ google_folder_iam_binding.authoritative,
+ google_folder_organization_policy.boolean,
+ google_folder_organization_policy.list
+ ]
}
output "names_list" {
diff --git a/modules/folders/variables.tf b/modules/folders/variables.tf
index 742031633..3dbf502b4 100644
--- a/modules/folders/variables.tf
+++ b/modules/folders/variables.tf
@@ -36,3 +36,20 @@ variable "parent" {
description = "Parent in folders/folder_id or organizations/org_id format."
type = string
}
+
+variable "policy_boolean" {
+ description = "Map of boolean org policies and enforcement value, set value to null for policy restore."
+ type = map(bool)
+ default = {}
+}
+
+variable "policy_list" {
+ description = "Map of list org policies, status is true for allow, false for deny, null for restore. Values can only be used for allow or deny."
+ type = map(object({
+ inherit_from_parent = bool
+ suggested_value = string
+ status = bool
+ values = list(string)
+ }))
+ default = {}
+}
diff --git a/modules/project/README.md b/modules/project/README.md
index 2d79bd688..a82836321 100644
--- a/modules/project/README.md
+++ b/modules/project/README.md
@@ -1,23 +1,54 @@
# Project Module
-## Example
+## Examples
+
+### Minimal example with IAM
```hcl
module "project" {
source = "./modules/project"
- parent = var.folder.id
- billing_account = var.billing_account_id
- prefix = "foo"
+ billing_account = "123456-123456-123456"
name = "project-example"
- oslogin = true
- oslogin_admins = var.admins
- services = concat(var.project_services, [
- "cloudkms.googleapis.com", "accesscontextmanager.googleapis.com"
- ])
+ parent = "folders/1234567890"
+ prefix = "foo"
+ services = [
+ "resourceviews.googleapis.com",
+ "stackdriver.googleapis.com"
+ ]
iam_roles = ["roles/container.hostServiceAgentUser"]
- iam_members = { "roles/container.hostServiceAgentUser" = [
- "serviceAccount:${var.gke_service_account}"
- ] }
+ iam_members = {
+ "roles/container.hostServiceAgentUser" = [
+ "serviceAccount:${var.gke_service_account}"
+ ]
+ }
+}
+```
+
+### Organization policies
+
+```hcl
+module "project" {
+ source = "./modules/project"
+ billing_account = "123456-123456-123456"
+ name = "project-example"
+ parent = "folders/1234567890"
+ prefix = "foo"
+ services = [
+ "resourceviews.googleapis.com",
+ "stackdriver.googleapis.com"
+ ]
+ policy_boolean = {
+ "constraints/compute.disableGuestAttributesAccess" = true
+ "constraints/compute.skipDefaultNetworkCreation" = true
+ }
+ policy_list = {
+ "constraints/compute.trustedImageProjects" = {
+ inherit_from_parent = null
+ suggested_value = null
+ status = true
+ values = ["projects/my-project"]
+ }
+ }
}
```
@@ -40,6 +71,8 @@ module "project" {
| *oslogin* | Enable OS Login. | bool | | false |
| *oslogin_admins* | List of IAM-style identities that will be granted roles necessary for OS Login administrators. | list(string) | | [] |
| *oslogin_users* | List of IAM-style identities that will be granted roles necessary for OS Login users. | list(string) | | [] |
+| *policy_boolean* | Map of boolean org policies and enforcement value, set value to null for policy restore. | map(bool) | | {} |
+| *policy_list* | Map of list org policies, status is true for allow, false for deny, null for restore. Values can only be used for allow or deny. | map(object({...})) | | {} |
| *prefix* | Prefix used to generate project id and name. | string | | null |
| *services* | Service APIs to enable. | list(string) | | [] |
@@ -47,13 +80,12 @@ module "project" {
| name | description | sensitive |
|---|---|:---:|
-| cloudsvc_service_account | Cloud services service account (depends on services). | |
+| cloudsvc_service_account | Cloud services service account. | |
| custom_roles | Ids of the created custom roles. | |
-| gce_service_account | Default GCE service account (depends on services). | |
-| gcr_service_account | Default GCR service account (depends on services). | |
-| gke_service_account | Default GKE service account (depends on services). | |
-| iam_project_id | Project id (depends on services and IAM bindings). | |
-| name | Name (depends on services). | |
-| number | Project number (depends on services). | |
-| project_id | Project id (depends on services). | |
+| gce_service_account | Default GCE service account. | |
+| gcr_service_account | Default GCR service account. | |
+| gke_service_account | Default GKE service account. | |
+| name | Project ame. | |
+| number | Project number. | |
+| project_id | Project id. | |
diff --git a/modules/project/main.tf b/modules/project/main.tf
index e6d8bab67..7e4aaeb0b 100644
--- a/modules/project/main.tf
+++ b/modules/project/main.tf
@@ -88,6 +88,10 @@ resource "google_project_iam_binding" "authoritative" {
project = google_project.project.project_id
role = each.value
members = lookup(var.iam_members, each.value, [])
+ depends_on = [
+ google_project_service.project_services,
+ google_project_iam_custom_role.roles
+ ]
}
resource "google_project_iam_member" "additive" {
@@ -124,3 +128,76 @@ resource "google_project_iam_member" "oslogin_users" {
role = "roles/compute.osLogin"
member = each.value
}
+
+resource "google_project_organization_policy" "boolean" {
+ for_each = var.policy_boolean
+ project = google_project.project.project_id
+ constraint = each.key
+
+ dynamic boolean_policy {
+ for_each = each.value == null ? [] : [each.value]
+ iterator = policy
+ content {
+ enforced = policy.value
+ }
+ }
+
+ dynamic restore_policy {
+ for_each = each.value == null ? [""] : []
+ content {
+ default = true
+ }
+ }
+}
+
+resource "google_project_organization_policy" "list" {
+ for_each = var.policy_list
+ project = google_project.project.project_id
+ constraint = each.key
+
+ dynamic list_policy {
+ for_each = each.value.status == null ? [] : [each.value]
+ iterator = policy
+ content {
+ inherit_from_parent = policy.value.inherit_from_parent
+ suggested_value = policy.value.suggested_value
+ dynamic allow {
+ for_each = policy.value.status ? [""] : []
+ content {
+ values = (
+ try(length(policy.value.values) > 0, false)
+ ? policy.value.values
+ : null
+ )
+ all = (
+ try(length(policy.value.values) > 0, false)
+ ? null
+ : true
+ )
+ }
+ }
+ dynamic deny {
+ for_each = policy.value.status ? [] : [""]
+ content {
+ values = (
+ try(length(policy.value.values) > 0, false)
+ ? policy.value.values
+ : null
+ )
+ all = (
+ try(length(policy.value.values) > 0, false)
+ ? null
+ : true
+ )
+ }
+ }
+ }
+ }
+
+ dynamic restore_policy {
+ for_each = each.value.status == null ? [true] : []
+ content {
+ default = true
+ }
+ }
+}
diff --git a/modules/project/outputs.tf b/modules/project/outputs.tf
index 7cb702174..c07516770 100644
--- a/modules/project/outputs.tf
+++ b/modules/project/outputs.tf
@@ -15,55 +15,55 @@
*/
output "project_id" {
- description = "Project id (depends on services)."
+ description = "Project id."
value = google_project.project.project_id
depends_on = [
+ google_project_organization_policy.boolean,
+ google_project_organization_policy.list,
google_project_service.project_services
]
}
-output "iam_project_id" {
- description = "Project id (depends on services and IAM bindings)."
- value = google_project.project.project_id
+output "name" {
+ description = "Project ame."
+ value = google_project.project.name
depends_on = [
- google_project_service.project_services,
- google_project_iam_binding.authoritative,
- google_project_iam_member.non_authoritative
+ google_project_organization_policy.boolean,
+ google_project_organization_policy.list,
+ google_project_service.project_services
]
}
-output "name" {
- description = "Name (depends on services)."
- value = google_project.project.name
- depends_on = [google_project_service.project_services]
-}
-
output "number" {
- description = "Project number (depends on services)."
+ description = "Project number."
value = google_project.project.number
- depends_on = [google_project_service.project_services]
+ depends_on = [
+ google_project_organization_policy.boolean,
+ google_project_organization_policy.list,
+ google_project_service.project_services
+ ]
}
output "cloudsvc_service_account" {
- description = "Cloud services service account (depends on services)."
+ description = "Cloud services service account."
value = "${local.cloudsvc_service_account}"
depends_on = [google_project_service.project_services]
}
output "gce_service_account" {
- description = "Default GCE service account (depends on services)."
+ description = "Default GCE service account."
value = local.gce_service_account
depends_on = [google_project_service.project_services]
}
output "gcr_service_account" {
- description = "Default GCR service account (depends on services)."
+ description = "Default GCR service account."
value = local.gcr_service_account
depends_on = [google_project_service.project_services]
}
output "gke_service_account" {
- description = "Default GKE service account (depends on services)."
+ description = "Default GKE service account."
value = local.gke_service_account
depends_on = [google_project_service.project_services]
}
diff --git a/modules/project/variables.tf b/modules/project/variables.tf
index b9ecc8645..fc6e12ab9 100644
--- a/modules/project/variables.tf
+++ b/modules/project/variables.tf
@@ -96,6 +96,23 @@ variable "parent" {
type = string
}
+variable "policy_boolean" {
+ description = "Map of boolean org policies and enforcement value, set value to null for policy restore."
+ type = map(bool)
+ default = {}
+}
+
+variable "policy_list" {
+ description = "Map of list org policies, status is true for allow, false for deny, null for restore. Values can only be used for allow or deny."
+ type = map(object({
+ inherit_from_parent = bool
+ suggested_value = string
+ status = bool
+ values = list(string)
+ }))
+ default = {}
+}
+
variable "prefix" {
description = "Prefix used to generate project id and name."
type = string
diff --git a/tests/modules/folders/fixture/main.tf b/tests/modules/folders/fixture/main.tf
index 4848ec5aa..71d13cd27 100644
--- a/tests/modules/folders/fixture/main.tf
+++ b/tests/modules/folders/fixture/main.tf
@@ -15,9 +15,11 @@
*/
module "test" {
- source = "../../../../modules/folders"
- parent = "organizations/12345678"
- names = ["folder-a", "folder-b"]
- iam_members = var.iam_members
- iam_roles = var.iam_roles
+ source = "../../../../modules/folders"
+ parent = "organizations/12345678"
+ names = ["folder-a", "folder-b"]
+ iam_members = var.iam_members
+ iam_roles = var.iam_roles
+ policy_boolean = var.policy_boolean
+ policy_list = var.policy_list
}
diff --git a/tests/modules/folders/fixture/variables.tf b/tests/modules/folders/fixture/variables.tf
index 02fb11081..d8a71531a 100644
--- a/tests/modules/folders/fixture/variables.tf
+++ b/tests/modules/folders/fixture/variables.tf
@@ -23,3 +23,18 @@ variable "iam_roles" {
type = map(list(string))
default = null
}
+
+variable "policy_boolean" {
+ type = map(bool)
+ default = {}
+}
+
+variable "policy_list" {
+ type = map(object({
+ inherit_from_parent = bool
+ suggested_value = string
+ status = bool
+ values = list(string)
+ }))
+ default = {}
+}
diff --git a/tests/modules/folders/test_plan_org_policies.py b/tests/modules/folders/test_plan_org_policies.py
new file mode 100644
index 000000000..dde2b30c8
--- /dev/null
+++ b/tests/modules/folders/test_plan_org_policies.py
@@ -0,0 +1,86 @@
+# Copyright 2020 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_policy_boolean(plan_runner):
+ "Test boolean folder policy."
+ policy_boolean = '{policy-a = true, policy-b = false, policy-c = null}'
+ _, resources = plan_runner(FIXTURES_DIR, policy_boolean=policy_boolean)
+ assert len(resources) == 8
+ resources = [r for r in resources if r['type']
+ == 'google_folder_organization_policy']
+ assert sorted([r['index'] for r in resources]) == [
+ 'folder-a-policy-a',
+ 'folder-a-policy-b',
+ 'folder-a-policy-c',
+ 'folder-b-policy-a',
+ 'folder-b-policy-b',
+ 'folder-b-policy-c'
+ ]
+ policy_values = []
+ for resource in resources:
+ for policy in ('boolean_policy', 'restore_policy'):
+ value = resource['values'][policy]
+ if value:
+ policy_values.append((resource['index'], policy,) + value[0].popitem())
+ assert sorted(policy_values) == [
+ ('folder-a-policy-a', 'boolean_policy', 'enforced', True),
+ ('folder-a-policy-b', 'boolean_policy', 'enforced', False),
+ ('folder-a-policy-c', 'restore_policy', 'default', True),
+ ('folder-b-policy-a', 'boolean_policy', 'enforced', True),
+ ('folder-b-policy-b', 'boolean_policy', 'enforced', False),
+ ('folder-b-policy-c', 'restore_policy', 'default', True)
+ ]
+
+
+def test_policy_list(plan_runner):
+ "Test list org policy."
+ policy_list = (
+ '{'
+ 'policy-a = {inherit_from_parent = true, suggested_value = null, status = true, values = []}, '
+ 'policy-b = {inherit_from_parent = null, suggested_value = "foo", status = false, values = ["bar"]}, '
+ 'policy-c = {inherit_from_parent = null, suggested_value = true, status = null, values = null}'
+ '}'
+ )
+ _, resources = plan_runner(FIXTURES_DIR, policy_list=policy_list)
+ assert len(resources) == 8
+ resources = [r for r in resources if r['type']
+ == 'google_folder_organization_policy']
+ assert sorted([r['index'] for r in resources]) == [
+ 'folder-a-policy-a',
+ 'folder-a-policy-b',
+ 'folder-a-policy-c',
+ 'folder-b-policy-a',
+ 'folder-b-policy-b',
+ 'folder-b-policy-c'
+ ]
+ values = [r['values'] for r in resources]
+ assert [r['constraint'] for r in values] == [
+ 'policy-a', 'policy-b', 'policy-c', 'policy-a', 'policy-b', 'policy-c'
+ ]
+ for i in (0, 3):
+ assert values[i]['list_policy'][0]['allow'] == [
+ {'all': True, 'values': None}]
+ for i in (1, 4):
+ assert values[i]['list_policy'][0]['deny'] == [
+ {'all': False, 'values': ["bar"]}]
+ for i in (2, 5):
+ assert values[i]['restore_policy'] == [{'default': True}]
diff --git a/tests/modules/project/fixture/main.tf b/tests/modules/project/fixture/main.tf
index 91a838f63..8d8808c22 100644
--- a/tests/modules/project/fixture/main.tf
+++ b/tests/modules/project/fixture/main.tf
@@ -30,6 +30,8 @@ module "test" {
oslogin_admins = var.oslogin_admins
oslogin_users = var.oslogin_users
parent = var.parent
+ policy_boolean = var.policy_boolean
+ policy_list = var.policy_list
prefix = var.prefix
services = var.services
}
diff --git a/tests/modules/project/fixture/variables.tf b/tests/modules/project/fixture/variables.tf
index bcf7da90c..3c467da10 100644
--- a/tests/modules/project/fixture/variables.tf
+++ b/tests/modules/project/fixture/variables.tf
@@ -74,6 +74,21 @@ variable "parent" {
default = "folders/12345678"
}
+variable "policy_boolean" {
+ type = map(bool)
+ default = {}
+}
+
+variable "policy_list" {
+ type = map(object({
+ inherit_from_parent = bool
+ suggested_value = string
+ status = bool
+ values = list(string)
+ }))
+ default = {}
+}
+
variable "prefix" {
type = string
default = null
diff --git a/tests/modules/project/test_plan_org_policies.py b/tests/modules/project/test_plan_org_policies.py
new file mode 100644
index 000000000..9f88a2eac
--- /dev/null
+++ b/tests/modules/project/test_plan_org_policies.py
@@ -0,0 +1,66 @@
+# Copyright 2020 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_policy_boolean(plan_runner):
+ "Test boolean org policy."
+ policy_boolean = '{policy-a = true, policy-b = false, policy-c = null}'
+ _, resources = plan_runner(FIXTURES_DIR, policy_boolean=policy_boolean)
+ assert len(resources) == 4
+ resources = [r for r in resources if r['type']
+ == 'google_project_organization_policy']
+ assert sorted([r['index'] for r in resources]) == [
+ 'policy-a', 'policy-b', 'policy-c'
+ ]
+ policy_values = []
+ for resource in resources:
+ for policy in ('boolean_policy', 'restore_policy'):
+ value = resource['values'][policy]
+ if value:
+ policy_values.append((policy,) + value[0].popitem())
+ assert sorted(policy_values) == [
+ ('boolean_policy', 'enforced', False),
+ ('boolean_policy', 'enforced', True),
+ ('restore_policy', 'default', True)
+ ]
+
+
+def test_policy_list(plan_runner):
+ "Test list org policy."
+ policy_list = (
+ '{'
+ 'policy-a = {inherit_from_parent = true, suggested_value = null, status = true, values = []}, '
+ 'policy-b = {inherit_from_parent = null, suggested_value = "foo", status = false, values = ["bar"]}, '
+ 'policy-c = {inherit_from_parent = null, suggested_value = true, status = null, values = null}'
+ '}'
+ )
+ _, resources = plan_runner(FIXTURES_DIR, policy_list=policy_list)
+ assert len(resources) == 4
+ values = [r['values'] for r in resources if r['type']
+ == 'google_project_organization_policy']
+ assert [r['constraint'] for r in values] == [
+ 'policy-a', 'policy-b', 'policy-c'
+ ]
+ assert values[0]['list_policy'][0]['allow'] == [
+ {'all': True, 'values': None}]
+ assert values[1]['list_policy'][0]['deny'] == [
+ {'all': False, 'values': ["bar"]}]
+ assert values[2]['restore_policy'] == [{'default': True}]