From a280dd880d54529ba3af381c995e1e949a35a0b4 Mon Sep 17 00:00:00 2001 From: Ludovico Magnocavallo Date: Wed, 8 Apr 2020 14:54:49 +0200 Subject: [PATCH] Add support for org policies to folder and project modules (#58) * modules/folders: add support for org policies * update README * update cloud config modules READMEs * modules/project: add org policies --- .../cloud-config-container/coredns/README.md | 4 +- .../cloud-config-container/mysql/README.md | 2 +- .../cloud-config-container/onprem/README.md | 3 +- modules/folders/README.md | 30 ++++++- modules/folders/main.tf | 88 +++++++++++++++++++ modules/folders/outputs.tf | 15 ++++ modules/folders/variables.tf | 17 ++++ modules/project/README.md | 72 ++++++++++----- modules/project/main.tf | 77 ++++++++++++++++ modules/project/outputs.tf | 38 ++++---- modules/project/variables.tf | 17 ++++ tests/modules/folders/fixture/main.tf | 12 +-- tests/modules/folders/fixture/variables.tf | 15 ++++ .../modules/folders/test_plan_org_policies.py | 86 ++++++++++++++++++ tests/modules/project/fixture/main.tf | 2 + tests/modules/project/fixture/variables.tf | 15 ++++ .../modules/project/test_plan_org_policies.py | 66 ++++++++++++++ 17 files changed, 509 insertions(+), 50 deletions(-) create mode 100644 tests/modules/folders/test_plan_org_policies.py create mode 100644 tests/modules/project/test_plan_org_policies.py diff --git a/modules/cloud-config-container/coredns/README.md b/modules/cloud-config-container/coredns/README.md index 633a5134..5f8d5719 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 c960ccfb..1ba160b0 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 334fe072..260aad1b 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 e12947a4..b1568711 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 228a195b..e144726b 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 c7d06150..cf7b54ca 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 74203163..3dbf502b 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 2d79bd68..a8283632 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 e6d8bab6..7e4aaeb0 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 7cb70217..c0751677 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 b9ecc864..fc6e12ab 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 4848ec5a..71d13cd2 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 02fb1108..d8a71531 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 00000000..dde2b30c --- /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 91a838f6..8d8808c2 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 bcf7da90..3c467da1 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 00000000..9f88a2ea --- /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}]