diff --git a/spiffworkflow/main.tf b/spiffworkflow/main.tf new file mode 100644 index 0000000..6ebfbd7 --- /dev/null +++ b/spiffworkflow/main.tf @@ -0,0 +1,217 @@ +locals { + route_prefix = (var.route_prefix != "" ? var.route_prefix : random_pet.route_prefix.id) + + backend_route = "${local.frontend_route}/api" + connector_route = "${local.route_prefix}-connector.apps.internal" + frontend_route = "${local.route_prefix}.app.cloud.gov" + + username = random_uuid.username.result + password = random_password.password.result + + # backend_url = "https://${local.username}:${local.password}@${local.backend_route}" + # connector_url = "https://${local.connector_route}:61443" + # frontend_url = "https://${local.username}:${local.password}@${local.frontend_route}" + + backend_url = "https://${local.backend_route}" + connector_url = "https://${local.connector_route}:61443" + frontend_url = "https://${local.frontend_route}" + + backend_app_id = cloudfoundry_app.backend.id + connector_app_id = cloudfoundry_app.connector.id + frontend_app_id = cloudfoundry_app.frontend.id + tags = setunion(["terraform-cloudgov-managed"], var.tags) + backend_baseimage = split(":", var.backend_imageref)[0] + frontend_baseimage = split(":", var.frontend_imageref)[0] + connector_baseimage = split(":", var.connector_imageref)[0] +} + +data "cloudfoundry_service_plans" "rds" { + name = var.rds_plan_name + service_offering_name = "aws-rds" +} + +data "cloudfoundry_org" "org" { + name = var.cf_org_name +} + +data "cloudfoundry_space" "space" { + name = var.cf_space_name + org = data.cloudfoundry_org.org.id +} +resource "cloudfoundry_service_instance" "database" { + name = "${var.app_prefix}-database" + space = data.cloudfoundry_space.space.id + type = "managed" + service_plan = data.cloudfoundry_service_plans.rds.service_plans.0.id + tags = local.tags + parameters = var.rds_json_params +} + +resource "random_uuid" "username" {} +resource "random_password" "password" { + length = 16 + special = false +} + +resource "random_pet" "route_prefix" { + prefix = "spiffworkflow" +} + +resource "random_password" "backend_flask_secret_key" { + length = 32 + special = true +} + +resource "random_password" "backend_openid_secret" { + length = 32 + special = true +} + +data "docker_registry_image" "backend" { + name = var.backend_imageref +} +resource "cloudfoundry_app" "backend" { + name = "${var.app_prefix}-backend" + org_name = var.cf_org_name + space_name = var.cf_space_name + docker_image = "${local.backend_baseimage}@${data.docker_registry_image.backend.sha256_digest}" + memory = var.backend_memory + instances = var.backend_instances + disk_quota = "3G" + strategy = "rolling" + command = <<-COMMAND + # Get the postgres URI from the service binding. (SQL Alchemy insists on "postgresql://".🙄) + export SPIFFWORKFLOW_BACKEND_DATABASE_URI=$( echo $VCAP_SERVICES | jq -r '.["aws-rds"][].credentials.uri' | sed -e s/postgres/postgresql/ ) + + # Make sure the Cloud Foundry-provided CA is recognized when making TLS connections + cat /etc/cf-system-certificates/* > /usr/local/share/ca-certificates/cf-system-certificates.crt + /usr/sbin/update-ca-certificates + + # Verify that this is working. It should return '{"ok": true}' + # curl https://spiffworkflow((slug))-connector.apps.internal:61443/liveness + + /app/bin/clone_process_models + /app/bin/boot_server_in_docker + COMMAND + health_check_type = "http" + health_check_http_endpoint = "/api/v1.0/status" + service_bindings = [ + { service_instance = cloudfoundry_service_instance.database.name } + ] + routes = [{ + route = local.backend_route + protocol = "http1" + }] + + environment = { + APPLICATION_ROOT : "/" + FLASK_SESSION_SECRET_KEY : random_password.backend_flask_secret_key.result + FLASK_DEBUG : "0" + REQUESTS_CA_BUNDLE : "/etc/ssl/certs/ca-certificates.crt" + + # All of the configuration variables are documented here: + # spiffworkflow-backend/src/spiffworkflow_backend/config/default.py + SPIFFWORKFLOW_BACKEND_BPMN_SPEC_ABSOLUTE_DIR : "/app/process_models" + SPIFFWORKFLOW_BACKEND_CHECK_FRONTEND_AND_BACKEND_URL_COMPATIBILITY : "false" + SPIFFWORKFLOW_BACKEND_CONNECTOR_PROXY_URL : local.connector_url + SPIFFWORKFLOW_BACKEND_DATABASE_TYPE : "postgres" + SPIFFWORKFLOW_BACKEND_ENV : "local_docker" + SPIFFWORKFLOW_BACKEND_EXTENSIONS_API_ENABLED : "true" + SPIFFWORKFLOW_BACKEND_GIT_COMMIT_ON_SAVE : "true" + SPIFFWORKFLOW_BACKEND_GIT_PUBLISH_CLONE_URL : var.process_models_repository + SPIFFWORKFLOW_BACKEND_GIT_PUBLISH_TARGET_BRANCH : var.process_models_publish_branch + + # This branch needs to exist, otherwise we can't clone it at startup and startup fails + SPIFFWORKFLOW_BACKEND_GIT_SOURCE_BRANCH : var.process_models_source_branch + SPIFFWORKFLOW_BACKEND_GIT_SSH_PRIVATE_KEY : var.process_models_ssh_key + SPIFFWORKFLOW_BACKEND_LOAD_FIXTURE_DATA : "false" + SPIFFWORKFLOW_BACKEND_LOG_LEVEL : "INFO" + + # TODO: We should make these configurable with variables so + # you can specify an external OIDC IDP. + SPIFFWORKFLOW_BACKEND_OPEN_ID_CLIENT_ID : "spiffworkflow-backend" + SPIFFWORKFLOW_BACKEND_OPEN_ID_CLIENT_SECRET_KEY : random_password.backend_openid_secret.result + SPIFFWORKFLOW_BACKEND_OPEN_ID_SERVER_URL : "${local.backend_url}/openid" + + # TODO: static creds are in this path in the image: + # /config/permissions/example.yml + # We should probably generate credentials only for the admin + # and have everything else be specified via DMN as described here: + # https://spiff-arena.readthedocs.io/en/latest/DevOps_installation_integration/admin_and_permissions.html#site-administration + SPIFFWORKFLOW_BACKEND_PERMISSIONS_FILE_NAME : "example.yml" + + SPIFFWORKFLOW_BACKEND_PORT : "8080" + SPIFFWORKFLOW_BACKEND_RUN_BACKGROUND_SCHEDULER_IN_CREATE_APP : "true" + SPIFFWORKFLOW_BACKEND_UPGRADE_DB : "true" + SPIFFWORKFLOW_BACKEND_URL : local.backend_url + SPIFFWORKFLOW_BACKEND_URL_FOR_FRONTEND : local.frontend_url + SPIFFWORKFLOW_BACKEND_USE_WERKZEUG_MIDDLEWARE_PROXY_FIX : "true" + SPIFFWORKFLOW_BACKEND_WSGI_PATH_PREFIX : "/api" + } +} + +resource "random_password" "connector_flask_secret_key" { + length = 32 + special = true +} + +data "docker_registry_image" "connector" { + name = var.connector_imageref +} + +resource "cloudfoundry_app" "connector" { + name = "${var.app_prefix}-connector" + org_name = var.cf_org_name + space_name = var.cf_space_name + docker_image = "${local.connector_baseimage}@${data.docker_registry_image.connector.sha256_digest}" + memory = var.connector_memory + instances = var.connector_instances + disk_quota = "3G" + strategy = "rolling" + command = <<-COMMAND + # Make sure the Cloud Foundry-provided CA is recognized when making TLS connections + cat /etc/cf-system-certificates/* > /usr/local/share/ca-certificates/cf-system-certificates.crt + /usr/sbin/update-ca-certificates + /app/bin/boot_server_in_docker + COMMAND + health_check_type = "http" + health_check_http_endpoint = "/liveness" + routes = [{ + route = local.connector_route + protocol = "http1" + }] + + environment = { + FLASK_DEBUG : "0" + FLASK_SESSION_SECRET_KEY : random_password.connector_flask_secret_key.result + CONNECTOR_PROXY_PORT : "8080" + REQUESTS_CA_BUNDLE : "/etc/ssl/certs/ca-certificates.crt" + } +} + +data "docker_registry_image" "frontend" { + name = var.frontend_imageref +} + +resource "cloudfoundry_app" "frontend" { + name = "${var.app_prefix}-frontend" + org_name = var.cf_org_name + space_name = var.cf_space_name + docker_image = "${local.frontend_baseimage}@${data.docker_registry_image.frontend.sha256_digest}" + memory = var.frontend_memory + instances = var.frontend_instances + strategy = "rolling" + health_check_type = "port" + routes = [{ + route = local.frontend_route + protocol = "http1" + }] + + environment = { + APPLICATION_ROOT : "/" + PORT0 : "80" + SPIFFWORKFLOW_FRONTEND_RUNTIME_CONFIG_APP_ROUTING_STRATEGY : "path_based" + SPIFFWORKFLOW_FRONTEND_RUNTIME_CONFIG_BACKEND_BASE_URL : local.backend_url + BACKEND_BASE_URL : local.backend_url + } +} diff --git a/spiffworkflow/outputs.tf b/spiffworkflow/outputs.tf new file mode 100644 index 0000000..d0f993e --- /dev/null +++ b/spiffworkflow/outputs.tf @@ -0,0 +1,27 @@ +output "backend_url" { + value = local.backend_url + sensitive = true +} + +output "connector_url" { + value = local.connector_url + sensitive = true +} + +output "frontend_url" { + value = local.frontend_url + sensitive = true +} + +output "backend_app_id" { + value = local.backend_app_id +} + +output "connector_app_id" { + value = local.connector_app_id +} + +output "frontend_app_id" { + value = local.frontend_app_id +} + diff --git a/spiffworkflow/providers.tf b/spiffworkflow/providers.tf new file mode 100644 index 0000000..eb23850 --- /dev/null +++ b/spiffworkflow/providers.tf @@ -0,0 +1,13 @@ +terraform { + required_version = "~> 1.0" + required_providers { + cloudfoundry = { + source = "cloudfoundry/cloudfoundry" + version = ">=1.1.0" + } + docker = { + source = "kreuzwerker/docker" + version = "~>3.0.2" + } + } +} diff --git a/spiffworkflow/tests/main.tf b/spiffworkflow/tests/main.tf new file mode 100644 index 0000000..8ce1fa0 --- /dev/null +++ b/spiffworkflow/tests/main.tf @@ -0,0 +1,20 @@ +terraform { + required_providers { + cloudfoundry = { + source = "cloudfoundry/cloudfoundry" + version = "1.1.0" + } + docker = { + source = "kreuzwerker/docker" + version = "~>3.0.2" + } + } +} + +module "spiff" { + source = "./.." + cf_org_name = "gsa-tts-oros-fac" + cf_space_name = "sandbox-workflow" + frontend_imageref = "ghcr.io/sartography/spiffworkflow-frontend:main-latest" + backend_imageref = "ghcr.io/sartography/spiffworkflow-backend:main-latest" +} diff --git a/spiffworkflow/variables.tf b/spiffworkflow/variables.tf new file mode 100644 index 0000000..30389ab --- /dev/null +++ b/spiffworkflow/variables.tf @@ -0,0 +1,125 @@ +variable "cf_org_name" { + type = string + description = "cloud.gov organization name" +} + +variable "cf_space_name" { + type = string + description = "cloud.gov space in which to deploy the apps" +} + +variable "app_prefix" { + type = string + description = "prefix to use for the three application names (-[connector|backend|frontend])" + default = "spiffworkflow" +} + +variable "route_prefix" { + type = string + description = "prefix to use for the application routes (-connector.app.internal, .apps.fr.cloud.gov[/api]); leave empty to generate" + default = "" +} + +variable "rds_plan_name" { + type = string + description = "PSQL database service plan to use" + # See options at https://cloud.gov/docs/services/relational-database/#plans + default = "small-psql" +} + +variable "rds_json_params" { + description = "A JSON string of arbitrary parameters" + type = string + default = null + # See options at https://cloud.gov/docs/services/relational-database/#setting-optional-parameters-1 +} + +variable "tags" { + description = "A list of tags to add to the module's resource" + type = set(string) + default = [] +} + +variable "process_models_repository" { + type = string + description = "git repository with process models (for read-write, use SSH-style 'git@github.com:...' and supply your ssh_key)" + default = "https://github.com/GSA-TTS/gsa-process-models.git" +} + +variable "process_models_ssh_key" { + type = string + description = "private SSH key (only needed for read-write to SSH-style 'git@github.com:...' repositories)" + # Should look like: + # -----BEGIN OPENSSH PRIVATE KEY----- + # ... + # ... + # ... + # -----END OPENSSH PRIVATE KEY----- + default = "" +} + +variable "process_models_source_branch" { + type = string + description = "branch for reading process models" + default = "main" +} + +variable "process_models_publish_branch" { + type = string + description = "branch for publishing process model changes" + default = "publish-branch" +} + +variable "backend_memory" { + type = string + description = "Memory to allocate to backend app, including units" + default = "512M" +} + +variable "connector_memory" { + type = string + description = "Memory to allocate to connector proxy app, including units" + default = "128M" +} + +variable "frontend_memory" { + type = string + description = "Memory to allocate to frontend app, including units" + default = "256M" +} + +variable "backend_imageref" { + type = string + description = "imageref for the specific version of the backend that you want to use. See https://github.com/orgs/GSA-TTS/packages for options." + default = "ghcr.io/gsa-tts/spiffworkflow-backend:deploy-to-cloud-gov-latest" +} + +variable "connector_imageref" { + type = string + description = "imageref for the specific version of the connector that you want to use. See https://github.com/orgs/GSA-TTS/packages for options." + default = "ghcr.io/gsa-tts/connector-proxy-demo:deploy-to-cloud-gov-latest" +} + +variable "frontend_imageref" { + type = string + description = "imageref for the specific version of the frontend that you want to use. See https://github.com/orgs/GSA-TTS/packages for options." + default = "ghcr.io/gsa-tts/spiffworkflow-frontend:deploy-to-cloud-gov-latest" +} + +variable "backend_instances" { + type = number + description = "the number of instances of the backend application to run (default: 1)" + default = 1 +} + +variable "connector_instances" { + type = number + description = "the number of instances of the connector application to run (default: 1)" + default = 1 +} + +variable "frontend_instances" { + type = number + description = "the number of instances of the frontend application to run (default: 1)" + default = 1 +}