diff --git a/.terraform-version b/.terraform-version index 8decb92..66beabb 100644 --- a/.terraform-version +++ b/.terraform-version @@ -1 +1 @@ -1.8.5 +1.9.8 diff --git a/README.md b/README.md index 102d5c3..aabbd51 100644 --- a/README.md +++ b/README.md @@ -10,15 +10,14 @@ Creates an RDS database based on the `rds_plan_name` variable and outputs the `i ``` module "database" { - source = "github.com/GSA-TTS/terraform-cloudgov//database?ref=v1.1.0" + source = "github.com/GSA-TTS/terraform-cloudgov//database?ref=v2.0.0" - cf_org_name = local.cf_org_name - cf_space_name = local.cf_space_name - name = "database_name" - rds_plan_name = "micro-psql" - tags = ["tag1", "tag2"] + cf_space_id = data.cloudfoundry_space.app_space.id + name = "database_name" + rds_plan_name = "micro-psql" + tags = ["tag1", "tag2"] # See options at https://cloud.gov/docs/services/relational-database/#setting-optional-parameters-1 - json_params = jsonencode( + json_params = jsonencode( { "storage" : 10, } @@ -32,17 +31,16 @@ Creates a Elasticache redis instance and outputs the `instance_id` for use elsew ``` module "redis" { - source = "github.com/GSA-TTS/terraform-cloudgov//redis?ref=v1.1.0" + source = "github.com/GSA-TTS/terraform-cloudgov//redis?ref=v2.0.0" - cf_org_name = local.cf_org_name - cf_space_name = local.cf_space_name - name = "redis_name" - redis_plan_name = "redis-dev" - tags = ["tag1", "tag2"] + cf_space_id = data.cloudfoundry_space.app_space.id + name = "redis_name" + redis_plan_name = "redis-dev" + tags = ["tag1", "tag2"] # See options at https://cloud.gov/docs/services/aws-elasticache/#setting-optional-parameters - json_params = jsonencode( + json_params = jsonencode( { - "engineVersion" : "6.2", + "engineVersion" : "7.0", } ) } @@ -54,14 +52,13 @@ Creates an s3 bucket and outputs the `bucket_id` for use elsewhere. ``` module "s3" { - source = "github.com/GSA-TTS/terraform-cloudgov//s3?ref=v1.1.0" + source = "github.com/GSA-TTS/terraform-cloudgov//s3?ref=v2.0.0" - cf_org_name = local.cf_org_name - cf_space_name = local.cf_space_name - name = "${local.app_name}-s3-${local.env}" - tags = ["tag1", "tag2"] + cf_space_id = data.cloudfoundry_space.app_space.id + name = "${local.app_name}-s3-${local.env}" + tags = ["tag1", "tag2"] # See options at https://cloud.gov/docs/services/s3/#setting-optional-parameters - json_params = jsonencode( + json_params = jsonencode( { "object_ownership" : "ObjectWriter", } @@ -75,19 +72,19 @@ Connects a custom domain name or domain name with CDN to an already running appl Note that the domain must be created in cloud.gov by an OrgManager before this module is included. -`cf create-domain CLOUD_GOV_ORG my-production-domain-name` +`cf create-domain CLOUD_GOV_ORG my-production-domain.name` ``` module "domain" { - source = "github.com/GSA-TTS/terraform-cloudgov//domain?ref=v1.1.0" + source = "github.com/GSA-TTS/terraform-cloudgov//domain?ref=v2.0.0" - cf_org_name = local.cf_org_name - cf_space_name = local.cf_space_name - app_name_or_id = "app_name" - cdn_plan_name = "domain" - domain_name = "my-production-domain-name" - host_name = "my-production-host-name" - tags = ["tag1", "tag2"] + cf_org_name = local.cf_org_name + cf_space = data.cloudfoundry_space.app_space + app_names = ["app_name"] + cdn_plan_name = "domain" + domain_name = "my-production-domain.name" + host_name = "my-production-host-name" + tags = ["tag1", "tag2"] } ``` @@ -101,11 +98,10 @@ Notes: ``` module "clamav" { - source = "github.com/GSA-TTS/terraform-cloudgov//clamav?ref=v1.1.0" + source = "github.com/GSA-TTS/terraform-cloudgov//clamav?ref=v2.0.0" cf_org_name = local.cf_org_name cf_space_name = local.cf_space_name - app_name_or_id = "app_name" name = "my_clamav_name" clamav_image = "ghcr.io/gsa-tts/clamav-rest/clamav:TAG_NAME" max_file_size = "30M" @@ -129,10 +125,11 @@ Creates a new cloud.gov space, such as when creating an egress space, and output ``` module "egress_space" { - source = "github.com/GSA-TTS/terraform-cloudgov//cg_space?ref=v1.1.0" + source = "github.com/GSA-TTS/terraform-cloudgov//cg_space?ref=v2.0.0" cf_org_name = local.cf_org_name cf_space_name = "${local.cf_space_name}-egress" + allow_ssh = false managers = [ "space.manager@gsa.gov" ] @@ -156,12 +153,12 @@ Prerequities: ``` module "egress_proxy" { - source = "github.com/GSA-TTS/terraform-cloudgov//egress_proxy?ref=v1.1.0" + source = "github.com/GSA-TTS/terraform-cloudgov//egress_proxy?ref=v2.0.0" - cf_org_name = local.cf_org_name - cf_space_name = "${local.cf_space_name}-egress" - client_space = local.cf_space_name - name = "egress-proxy" + cf_org_name = local.cf_org_name + cf_egress_space = data.cloudfoundry_space.egress_space + cf_client_spaces = {(data.cloudfoundry_space.app_space.name) = data.cloudfoundy_space.app_space.id} + name = "egress-proxy" allowlist = { "source_app_name" = ["host.com:443", "otherhost.com:443"] } diff --git a/SECURITY.md b/SECURITY.md index 5289d1d..846f684 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -7,6 +7,7 @@ Only certain branches are supported with security updates. | Version (branch) | Supported | | ---------------- | --------- | | main | :white_check_mark: | +| v1 | :white_check_mark: | | other | :x: | When using this code or reporting vulnerability please be sure to use supported branches and the most recent release tag. diff --git a/UPGRADING.md b/UPGRADING.md new file mode 100644 index 0000000..55ad25c --- /dev/null +++ b/UPGRADING.md @@ -0,0 +1,139 @@ +# Upgrading from v1 to v2 + +The terraform-cloudgov modules have many backwards-incompatible changes between v1 and v2. These changes are mostly around: + +1. Changing from the [cloudfoundry-community](https://registry.terraform.io/providers/cloudfoundry-community/cloudfoundry/latest/docs) provider to the [cloudfoundry](https://registry.terraform.io/providers/cloudfoundry/cloudfoundry/latest/docs) provider. +1. Changes to inputs and outputs and resources created to better fit with the new provider and as the result of lessons learned over the life of the v1 code base. + +## Using v1 and v2 together + +It is possible to use modules from both v1 and v2 in the same root module, to ease migration (or even remain on v1 for existing resources and use v2 for new ones). We will continue to maintain the v1 branch for awhile with bug-fixes as needed. + +Specify both providers in your root module (here, `cloudfoundry-community` is probably called `cloudfoundry` in your old module): + +``` +terraform { + required_version = "~> 1.0" + required_providers { + cloudfoundry = { + source = "cloudfoundry/cloudfoundry" + version = "1.1.0" + } + + cloudfoundry-community = { + source = "cloudfoundry-community/cloudfoundry" + version = "0.53.1" + } + } +} + +provider "cloudfoundry" { + api_url = "https://api.fr.cloud.gov" + user = var.cf_user + password = var.cf_password +} + +provider "cloudfoundry-community" { + api_url = "https://api.fr.cloud.gov" + user = var.cf_user + password = var.cf_password +} +``` + +The v1 modules should properly select the `cloudfoundry-community` provider, but if they don't you may need to [explicitly set the provider](https://developer.hashicorp.com/terraform/language/modules/develop/providers#passing-providers-explicitly): + +``` +module "database" { + source = "github.com/gsa-tts/terraform-cloudgov//database?ref=v1.1.0" + providers = { + cloudfoundry = cloudfoundry-community + } + + # ... +} +``` + +## Provider Upgrades + +Follow the steps in the [cloudfoundry provider migration guide](https://github.com/cloudfoundry/terraform-provider-cloudfoundry/blob/main/migration-guide/Readme.md) to migrate an existing use of the v1 module to v2. As an example, here are the steps for upgrading a database module: + +1. Update source line to point to v2 module and change `cf_space_name` to `cf_space_id` +1. Verify that `terraform validate` passes +1. Run: `terraform state show module.database.cloudfoundry_service_instance.rds | grep -m 1 id` and copy the ID +1. Run: `terraform state rm module.database.cloudfoundry_service_instance.rds` +1. Run: `terraform import module.database.cloudfoundry_service_instance.rds ID_FROM_STEP_3` +1. Run: `terraform apply` to fill in new computed attributes + +## Module Changes + +### Common Changes + +1. Check the `variables.tf` and `outputs.tf` files for each module for new names of variables and outputs. There should not be any variables or outputs that kept the same name but changed behavior. + +### Egress Proxy + +Egress Proxy no longer sets up network policies between the proxy and client apps, and does not create a User Provided Service Instance to deliver the credentials to the app. It is the developer's responsibility to do those things in the root module to better handle circular dependencies between creating the client app(s) and the proxy. + +To setup the network policy in your root module, add: + +``` +resource "cloudfoundry_network_policy" "egress_policy" { + provider = cloudfoundry-community + policy { + source_app = cloudfoundry_app.client_app.id # assumes you're deploying the client app with terraform + destination_app = module.egress_proxy.app_id + port = module.egress_proxy.https_port + } +} +``` + +To add a UPSI: + +``` +resource "cloudfoundry_service_instance" "egress_proxy_credentials" { + name = "egress-proxy-credentials" + space = module.app_space.space_id + type = "user-provided" + credentials = module.egress_proxy.json_credentials +} +``` + +### ClamAV + +ClamAV no longer sets up network policies between the clamav app and client apps. It is the developer's responsibility to set this up to better handle circular dependencies between the various apps. + +To setup the network policy in your root module, add: + +``` +resource "cloudfoundry_network_policy" "clamav_policy" { + provider = cloudfoundry-community + policy { + source_app = cloudfoundry_app.client_app.id # assumes you're deploying the client app with terraform + destination_app = module.clamav_scanner.app_id + port = "61443" + } +} +``` + +### cg_space + +The new cg_space sets up all of the same resources and permissions as the old cg_space, however the way permissions are done is incompatible with the old provider and cannot be cleanly imported the way we can with the other providers. + +This leads to a race condition where: + +* The terraform user can't add itself with the new resource first, because it already has the permission that the resource is trying to create, but +* The terraform user can't remove itself from the old resource first, because then it doesn't have permission to re-add itself with the new resource. + +To solve this involves some extra manual action: + +1. Upgrade the space module source to v2 in your root module +1. `terraform apply -target=module.space.cloudfoundry_space_users.space_permissions` to remove the old permissions resources. +1. Manually add your terraform user as a SpaceDeveloper and a SpaceManager to the space. + ``` + cf set-space-role CF_USER_GUID ORG SPACE SpaceDeveloper + cf set-space-role CF_USER_GUID ORG SPACE SpaceManager + ``` +1. Ensure that your terraform user is _not_ listed in the terraform as a deployer, manager, or developer +1. `terraform apply` to add new permissions resources. +1. Use `cf unset-space-role` to remove the manual permissions settings or destroy the service account entirely to clean up after yourself. +1. (Optional, if you didn't destroy the service account) Re-add your terraform user to the terraform permissions configuration and have a different deployer apply it (like via CI/CD) diff --git a/cg_space/main.tf b/cg_space/main.tf index e6e4fe5..ae5bee3 100644 --- a/cg_space/main.tf +++ b/cg_space/main.tf @@ -3,46 +3,30 @@ data "cloudfoundry_org" "org" { } resource "cloudfoundry_space" "space" { - name = var.cf_space_name - org = data.cloudfoundry_org.org.id + name = var.cf_space_name + org = data.cloudfoundry_org.org.id + allow_ssh = var.allow_ssh } ### # User roles ### -data "cloudfoundry_user" "managers" { - for_each = var.managers - name = each.key - org_id = data.cloudfoundry_org.org.id -} - -data "cloudfoundry_user" "developers" { - for_each = var.developers - name = each.key - org_id = data.cloudfoundry_org.org.id -} - -data "cloudfoundry_user" "deployers" { - for_each = var.deployers - name = each.key - org_id = data.cloudfoundry_org.org.id +locals { + manager_names = setunion(var.managers, var.deployers) + developer_names = setunion(var.developers, var.deployers) } - -locals { - manager_ids = concat( - [for user in data.cloudfoundry_user.managers : user.id], - [for user in data.cloudfoundry_user.deployers : user.id] - ) - developer_ids = concat( - [for user in data.cloudfoundry_user.developers : user.id], - [for user in data.cloudfoundry_user.deployers : user.id] - ) +resource "cloudfoundry_space_role" "managers" { + for_each = local.manager_names + username = each.key + space = cloudfoundry_space.space.id + type = "space_manager" } -resource "cloudfoundry_space_users" "space_permissions" { - space = cloudfoundry_space.space.id - managers = local.manager_ids - developers = local.developer_ids +resource "cloudfoundry_space_role" "developers" { + for_each = local.developer_names + username = each.key + space = cloudfoundry_space.space.id + type = "space_developer" } diff --git a/cg_space/outputs.tf b/cg_space/outputs.tf index 0217e76..1182b42 100644 --- a/cg_space/outputs.tf +++ b/cg_space/outputs.tf @@ -5,3 +5,7 @@ output "space_id" { output "space_name" { value = cloudfoundry_space.space.name } + +output "space" { + value = cloudfoundry_space.space +} diff --git a/cg_space/providers.tf b/cg_space/providers.tf index aa59fd5..b6ad4a3 100644 --- a/cg_space/providers.tf +++ b/cg_space/providers.tf @@ -2,8 +2,8 @@ terraform { required_version = "~> 1.0" required_providers { cloudfoundry = { - source = "cloudfoundry-community/cloudfoundry" - version = ">=0.53.1" + source = "cloudfoundry/cloudfoundry" + version = ">=1.1.0" } } } diff --git a/cg_space/tests/creation.tftest.hcl b/cg_space/tests/creation.tftest.hcl index 6d010ce..ad4005f 100644 --- a/cg_space/tests/creation.tftest.hcl +++ b/cg_space/tests/creation.tftest.hcl @@ -1,22 +1,6 @@ -mock_provider "cloudfoundry" { - override_data { - target = data.cloudfoundry_user.managers["user.manager@gsa.gov"] - values = { - id = "1e5143a4-aa47-483c-8352-557988d5cc7a" - } - } - override_data { - target = data.cloudfoundry_user.deployers["user.manager@gsa.gov"] - values = { - id = "1e5143a4-aa47-483c-8352-557988d5cc7a" - } - } - override_data { - target = data.cloudfoundry_user.developers["user.developer@gsa.gov"] - values = { - id = "2c945842-13ee-4383-84ad-34ecbcde5ce6" - } - } +provider "cloudfoundry" { + api_url = "https://api.fr.cloud.gov" + # cf_user and cf_password are passed in via CF_USER and CF_PASSWORD env vars } variables { @@ -39,54 +23,59 @@ run "test_space_creation" { condition = cloudfoundry_space.space.name == output.space_name error_message = "Space name output must match the new space" } + + assert { + condition = cloudfoundry_space.space == output.space + error_message = "Entire space is output from the module" + } } run "test_manager_only" { variables { - managers = ["user.manager@gsa.gov"] + managers = ["ryan.ahearn@gsa.gov"] } assert { - condition = cloudfoundry_space_users.space_permissions.managers == toset(["1e5143a4-aa47-483c-8352-557988d5cc7a"]) + condition = keys(cloudfoundry_space_role.managers) == ["ryan.ahearn@gsa.gov"] error_message = "Should be able to set Space Managers" } assert { - condition = length(cloudfoundry_space_users.space_permissions.developers) == 0 + condition = length(cloudfoundry_space_role.developers) == 0 error_message = "Should not have set any Space Developers" } } run "test_individual_permissions" { variables { - managers = ["user.manager@gsa.gov"] - developers = ["user.developer@gsa.gov"] + managers = ["paul.hirsch@gsa.gov"] + developers = ["ryan.ahearn@gsa.gov"] } assert { - condition = cloudfoundry_space_users.space_permissions.managers == toset(["1e5143a4-aa47-483c-8352-557988d5cc7a"]) + condition = keys(cloudfoundry_space_role.managers) == ["paul.hirsch@gsa.gov"] error_message = "Should be able to set Space Managers" } assert { - condition = cloudfoundry_space_users.space_permissions.developers == toset(["2c945842-13ee-4383-84ad-34ecbcde5ce6"]) + condition = keys(cloudfoundry_space_role.developers) == ["ryan.ahearn@gsa.gov"] error_message = "Should be able to set Space Developers" } } run "test_deployer_permissions" { variables { - developers = ["user.developer@gsa.gov"] - deployers = ["user.manager@gsa.gov"] + developers = ["paul.hirsch@gsa.gov"] + deployers = ["ryan.ahearn@gsa.gov"] } assert { - condition = cloudfoundry_space_users.space_permissions.managers == toset(["1e5143a4-aa47-483c-8352-557988d5cc7a"]) + condition = keys(cloudfoundry_space_role.managers) == ["ryan.ahearn@gsa.gov"] error_message = "Should be able to set Space Managers via var.deployers" } assert { - condition = cloudfoundry_space_users.space_permissions.developers == toset(["2c945842-13ee-4383-84ad-34ecbcde5ce6", "1e5143a4-aa47-483c-8352-557988d5cc7a"]) + condition = keys(cloudfoundry_space_role.developers) == ["paul.hirsch@gsa.gov", "ryan.ahearn@gsa.gov"] error_message = "Should set Space Developers to var.developers + var.deployers" } } diff --git a/cg_space/variables.tf b/cg_space/variables.tf index 3a80cfe..086f87a 100644 --- a/cg_space/variables.tf +++ b/cg_space/variables.tf @@ -8,6 +8,12 @@ variable "cf_space_name" { description = "cloud.gov space name to create" } +variable "allow_ssh" { + type = bool + description = "whether to allow ssh access to apps running in this space" + default = false +} + variable "managers" { type = set(string) description = "list of cloud.gov users to be assigned to the SpaceManager role" diff --git a/clamav/main.tf b/clamav/main.tf index b133ef4..d1b76d0 100644 --- a/clamav/main.tf +++ b/clamav/main.tf @@ -1,35 +1,23 @@ -data "cloudfoundry_space" "space" { - org_name = var.cf_org_name - name = var.cf_space_name -} - -data "cloudfoundry_domain" "internal" { - name = "apps.internal" -} - -data "cloudfoundry_app" "app" { - name_or_id = var.app_name_or_id - space = data.cloudfoundry_space.space.id -} - -resource "cloudfoundry_route" "clamav_route" { - space = data.cloudfoundry_space.space.id - domain = data.cloudfoundry_domain.internal.id - hostname = var.name +locals { + endpoint = "${var.name}.apps.internal" } resource "cloudfoundry_app" "clamav_api" { - name = var.name - space = data.cloudfoundry_space.space.id - memory = var.clamav_memory - disk_quota = 2048 - timeout = 600 - strategy = "rolling" - instances = var.instances - docker_image = var.clamav_image - routes { - route = cloudfoundry_route.clamav_route.id - } + name = var.name + space_name = var.cf_space_name + org_name = var.cf_org_name + + memory = var.clamav_memory + disk_quota = "2048M" + health_check_invocation_timeout = 600 + strategy = "rolling" + instances = var.instances + docker_image = var.clamav_image + routes = [ + { + route = local.endpoint + } + ] environment = { # Only set the proxy environment variables if a value was supplied. # Otherwise, ensure that a harmless envvar gets set instead. @@ -41,11 +29,3 @@ resource "cloudfoundry_app" "clamav_api" { MAX_FILE_SIZE = var.max_file_size } } - -resource "cloudfoundry_network_policy" "clamav_routing" { - policy { - source_app = data.cloudfoundry_app.app.id - destination_app = cloudfoundry_app.clamav_api.id - port = "61443" - } -} diff --git a/clamav/outputs.tf b/clamav/outputs.tf index 2cfc56e..9d172ce 100644 --- a/clamav/outputs.tf +++ b/clamav/outputs.tf @@ -2,10 +2,6 @@ output "app_id" { value = cloudfoundry_app.clamav_api.id } -output "route_id" { - value = cloudfoundry_route.clamav_route.id -} - output "endpoint" { - value = cloudfoundry_route.clamav_route.endpoint + value = local.endpoint } diff --git a/clamav/providers.tf b/clamav/providers.tf index aa59fd5..b6ad4a3 100644 --- a/clamav/providers.tf +++ b/clamav/providers.tf @@ -2,8 +2,8 @@ terraform { required_version = "~> 1.0" required_providers { cloudfoundry = { - source = "cloudfoundry-community/cloudfoundry" - version = ">=0.53.1" + source = "cloudfoundry/cloudfoundry" + version = ">=1.1.0" } } } diff --git a/clamav/tests/creation.tftest.hcl b/clamav/tests/creation.tftest.hcl index 699ad4d..75c5c7a 100644 --- a/clamav/tests/creation.tftest.hcl +++ b/clamav/tests/creation.tftest.hcl @@ -1,12 +1,11 @@ mock_provider "cloudfoundry" {} variables { - cf_org_name = "gsa-tts-devtools-prototyping" - cf_space_name = "terraform-cloudgov-ci-tests" - app_name_or_id = "terraform_cloudgov_app" - name = "terraform-cloudgov-clamav-test" - clamav_image = "ghcr.io/gsa-tts/clamav-rest/clamav:TAG" - max_file_size = "30M" + cf_org_name = "gsa-tts-devtools-prototyping" + cf_space_name = "terraform-cloudgov-ci-tests" + name = "terraform-cloudgov-clamav-test" + clamav_image = "ghcr.io/gsa-tts/clamav-rest/clamav:TAG" + max_file_size = "30M" } run "test_app_creation" { @@ -16,20 +15,10 @@ run "test_app_creation" { } assert { - condition = cloudfoundry_route.clamav_route.id == output.route_id - error_message = "Route ID output must match the ID of the route to the clamav app" - } - - assert { - condition = cloudfoundry_route.clamav_route.endpoint == output.endpoint + condition = "${var.name}.apps.internal" == output.endpoint error_message = "Endpoint output must match the clamav route endpoint" } - assert { - condition = cloudfoundry_route.clamav_route.hostname == var.name - error_message = "ClamAV route uses the service name for hostname" - } - assert { condition = cloudfoundry_app.clamav_api.name == var.name error_message = "App name matches var.name" @@ -45,11 +34,6 @@ run "test_app_creation" { error_message = "Docker image is passed directly in as var.clamav_image" } - assert { - condition = [for r in cloudfoundry_app.clamav_api.routes : r.route] == [cloudfoundry_route.clamav_route.id] - error_message = "ClamAV app has the route set on the internal domain" - } - assert { condition = cloudfoundry_app.clamav_api.environment["MAX_FILE_SIZE"] == var.max_file_size error_message = "Sets the max file size to var.max_file_size" @@ -74,21 +58,6 @@ run "test_app_creation" { condition = lookup(cloudfoundry_app.clamav_api.environment, "PROXY_PASSWORD", null) == null error_message = "Does not set the PROXY_PASSWORD environment by default" } - - assert { - condition = [for policy in cloudfoundry_network_policy.clamav_routing.policy : policy.source_app] == [data.cloudfoundry_app.app.id] - error_message = "Routing policy allows traffic from the source app" - } - - assert { - condition = [for policy in cloudfoundry_network_policy.clamav_routing.policy : policy.destination_app] == [cloudfoundry_app.clamav_api.id] - error_message = "Routing policy allows traffic to the clamav app" - } - - assert { - condition = [for policy in cloudfoundry_network_policy.clamav_routing.policy : policy.port] == ["61443"] - error_message = "Routing policy opens up traffic on the internal https port" - } } run "test_with_proxy" { diff --git a/clamav/variables.tf b/clamav/variables.tf index 1451f09..c161deb 100644 --- a/clamav/variables.tf +++ b/clamav/variables.tf @@ -5,12 +5,7 @@ variable "cf_org_name" { variable "cf_space_name" { type = string - description = "cloud.gov space name (staging or prod)" -} - -variable "app_name_or_id" { - type = string - description = "base application name to allow routing to the clamav app" + description = "cloud.gov space name" } variable "name" { @@ -24,9 +19,9 @@ variable "clamav_image" { } variable "clamav_memory" { - type = number - description = "Memory in MB to allocate to clamav app" - default = 3072 + type = string + description = "Memory to allocate to clamav app" + default = "3072M" } variable "max_file_size" { diff --git a/database/main.tf b/database/main.tf index 6a65d6e..8679665 100644 --- a/database/main.tf +++ b/database/main.tf @@ -1,16 +1,17 @@ -data "cloudfoundry_space" "space" { - org_name = var.cf_org_name - name = var.cf_space_name +locals { + tags = setunion(["terraform-cloudgov-managed"], var.tags) } -data "cloudfoundry_service" "rds" { - name = "aws-rds" +data "cloudfoundry_service_plans" "rds" { + name = var.rds_plan_name + service_offering_name = "aws-rds" } resource "cloudfoundry_service_instance" "rds" { name = var.name - space = data.cloudfoundry_space.space.id - service_plan = data.cloudfoundry_service.rds.service_plans[var.rds_plan_name] - tags = var.tags - json_params = var.json_params + space = var.cf_space_id + type = "managed" + service_plan = data.cloudfoundry_service_plans.rds.service_plans.0.id + tags = local.tags + parameters = var.json_params } diff --git a/database/providers.tf b/database/providers.tf index aa59fd5..b6ad4a3 100644 --- a/database/providers.tf +++ b/database/providers.tf @@ -2,8 +2,8 @@ terraform { required_version = "~> 1.0" required_providers { cloudfoundry = { - source = "cloudfoundry-community/cloudfoundry" - version = ">=0.53.1" + source = "cloudfoundry/cloudfoundry" + version = ">=1.1.0" } } } diff --git a/database/tests/creation.tftest.hcl b/database/tests/creation.tftest.hcl index a4f7170..579be05 100644 --- a/database/tests/creation.tftest.hcl +++ b/database/tests/creation.tftest.hcl @@ -1,32 +1,34 @@ -mock_provider "cloudfoundry" { - mock_data "cloudfoundry_service" { - defaults = { - service_plans = { - "micro-psql" = "03c93c7b-3e1c-47c5-a6c3-1df151d280dd" - } - } - } +provider "cloudfoundry" { + api_url = "https://api.fr.cloud.gov" + # cf_user and cf_password are passed in via CF_USER and CF_PASSWORD env vars } variables { - cf_org_name = "gsa-tts-devtools-prototyping" - cf_space_name = "terraform-cloudgov-ci-tests" + # this is the ID of the terraform-cloudgov-ci-tests space + cf_space_id = "15836eb6-a57e-4579-bca7-99764c5a01a4" rds_plan_name = "micro-psql" name = "terraform-cloudgov-rds-test" - tags = ["terraform-cloudgov", "tests"] + tags = ["terraform-cloudgov-managed", "tests"] json_params = jsonencode({ backup_retention_period = 30 }) } run "test_db_creation" { + override_resource { + target = cloudfoundry_service_instance.rds + values = { + id = "f6925fad-f9e8-4c93-b69f-132438f6a2f4" + } + } + assert { condition = cloudfoundry_service_instance.rds.id == output.instance_id error_message = "Instance ID output must match the service instance" } assert { - condition = cloudfoundry_service_instance.rds.service_plan == data.cloudfoundry_service.rds.service_plans[var.rds_plan_name] + condition = cloudfoundry_service_instance.rds.service_plan == data.cloudfoundry_service_plans.rds.service_plans.0.id error_message = "Service Plan should match the rds_plan_name variable" } @@ -36,12 +38,12 @@ run "test_db_creation" { } assert { - condition = cloudfoundry_service_instance.rds.tags == var.tags + condition = cloudfoundry_service_instance.rds.tags == tolist(var.tags) error_message = "Service instance tags should match the tags variable" } assert { - condition = cloudfoundry_service_instance.rds.json_params == "{\"backup_retention_period\":30}" + condition = cloudfoundry_service_instance.rds.parameters == "{\"backup_retention_period\":30}" error_message = "Service instance json_params should be configurable" } } diff --git a/database/variables.tf b/database/variables.tf index a9f67eb..a0e8892 100644 --- a/database/variables.tf +++ b/database/variables.tf @@ -1,11 +1,6 @@ -variable "cf_org_name" { +variable "cf_space_id" { type = string - description = "cloud.gov organization name" -} - -variable "cf_space_name" { - type = string - description = "cloud.gov space name" + description = "cloud.gov space GUID" } variable "name" { @@ -21,7 +16,7 @@ variable "rds_plan_name" { variable "tags" { description = "A list of tags to add to the resource" - type = list(string) + type = set(string) default = [] } diff --git a/domain/main.tf b/domain/main.tf index 9886f80..c553799 100644 --- a/domain/main.tf +++ b/domain/main.tf @@ -1,25 +1,22 @@ locals { - endpoint = (var.host_name != null ? "${var.host_name}.${var.domain_name}" : var.domain_name) - target_apps = (var.app_name_or_id != null ? [var.app_name_or_id] : var.app_names_or_ids) - service_name = (var.name == "" ? "${data.cloudfoundry_app.app[local.target_apps[0]].name}-${var.domain_name}" : var.name) -} - -data "cloudfoundry_space" "space" { - org_name = var.cf_org_name - name = var.cf_space_name + service_name = (var.name == "" ? "${var.app_names[0]}-${var.domain_name}" : var.name) + tags = setunion(["terraform-cloudgov-managed"], var.tags) + connect_route = length(var.app_names) > 0 + endpoint = (local.connect_route ? cloudfoundry_route.origin_route_connected.0.url : cloudfoundry_route.origin_route.0.url) } data "cloudfoundry_app" "app" { - for_each = toset(local.target_apps) - name_or_id = each.key - space = data.cloudfoundry_space.space.id + for_each = toset(var.app_names) + name = each.key + space_name = var.cf_space.name + org_name = var.cf_org_name } ########################################################################### # There are two prerequisites for running this module: # # 1) Domain must be manually created by an OrgManager: -# cf create-domain <%= cloud_gov_organization %> TKTK-production-domain-name +# cf create-domain <%= var.cf_org_name %> <%= var.domain_name %> # 2) ACME challenge record must be created. # See https://cloud.gov/docs/services/external-domain-service/#how-to-create-an-instance-of-this-service ########################################################################### @@ -27,27 +24,32 @@ data "cloudfoundry_domain" "origin_url" { name = var.domain_name } -resource "cloudfoundry_route" "origin_route" { - domain = data.cloudfoundry_domain.origin_url.id - hostname = var.host_name - space = data.cloudfoundry_space.space.id +resource "cloudfoundry_route" "origin_route_connected" { + count = local.connect_route ? 1 : 0 + space = var.cf_space.id + domain = data.cloudfoundry_domain.origin_url.id + host = var.host_name - dynamic "target" { - for_each = data.cloudfoundry_app.app - content { - app = target.value.id - } - } + destinations = [for name, app in data.cloudfoundry_app.app : { app_id = app.id }] +} + +resource "cloudfoundry_route" "origin_route" { + count = local.connect_route ? 0 : 1 + space = var.cf_space.id + domain = data.cloudfoundry_domain.origin_url.id + host = var.host_name } -data "cloudfoundry_service" "external_domain" { - name = "external-domain" +data "cloudfoundry_service_plans" "external_domain" { + name = var.cdn_plan_name + service_offering_name = "external-domain" } resource "cloudfoundry_service_instance" "external_domain_instance" { name = local.service_name - space = data.cloudfoundry_space.space.id - service_plan = data.cloudfoundry_service.external_domain.service_plans[var.cdn_plan_name] - json_params = "{\"domains\": \"${local.endpoint}\"}" - tags = var.tags + space = var.cf_space.id + service_plan = data.cloudfoundry_service_plans.external_domain.service_plans.0.id + parameters = "{\"domains\": \"${local.endpoint}\"}" + tags = local.tags + type = "managed" } diff --git a/domain/outputs.tf b/domain/outputs.tf index ca850af..de97c7a 100644 --- a/domain/outputs.tf +++ b/domain/outputs.tf @@ -3,5 +3,5 @@ output "instance_id" { } output "route_id" { - value = cloudfoundry_route.origin_route.id + value = (length(var.app_names) == 0 ? cloudfoundry_route.origin_route.0.id : cloudfoundry_route.origin_route_connected.0.id) } diff --git a/domain/providers.tf b/domain/providers.tf index aa59fd5..b6ad4a3 100644 --- a/domain/providers.tf +++ b/domain/providers.tf @@ -2,8 +2,8 @@ terraform { required_version = "~> 1.0" required_providers { cloudfoundry = { - source = "cloudfoundry-community/cloudfoundry" - version = ">=0.53.1" + source = "cloudfoundry/cloudfoundry" + version = ">=1.1.0" } } } diff --git a/domain/tests/creation.tftest.hcl b/domain/tests/creation.tftest.hcl index 467969f..d3e5b93 100644 --- a/domain/tests/creation.tftest.hcl +++ b/domain/tests/creation.tftest.hcl @@ -1,21 +1,46 @@ -mock_provider "cloudfoundry" { - mock_data "cloudfoundry_service" { - defaults = { - service_plans = { - "domain" = "03c93c7b-3e1c-47c5-a6c3-1df151d280dd" - "domain-with-cdn" = "7dd54395-1e90-4493-8a6d-45d81469291f" - } - } +provider "cloudfoundry" { + api_url = "https://api.fr.cloud.gov" + # cf_user and cf_password are passed in via CF_USER and CF_PASSWORD env vars +} + +override_data { + target = data.cloudfoundry_app.app["test-app-does-not-exist"] + values = { + id = "f9722bd0-ee5c-4b83-afd9-24e03760a692" + } +} + +override_data { + target = data.cloudfoundry_app.app["test-app-does-not-exist-2"] + values = { + id = "6e214634-8cf6-435c-858c-b0fd4bba8f48" } } +# don't create the connected route because the destination apps don't exist +override_resource { + target = cloudfoundry_route.origin_route_connected + values = { + url = "www.apps.internal" + } +} + +# don't create the external domain instance because the CNAME records don't exist +override_resource { + target = cloudfoundry_service_instance.external_domain_instance +} + variables { - cf_org_name = "gsa-tts-devtools-prototyping" - cf_space_name = "terraform-cloudgov-ci-tests" + cf_org_name = "gsa-tts-devtools-prototyping" + cf_space = { + id = "15836eb6-a57e-4579-bca7-99764c5a01a4" + name = "terraform-cloudgov-ci-tests" + } cdn_plan_name = "domain" - domain_name = "devtools.tts.gsa.gov" + domain_name = "apps.internal" name = "terraform-cloudgov-domain-test" - tags = ["terraform-cloudgov", "tests"] + app_names = ["test-app-does-not-exist"] + tags = ["terraform-cloudgov-managed", "tests"] } run "test_domain_creation" { @@ -25,12 +50,12 @@ run "test_domain_creation" { } assert { - condition = cloudfoundry_route.origin_route.id == output.route_id + condition = cloudfoundry_route.origin_route_connected.0.id == output.route_id error_message = "Route ID output must match the created route" } assert { - condition = cloudfoundry_service_instance.external_domain_instance.service_plan == data.cloudfoundry_service.external_domain.service_plans[var.cdn_plan_name] + condition = cloudfoundry_service_instance.external_domain_instance.service_plan == data.cloudfoundry_service_plans.external_domain.service_plans.0.id error_message = "Service Plan should match the cdn_plan_name variable" } @@ -43,10 +68,22 @@ run "test_domain_creation" { condition = cloudfoundry_service_instance.external_domain_instance.tags == var.tags error_message = "Service instance tags should match the tags variable" } +} + +run "test_no_apps" { + variables { + host_name = "terraform-ci-test" + app_names = [] + } assert { - condition = cloudfoundry_service_instance.external_domain_instance.json_params == "{\"domains\": \"${var.domain_name}\"}" - error_message = "Service instance json_params should define the endpoint" + condition = cloudfoundry_route.origin_route.0.id == output.route_id + error_message = "Route ID should return the correct resource id when apps are not specified" + } + + assert { + condition = cloudfoundry_service_instance.external_domain_instance.parameters == "{\"domains\": \"${var.host_name}.${var.domain_name}\"}" + error_message = "Service instance parameters should define the endpoint" } } @@ -56,13 +93,13 @@ run "test_with_hostname" { } assert { - condition = cloudfoundry_route.origin_route.hostname == var.host_name + condition = cloudfoundry_route.origin_route_connected.0.host == var.host_name error_message = "Route hostname should be set to value of var.host_name" } assert { - condition = cloudfoundry_service_instance.external_domain_instance.json_params == "{\"domains\": \"${var.host_name}.${var.domain_name}\"}" - error_message = "Service instance json_params should define the endpoint" + condition = cloudfoundry_service_instance.external_domain_instance.parameters == "{\"domains\": \"${var.host_name}.${var.domain_name}\"}" + error_message = "Service instance parameters should define the endpoint" } } @@ -72,58 +109,24 @@ run "test_cdn_creation" { } assert { - condition = cloudfoundry_service_instance.external_domain_instance.service_plan == data.cloudfoundry_service.external_domain.service_plans[var.cdn_plan_name] + condition = cloudfoundry_service_instance.external_domain_instance.service_plan == data.cloudfoundry_service_plans.external_domain.service_plans.0.id error_message = "Service Plan should match the cdn_plan_name variable" } } -run "test_single_app_target" { - variables { - name = "" - app_name_or_id = "terraform_cloudgov_app" - } - - assert { - condition = can(regex("^\\w{8}-${var.domain_name}$", cloudfoundry_service_instance.external_domain_instance.name)) - error_message = "Service Instance name is built from the first app name and domain_name" - } - - assert { - condition = [for t in cloudfoundry_route.origin_route.target : t.app] == [data.cloudfoundry_app.app[var.app_name_or_id].id] - error_message = "Sets the route targets to be the app_name_or_id" - } -} - run "test_multi_app_target" { variables { - name = "" - app_names_or_ids = ["terraform_cloudgov_app", "terraform_cloudgov_app_2"] + name = "" + app_names = ["test-app-does-not-exist", "test-app-does-not-exist-2"] } assert { - condition = can(regex("^\\w{8}-${var.domain_name}$", cloudfoundry_service_instance.external_domain_instance.name)) + condition = can(regex("^[a-z-]{23}-${var.domain_name}$", cloudfoundry_service_instance.external_domain_instance.name)) error_message = "Service Instance name is built from the first app name and domain_name" } assert { - condition = toset([for name, value in data.cloudfoundry_app.app : value.id]) == toset([for t in cloudfoundry_route.origin_route.target : t.app]) - error_message = "Target apps is set to the list of app_names_or_ids" - } -} - -run "test_conflicting_variables" { - variables { - app_name_or_id = "terraform_cloudgov_app" - app_names_or_ids = ["terraform_cloudgov_app_2", "terraform_cloudgov_app_3"] - } - - assert { - condition = [for t in cloudfoundry_route.origin_route.target : t.app] == [data.cloudfoundry_app.app[var.app_name_or_id].id] - error_message = "Sets the route targets to be the app_name_or_id" - } - - assert { - condition = cloudfoundry_service_instance.external_domain_instance.name == var.name - error_message = "Service Instance name is set to var.name when present" + condition = toset([for name, value in data.cloudfoundry_app.app : value.id]) == toset([for t in cloudfoundry_route.origin_route_connected.0.destinations : t.app_id]) + error_message = "Target apps is set to the list of app_names" } } diff --git a/domain/variables.tf b/domain/variables.tf index ca91bc3..6a8bab5 100644 --- a/domain/variables.tf +++ b/domain/variables.tf @@ -3,26 +3,23 @@ variable "cf_org_name" { description = "cloud.gov organization name" } -variable "cf_space_name" { - type = string - description = "cloud.gov space name (staging or prod)" -} - -variable "app_name_or_id" { - type = string - description = "base application name or id to be accessed at this domain name. Overrides var.app_names_or_ids if used" - default = null +variable "cf_space" { + type = object({ + id = string + name = string + }) + description = "cloud.gov space resource" } -variable "app_names_or_ids" { +variable "app_names" { type = list(string) - description = "base application names or ids to be accessed at this domain name. Overridden by var.app_name_or_id if used" + description = "base application names to be accessed at this domain name." default = [] } variable "name" { type = string - description = "name of the service instance. Required if not passing in app names or ids" + description = "name of the service instance" default = "" } diff --git a/egress_proxy/acl.tftpl b/egress_proxy/acl.tftpl index a109cb1..f0d210a 100644 --- a/egress_proxy/acl.tftpl +++ b/egress_proxy/acl.tftpl @@ -1,5 +1,3 @@ -%{ for app, dests in list ~} -%{ for dest in dests ~} -${ split(":", dest)[0] } -%{ endfor ~} +%{ for dest in list ~} +${ split(":", dest)[0] } %{ endfor ~} diff --git a/egress_proxy/main.tf b/egress_proxy/main.tf index b2953a5..e124f00 100644 --- a/egress_proxy/main.tf +++ b/egress_proxy/main.tf @@ -1,48 +1,20 @@ locals { - - # Make a clean list of the client apps for iteration purposes - clients = toset(keys(merge(var.allowlist, var.denylist))) - # Generate Caddy-compatible allow and deny ACLs, one target per line. - # - # For now, there's just one consolidated allowlist and denylist, no matter - # what apps they were specified for. Future improvments could improve this, - # but it would mean also changing the proxy to be both more complex (in terms - # of how the Caddyfile is constructed) and more discriminating (in terms of - # recognizing client apps based on GUIDs supplied by Envoy in request headers, - # as well as the destination ports). However, adding these improvements won't - # require modifying the module's interface, since we're already collecting - # that refined information. allowacl = templatefile("${path.module}/acl.tftpl", { list = var.allowlist }) denyacl = templatefile("${path.module}/acl.tftpl", { list = var.denylist }) -} - -### -### Set up the authenticated egress application in the target space on apps.internal -### - -data "cloudfoundry_domain" "internal" { - name = "apps.internal" -} -resource "cloudfoundry_route" "egress_route" { - space = data.cloudfoundry_space.egress_space.id - domain = data.cloudfoundry_domain.internal.id - hostname = substr("${var.cf_org_name}-${replace(var.cf_space_name, ".", "-")}-${var.name}", -63, -1) # Yields something like: orgname-spacename-name.apps.internal, limited to the last 63 characters + route_host = substr("${var.cf_org_name}-${replace(var.cf_egress_space.name, ".", "-")}-${var.name}", -63, -1) + egress_route = "${local.route_host}.apps.internal" } + resource "random_uuid" "username" {} resource "random_password" "password" { length = 16 special = false } -data "cloudfoundry_space" "egress_space" { - org_name = var.cf_org_name - name = var.cf_space_name -} - # This zips up just the depoyable files from the specified gitref in the # cg-egress-proxy repository data "external" "proxyzip" { @@ -54,19 +26,23 @@ data "external" "proxyzip" { } resource "cloudfoundry_app" "egress_app" { - name = var.name - space = data.cloudfoundry_space.egress_space.id + name = var.name + space_name = var.cf_egress_space.name + org_name = var.cf_org_name + + path = "${path.module}/${data.external.proxyzip.result.path}" source_code_hash = filesha256("${path.module}/${data.external.proxyzip.result.path}") - buildpack = "binary_buildpack" + buildpacks = ["binary_buildpack"] command = "./caddy run --config Caddyfile" memory = var.egress_memory instances = var.instances strategy = "rolling" - routes { - route = cloudfoundry_route.egress_route.id - } + routes = [{ + route = local.egress_route + }] + environment = { PROXY_PORTS : join(" ", var.allowports) PROXY_ALLOW : local.allowacl @@ -76,52 +52,12 @@ resource "cloudfoundry_app" "egress_app" { } } -### -### Set up network policies so that the clients can reach the proxy -### - -data "cloudfoundry_space" "client_space" { - org_name = var.cf_org_name - name = var.client_space -} - -data "cloudfoundry_app" "clients" { - for_each = local.clients - name_or_id = each.key - space = data.cloudfoundry_space.client_space.id -} - -resource "cloudfoundry_network_policy" "client_routing" { - for_each = local.clients - policy { - source_app = data.cloudfoundry_app.clients[each.key].id - destination_app = cloudfoundry_app.egress_app.id - port = "61443" - } -} - -### -### Create a credential service for bound clients to use when make requests of the proxy -### locals { - https_proxy = "https://${random_uuid.username.result}:${random_password.password.result}@${cloudfoundry_route.egress_route.endpoint}:61443" - domain = cloudfoundry_route.egress_route.endpoint + https_proxy = "https://${random_uuid.username.result}:${random_password.password.result}@${local.egress_route}:61443" + http_proxy = "http://${random_uuid.username.result}:${random_password.password.result}@${local.egress_route}:8080" + domain = local.egress_route username = random_uuid.username.result password = random_password.password.result - protocol = "https" - port = 61443 - app_id = cloudfoundry_app.egress_app.id -} - -resource "cloudfoundry_user_provided_service" "credentials" { - name = "${var.name}-creds" - space = data.cloudfoundry_space.client_space.id - credentials = { - "uri" = local.https_proxy - "domain" = local.domain - "username" = local.username - "password" = local.password - "protocol" = local.protocol - "port" = local.port - } + https_port = 61443 + http_port = 8080 } diff --git a/egress_proxy/outputs.tf b/egress_proxy/outputs.tf index db9e66e..f5d1799 100644 --- a/egress_proxy/outputs.tf +++ b/egress_proxy/outputs.tf @@ -3,10 +3,23 @@ output "https_proxy" { sensitive = true } +output "http_proxy" { + value = local.http_proxy + sensitive = true +} + output "domain" { value = local.domain } +output "http_port" { + value = local.http_port +} + +output "https_port" { + value = local.https_port +} + output "username" { value = local.username } @@ -16,14 +29,19 @@ output "password" { sensitive = true } -output "protocol" { - value = local.protocol -} - output "app_id" { - value = local.app_id + value = cloudfoundry_app.egress_app.id } -output "port" { - value = local.port +output "json_credentials" { + value = jsonencode({ + "https_uri" = local.https_proxy + "http_uri" = local.http_proxy + "domain" = local.domain + "username" = local.username + "password" = local.password + "https_port" = local.https_port + "http_port" = local.http_port + }) + sensitive = true } diff --git a/egress_proxy/providers.tf b/egress_proxy/providers.tf index aa59fd5..b6ad4a3 100644 --- a/egress_proxy/providers.tf +++ b/egress_proxy/providers.tf @@ -2,8 +2,8 @@ terraform { required_version = "~> 1.0" required_providers { cloudfoundry = { - source = "cloudfoundry-community/cloudfoundry" - version = ">=0.53.1" + source = "cloudfoundry/cloudfoundry" + version = ">=1.1.0" } } } diff --git a/egress_proxy/tests/creation.tftest.hcl b/egress_proxy/tests/creation.tftest.hcl index 1098e30..90f8ddf 100644 --- a/egress_proxy/tests/creation.tftest.hcl +++ b/egress_proxy/tests/creation.tftest.hcl @@ -1,11 +1,13 @@ mock_provider "cloudfoundry" {} variables { - cf_org_name = "gsa-tts-devtools-prototyping" - cf_space_name = "terraform-cloudgov-ci-tests-egress" - client_space = "terraform-cloudgov-ci-tests" - name = "terraform-egress-app" - allowlist = { "continuous_monitoring-staging" = ["raw.githubusercontent.com:443"] } + cf_org_name = "gsa-tts-devtools-prototyping" + cf_egress_space = { + id = "5178d8f5-d19a-4782-ad07-467822480c68" + name = "terraform-cloudgov-ci-tests-egress" + } + name = "terraform-egress-app" + allowlist = ["raw.githubusercontent.com:443"] } run "test_proxy_creation" { @@ -15,8 +17,8 @@ run "test_proxy_creation" { } assert { - condition = output.domain == cloudfoundry_route.egress_route.endpoint - error_message = "Output domain must match the route endpoint" + condition = output.domain == local.egress_route + error_message = "Output domain must match the route url" } assert { @@ -30,17 +32,17 @@ run "test_proxy_creation" { } assert { - condition = output.protocol == "https" - error_message = "protocol only supports https" + condition = output.app_id == cloudfoundry_app.egress_app.id + error_message = "Output app_id is the egress_app's ID" } assert { - condition = output.app_id == cloudfoundry_app.egress_app.id - error_message = "Output app_id is the egress_app's ID" + condition = output.https_port == 61443 + error_message = "https_port only supports 61443 internal https listener" } assert { - condition = output.port == 61443 - error_message = "port only supports 61443 internal https listener" + condition = output.http_port == 8080 + error_message = "http_port reports port 8080 for plaintext" } } diff --git a/egress_proxy/variables.tf b/egress_proxy/variables.tf index 1a680a2..1d26edc 100644 --- a/egress_proxy/variables.tf +++ b/egress_proxy/variables.tf @@ -3,14 +3,12 @@ variable "cf_org_name" { description = "cloud.gov organization name" } -variable "cf_space_name" { - type = string - description = "cloud.gov space name for egress (eg staging-egress or prod-egress)" -} - -variable "client_space" { - type = string - description = "cloud.gov space name for client apps (eg staging or prod)" +variable "cf_egress_space" { + type = object({ + id = string + name = string + }) + description = "cloud.gov space egress" } variable "name" { @@ -19,9 +17,9 @@ variable "name" { } variable "egress_memory" { - type = number - description = "Memory in MB to allocate to egress proxy app" - default = 64 + type = string + description = "Memory to allocate to egress proxy app, including unit" + default = "64M" } variable "gitref" { @@ -38,23 +36,19 @@ variable "allowports" { } variable "allowlist" { - description = "Allowed egress for apps (applied first). A map where keys are app names, and the values are sets of acl strings." + description = "Allowed egress for apps (applied first). A set of allowed acl strings." # See the upstream documentation for possible acl strings: # https://github.com/caddyserver/forwardproxy/blob/caddy2/README.md#caddyfile-syntax-server-configuration - type = map(set(string)) - default = { - # appname = [ "*.example.com:443", "example2.com:443" ] - } + type = set(string) + default = [] # [ "*.example.com:443", "example2.com:443" ] } variable "denylist" { - description = "Denied egress for apps (applied second). A map where keys are app names, and the values are sets of host:port strings." + description = "Denied egress for apps (applied second). A set of disallowed host:port strings." # See the upstream documentation for possible acl strings: # https://github.com/caddyserver/forwardproxy/blob/caddy2/README.md#caddyfile-syntax-server-configuration - type = map(set(string)) - default = { - # appname = [ "bad.example.com:443" ] - } + type = set(string) + default = [] # [ "bad.example.com:443" ] } variable "instances" { diff --git a/redis/main.tf b/redis/main.tf index e276574..923718b 100644 --- a/redis/main.tf +++ b/redis/main.tf @@ -1,16 +1,17 @@ -data "cloudfoundry_space" "space" { - org_name = var.cf_org_name - name = var.cf_space_name +locals { + tags = setunion(["terraform-cloudgov-managed"], var.tags) } -data "cloudfoundry_service" "redis" { - name = "aws-elasticache-redis" +data "cloudfoundry_service_plans" "redis" { + name = var.redis_plan_name + service_offering_name = "aws-elasticache-redis" } resource "cloudfoundry_service_instance" "redis" { name = var.name - space = data.cloudfoundry_space.space.id - service_plan = data.cloudfoundry_service.redis.service_plans[var.redis_plan_name] - tags = var.tags - json_params = var.json_params + space = var.cf_space_id + type = "managed" + service_plan = data.cloudfoundry_service_plans.redis.service_plans.0.id + tags = local.tags + parameters = var.json_params } diff --git a/redis/providers.tf b/redis/providers.tf index aa59fd5..b6ad4a3 100644 --- a/redis/providers.tf +++ b/redis/providers.tf @@ -2,8 +2,8 @@ terraform { required_version = "~> 1.0" required_providers { cloudfoundry = { - source = "cloudfoundry-community/cloudfoundry" - version = ">=0.53.1" + source = "cloudfoundry/cloudfoundry" + version = ">=1.1.0" } } } diff --git a/redis/tests/creation.tftest.hcl b/redis/tests/creation.tftest.hcl index d67d7b4..8b5ecbd 100644 --- a/redis/tests/creation.tftest.hcl +++ b/redis/tests/creation.tftest.hcl @@ -1,32 +1,34 @@ -mock_provider "cloudfoundry" { - mock_data "cloudfoundry_service" { - defaults = { - service_plans = { - "redis-dev" = "03c93c7b-3e1c-47c5-a6c3-1df151d280dd" - } - } - } +provider "cloudfoundry" { + api_url = "https://api.fr.cloud.gov" + # cf_user and cf_password are passed in via CF_USER and CF_PASSWORD env vars } variables { - cf_org_name = "gsa-tts-devtools-prototyping" - cf_space_name = "terraform-cloudgov-ci-tests" + # this is the ID of the terraform-cloudgov-ci-tests space + cf_space_id = "15836eb6-a57e-4579-bca7-99764c5a01a4" redis_plan_name = "redis-dev" name = "terraform-cloudgov-redis-test" - tags = ["terraform-cloudgov", "tests"] + tags = ["terraform-cloudgov-managed", "tests"] json_params = jsonencode({ engineVersion = "7.0" }) } run "test_redis_creation" { + override_resource { + target = cloudfoundry_service_instance.redis + values = { + id = "2a4dae63-2fb7-4a76-975d-eebb9a7b8d96" + } + } + assert { condition = cloudfoundry_service_instance.redis.id == output.instance_id error_message = "Instance ID output must match the service instance" } assert { - condition = cloudfoundry_service_instance.redis.service_plan == data.cloudfoundry_service.redis.service_plans[var.redis_plan_name] + condition = cloudfoundry_service_instance.redis.service_plan == data.cloudfoundry_service_plans.redis.service_plans.0.id error_message = "Service Plan should match the redis_plan_name variable" } @@ -36,12 +38,12 @@ run "test_redis_creation" { } assert { - condition = cloudfoundry_service_instance.redis.tags == var.tags + condition = cloudfoundry_service_instance.redis.tags == tolist(var.tags) error_message = "Service instance tags should match the tags variable" } assert { - condition = cloudfoundry_service_instance.redis.json_params == "{\"engineVersion\":\"7.0\"}" - error_message = "Service instance json_params should be configurable" + condition = cloudfoundry_service_instance.redis.parameters == "{\"engineVersion\":\"7.0\"}" + error_message = "Service instance parameters should be configurable" } } diff --git a/redis/variables.tf b/redis/variables.tf index ceda98e..50f9997 100644 --- a/redis/variables.tf +++ b/redis/variables.tf @@ -1,11 +1,6 @@ -variable "cf_org_name" { +variable "cf_space_id" { type = string - description = "cloud.gov organization name" -} - -variable "cf_space_name" { - type = string - description = "cloud.gov space name (staging or prod)" + description = "cloud.gov space id" } variable "name" { @@ -20,7 +15,7 @@ variable "redis_plan_name" { variable "tags" { description = "A list of tags to add to the resource" - type = list(string) + type = set(string) default = [] } diff --git a/s3/main.tf b/s3/main.tf index 0bf8c3c..1ad84e1 100644 --- a/s3/main.tf +++ b/s3/main.tf @@ -1,16 +1,17 @@ -data "cloudfoundry_space" "space" { - org_name = var.cf_org_name - name = var.cf_space_name +locals { + tags = setunion(["terraform-cloudgov-managed"], var.tags) } -data "cloudfoundry_service" "s3" { - name = "s3" +data "cloudfoundry_service_plans" "s3" { + name = var.s3_plan_name + service_offering_name = "s3" } resource "cloudfoundry_service_instance" "bucket" { name = var.name - space = data.cloudfoundry_space.space.id - service_plan = data.cloudfoundry_service.s3.service_plans[var.s3_plan_name] - tags = var.tags - json_params = var.json_params + space = var.cf_space_id + type = "managed" + service_plan = data.cloudfoundry_service_plans.s3.service_plans.0.id + tags = local.tags + parameters = var.json_params } diff --git a/s3/providers.tf b/s3/providers.tf index aa59fd5..b6ad4a3 100644 --- a/s3/providers.tf +++ b/s3/providers.tf @@ -2,8 +2,8 @@ terraform { required_version = "~> 1.0" required_providers { cloudfoundry = { - source = "cloudfoundry-community/cloudfoundry" - version = ">=0.53.1" + source = "cloudfoundry/cloudfoundry" + version = ">=1.1.0" } } } diff --git a/s3/tests/creation.tftest.hcl b/s3/tests/creation.tftest.hcl index 00850ce..dc059b8 100644 --- a/s3/tests/creation.tftest.hcl +++ b/s3/tests/creation.tftest.hcl @@ -4,11 +4,11 @@ provider "cloudfoundry" { } variables { - cf_org_name = "gsa-tts-devtools-prototyping" - cf_space_name = "terraform-cloudgov-ci-tests" - s3_plan_name = "basic" - name = "terraform-cloudgov-s3-test" - tags = ["terraform-cloudgov", "tests"] + # this is the ID of the terraform-cloudgov-ci-tests space + cf_space_id = "15836eb6-a57e-4579-bca7-99764c5a01a4" + s3_plan_name = "basic-sandbox" + name = "terraform-cloudgov-s3-test" + tags = ["terraform-cloudgov-managed", "tests"] } run "test_bucket_creation" { @@ -23,7 +23,7 @@ run "test_bucket_creation" { } assert { - condition = cloudfoundry_service_instance.bucket.service_plan == data.cloudfoundry_service.s3.service_plans[var.s3_plan_name] + condition = cloudfoundry_service_instance.bucket.service_plan == data.cloudfoundry_service_plans.s3.service_plans.0.id error_message = "Service Plan should match the s3_plan_name variable" } @@ -33,12 +33,12 @@ run "test_bucket_creation" { } assert { - condition = cloudfoundry_service_instance.bucket.tags == var.tags + condition = cloudfoundry_service_instance.bucket.tags == tolist(var.tags) error_message = "Service instance tags should match the tags variable" } } -run "test_json_params" { +run "test_parameters" { command = plan variables { @@ -48,7 +48,7 @@ run "test_json_params" { } assert { - condition = cloudfoundry_service_instance.bucket.json_params == "{\"object_ownership\":\"BucketOwnerEnforced\"}" - error_message = "Service instance json_params should be configurable" + condition = cloudfoundry_service_instance.bucket.parameters == "{\"object_ownership\":\"BucketOwnerEnforced\"}" + error_message = "Service instance parameters should be configurable" } } diff --git a/s3/variables.tf b/s3/variables.tf index 635004f..d241ab2 100644 --- a/s3/variables.tf +++ b/s3/variables.tf @@ -1,11 +1,6 @@ -variable "cf_org_name" { +variable "cf_space_id" { type = string - description = "cloud.gov organization name" -} - -variable "cf_space_name" { - type = string - description = "cloud.gov space name (staging or prod)" + description = "cloud.gov space id" } variable "name" { @@ -22,7 +17,7 @@ variable "s3_plan_name" { variable "tags" { description = "A list of tags to add to the resource" - type = list(string) + type = set(string) default = [] }