├── docs └── dns-structure.png ├── terraform ├── common │ ├── scripts │ │ └── opsman │ │ │ ├── apply_changes.sh │ │ │ ├── destroy_opsman.sh │ │ │ ├── wrap.sh │ │ │ ├── install_raw_tile.sh │ │ │ ├── configure_tile.sh │ │ │ ├── configure_opsman.sh │ │ │ ├── post_install_opsman.sh │ │ │ ├── install_tile.sh │ │ │ └── provision_opsman.sh │ ├── templates │ │ ├── tiles │ │ │ ├── antivirus_config.yml │ │ │ ├── credhub_config.yml │ │ │ ├── scs3_config.yml │ │ │ ├── gateway_config.yml │ │ │ ├── sso_config.yml │ │ │ ├── scdf_config.yml │ │ │ ├── metrics_config.yml │ │ │ ├── redis_config.yml │ │ │ ├── rabbitmq_config.yml │ │ │ ├── healthwatch_config.yml │ │ │ ├── pcc_config.yml │ │ │ ├── mysql_config.yml │ │ │ └── pas_config.yml │ │ ├── az-config.yml │ │ └── opsman_config.yml │ ├── providers.tf │ ├── outputs.tf │ ├── fim.tf │ ├── pcc.tf │ ├── redis.tf │ ├── mysql.tf │ ├── credhub.tf │ ├── rabbitmq.tf │ ├── scdf.tf │ ├── sso.tf │ ├── scs3.tf │ ├── antivirus.tf │ ├── gateway.tf │ ├── metrics.tf │ ├── healthwatch.tf │ ├── pas.tf │ ├── opsman.tf │ └── variables.tf ├── azure │ ├── providers.tf │ ├── cidrs.tf │ ├── templates │ │ ├── opsman_subnet_config.yml │ │ ├── pas_config_ops.yml │ │ └── opsman_config_ops.yml │ ├── dns.tf │ ├── outputs.tf │ ├── pas.tf │ ├── variables.tf │ ├── ssl.tf │ ├── opsman.tf │ └── azure.tf ├── gcp │ ├── providers.tf │ ├── storage.tf │ ├── templates │ │ ├── opsman_subnet_config.yml │ │ ├── opsman_config_ops.yml │ │ └── pas_config_ops.yml │ ├── dns.tf │ ├── iam.tf │ ├── outputs.tf │ ├── nat.tf │ ├── pas.tf │ ├── variables.tf │ ├── ssl.tf │ ├── opsman.tf │ └── gcp.tf └── aws │ ├── templates │ ├── vm_extension.yml │ ├── opsman_subnet_config.yml │ ├── pas_config_ops.yml │ └── opsman_config_ops.yml │ ├── dns.tf │ ├── outputs.tf │ ├── pas.tf │ ├── ssl.tf │ ├── variables.tf │ ├── iam.tf │ ├── aws.tf │ └── opsman.tf ├── bin ├── ssh-opsman.sh ├── opsman-info.sh ├── opsman-env.sh ├── cf-login.sh └── switch.sh ├── codebuild ├── bin │ ├── destroy.sh │ ├── build.sh │ ├── install.sh │ └── cloud.sh ├── buildspec.yml ├── destroy-buildspec.yml └── cloudformation │ └── cf.json ├── ci └── stack.sh └── README.md /docs/dns-structure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nthomson-pivotal/pcf-paasify/HEAD/docs/dns-structure.png -------------------------------------------------------------------------------- /terraform/common/scripts/opsman/apply_changes.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | source ~/.om_profile 6 | 7 | om apply-changes -------------------------------------------------------------------------------- /terraform/azure/providers.tf: -------------------------------------------------------------------------------- 1 | provider "template" { 2 | version = "~> 2.1.0" 3 | } 4 | 5 | provider "tls" { 6 | version = "~> 2.0.0" 7 | } -------------------------------------------------------------------------------- /terraform/gcp/providers.tf: -------------------------------------------------------------------------------- 1 | provider "template" { 2 | version = "~> 2.1.0" 3 | } 4 | 5 | provider "tls" { 6 | version = "~> 2.0.0" 7 | } -------------------------------------------------------------------------------- /terraform/common/scripts/opsman/destroy_opsman.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | source ~/.om_profile 6 | 7 | om delete-installation -f 8 | -------------------------------------------------------------------------------- /terraform/aws/templates/vm_extension.yml: -------------------------------------------------------------------------------- 1 | vm-extension-config: 2 | name: ${name} 3 | cloud_properties: 4 | security_groups: [${sg_name}, vms_security_group] -------------------------------------------------------------------------------- /terraform/common/templates/tiles/antivirus_config.yml: -------------------------------------------------------------------------------- 1 | product-name: p-antivirus 2 | product-properties: 3 | .properties.database_mirrors: 4 | value: official_mirror -------------------------------------------------------------------------------- /terraform/azure/cidrs.tf: -------------------------------------------------------------------------------- 1 | 2 | 3 | module "cidr_calculator" { 4 | source = "github.com/pivotal-cf/terraforming-aws?ref=3f77c15//modules/calculate_subnets" 5 | 6 | vpc_cidr = "${var.vpc_cidr}" 7 | } -------------------------------------------------------------------------------- /terraform/azure/templates/opsman_subnet_config.yml: -------------------------------------------------------------------------------- 1 | - iaas_identifier: ${id} 2 | cidr: ${cidr} 3 | dns: ${dns} 4 | gateway: ${gateway} 5 | reserved_ip_ranges: ${reserved} -------------------------------------------------------------------------------- /terraform/gcp/storage.tf: -------------------------------------------------------------------------------- 1 | resource "google_storage_bucket" "backup" { 2 | name = "${var.project}-${var.env_name}-backup" 3 | location = "${var.buckets_location}" 4 | force_destroy = true 5 | } -------------------------------------------------------------------------------- /terraform/common/providers.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = "~> 0.11" 3 | } 4 | 5 | provider "null" { 6 | version = "~> 2.1.0" 7 | } 8 | 9 | provider "random" { 10 | version = "~> 2.1.0" 11 | } -------------------------------------------------------------------------------- /terraform/aws/templates/opsman_subnet_config.yml: -------------------------------------------------------------------------------- 1 | - iaas_identifier: ${id} 2 | cidr: ${cidr} 3 | dns: ${dns} 4 | gateway: ${gateway} 5 | reserved_ip_ranges: ${reserved} 6 | availability_zone_names: 7 | - ${az} -------------------------------------------------------------------------------- /terraform/common/outputs.tf: -------------------------------------------------------------------------------- 1 | output "opsman_password" { 2 | value = "${local.opsman_password}" 3 | } 4 | 5 | output "opsman_version" { 6 | value = "${var.opsman_version}" 7 | } 8 | 9 | output "opsman_build" { 10 | value = "${var.opsman_build}" 11 | } -------------------------------------------------------------------------------- /terraform/common/scripts/opsman/wrap.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "Wrapping $1" 4 | 5 | { 6 | eval "$@" 7 | } || { 8 | echo "Caught error in $1" 9 | # This is needed to ensure output 10 | sleep 1 11 | exit 1 12 | } 13 | 14 | echo "Finished $1" -------------------------------------------------------------------------------- /terraform/common/templates/az-config.yml: -------------------------------------------------------------------------------- 1 | network-properties: 2 | network: 3 | name: pas 4 | service_network: 5 | name: services 6 | other_availability_zones: 7 | - name: ${az1} 8 | - name: ${az2} 9 | - name: ${az3} 10 | singleton_availability_zone: 11 | name: ${az1} -------------------------------------------------------------------------------- /terraform/gcp/templates/opsman_subnet_config.yml: -------------------------------------------------------------------------------- 1 | - iaas_identifier: ${id} 2 | cidr: ${cidr} 3 | dns: ${dns} 4 | gateway: ${gateway} 5 | reserved_ip_ranges: ${reserved} 6 | availability_zone_names: 7 | - ${az1} 8 | - ${az2} 9 | - ${az3} -------------------------------------------------------------------------------- /bin/ssh-opsman.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" 4 | 5 | tmp_ssh_file="$(mktemp)" 6 | 7 | terraform output opsman_ssh_private_key > $tmp_ssh_file 8 | 9 | chmod 400 $tmp_ssh_file 10 | 11 | ssh -i $tmp_ssh_file ubuntu@$(terraform output opsman_host) -------------------------------------------------------------------------------- /codebuild/bin/destroy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | . $CODEBUILD_SRC_DIR/codebuild/bin/cloud.sh 6 | 7 | TF_WARN_OUTPUT_ERRORS=1 TF_VAR_env_name=$env TF_VAR_dns_suffix=$dns_suffix TF_VAR_pivnet_token=$pivnet_token \ 8 | terraform destroy -input=false -auto-approve -state=$HOME/state/terraform.tfstate $CLOUD_TF_DIR 9 | -------------------------------------------------------------------------------- /bin/opsman-info.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" 6 | 7 | source $DIR/opsman-env.sh 8 | 9 | echo "OpsManager information" 10 | echo "" 11 | echo "Endpoint: https://${OM_TARGET}" 12 | echo "Username: ${OM_USERNAME}" 13 | echo "Password: ${OM_PASSWORD}" 14 | -------------------------------------------------------------------------------- /terraform/common/templates/tiles/credhub_config.yml: -------------------------------------------------------------------------------- 1 | product-name: credhub-service-broker 2 | network-properties: 3 | network: 4 | name: pas 5 | service_network: 6 | name: services 7 | other_availability_zones: 8 | - name: ${az1} 9 | - name: ${az2} 10 | - name: ${az3} 11 | singleton_availability_zone: 12 | name: ${az1} -------------------------------------------------------------------------------- /terraform/common/templates/tiles/scs3_config.yml: -------------------------------------------------------------------------------- 1 | product-name: p_spring-cloud-services 2 | network-properties: 3 | network: 4 | name: pas 5 | service_network: 6 | name: services 7 | other_availability_zones: 8 | - name: ${az1} 9 | - name: ${az2} 10 | - name: ${az3} 11 | singleton_availability_zone: 12 | name: ${az1} -------------------------------------------------------------------------------- /bin/opsman-env.sh: -------------------------------------------------------------------------------- 1 | # Source me 2 | 3 | if [ ! -f "terraform.tfstate" ]; then 4 | echo "Error: Must be run from directory that contains terraform.tfstate file" 5 | exit 1 6 | fi 7 | 8 | export OM_TARGET=$(terraform output opsman_host) 9 | export OM_USERNAME=$(terraform output opsman_user) 10 | export OM_PASSWORD=$(terraform output opsman_password) -------------------------------------------------------------------------------- /terraform/common/templates/tiles/gateway_config.yml: -------------------------------------------------------------------------------- 1 | product-name: p_spring-cloud-gateway-service 2 | network-properties: 3 | network: 4 | name: pas 5 | service_network: 6 | name: services 7 | other_availability_zones: 8 | - name: ${az1} 9 | - name: ${az2} 10 | - name: ${az3} 11 | singleton_availability_zone: 12 | name: ${az1} -------------------------------------------------------------------------------- /terraform/common/templates/tiles/sso_config.yml: -------------------------------------------------------------------------------- 1 | product-name: Pivotal_Single_Sign-On_Service 2 | network-properties: 3 | network: 4 | name: pas 5 | service_network: 6 | name: services 7 | other_availability_zones: 8 | - name: ${az1} 9 | - name: ${az2} 10 | - name: ${az3} 11 | singleton_availability_zone: 12 | name: ${az1} 13 | -------------------------------------------------------------------------------- /terraform/common/templates/opsman_config.yml: -------------------------------------------------------------------------------- 1 | network-assignment: 2 | network: 3 | name: management 4 | other_availability_zones: [] 5 | singleton_availability_zone: 6 | name: ${az} 7 | properties-configuration: 8 | director_configuration: 9 | ntp_servers_string: time.google.com 10 | retry_bosh_deploys: true 11 | resurrector_enabled: true 12 | -------------------------------------------------------------------------------- /terraform/aws/dns.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | base_domain = "${var.env_name}.${var.dns_suffix}" 3 | } 4 | 5 | data "aws_route53_zone" "selected" { 6 | name = "${var.dns_suffix}." 7 | } 8 | 9 | resource "aws_route53_record" "ns" { 10 | zone_id = "${data.aws_route53_zone.selected.zone_id}" 11 | name = "${local.base_domain}" 12 | type = "NS" 13 | ttl = "30" 14 | 15 | records = [ 16 | "${module.aws.env_dns_zone_name_servers}", 17 | ] 18 | } -------------------------------------------------------------------------------- /terraform/common/scripts/opsman/install_raw_tile.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | source ~/.om_profile 6 | 7 | om_product=$1 8 | version=$2 9 | iaas=$3 10 | url=$4 11 | 12 | filename="$om_product-$version" 13 | 14 | if [ ! -f "$filename" ]; then 15 | echo "Downloading $om_product tile..." 16 | wget -q -O $filename $url 17 | else 18 | echo "Using cached product file" 19 | fi 20 | 21 | om upload-product -p $filename 22 | 23 | om stage-product -p $om_product -v $version -------------------------------------------------------------------------------- /codebuild/buildspec.yml: -------------------------------------------------------------------------------- 1 | # WARNING: In general do not edit this file 2 | # CodeBuild defaults to master on Git 3 | 4 | version: 0.2 5 | 6 | phases: 7 | install: 8 | commands: 9 | - . $CODEBUILD_SRC_DIR/bin/switch.sh 10 | - . $CODEBUILD_SRC_DIR/codebuild/bin/install.sh 11 | build: 12 | commands: 13 | - . $CODEBUILD_SRC_DIR/codebuild/bin/build.sh 14 | post_build: 15 | commands: 16 | - aws s3 cp $HOME/state s3://$state_bucket/$state_key --recursive -------------------------------------------------------------------------------- /terraform/common/fim.tf: -------------------------------------------------------------------------------- 1 | resource "null_resource" "setup_fim" { 2 | depends_on = ["null_resource.setup_pas"] 3 | 4 | provisioner "remote-exec" { 5 | inline = ["wrap install_tile p-fim-addon ${lookup(var.tile_versions, "fim")} pivotal ${var.iaas} p-fim"] 6 | } 7 | 8 | count = "${contains(var.tiles, "fim") ? 1 : 0}" 9 | 10 | connection { 11 | host = "${var.opsman_host}" 12 | user = "ubuntu" 13 | private_key = "${var.opsman_ssh_key}" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /terraform/common/templates/tiles/scdf_config.yml: -------------------------------------------------------------------------------- 1 | product-name: p-dataflow 2 | network-properties: 3 | network: 4 | name: pas 5 | service_network: 6 | name: services 7 | other_availability_zones: 8 | - name: ${az1} 9 | - name: ${az2} 10 | - name: ${az3} 11 | singleton_availability_zone: 12 | name: ${az1} 13 | product-properties: 14 | .properties.dataflow_messaging_service: 15 | value: p.rabbitmq 16 | .properties.dataflow_messaging_plan: 17 | value: single-node-3.7 -------------------------------------------------------------------------------- /codebuild/destroy-buildspec.yml: -------------------------------------------------------------------------------- 1 | # WARNING: In general do not edit this file 2 | # CodeBuild defaults to master on Git 3 | 4 | version: 0.2 5 | 6 | phases: 7 | install: 8 | commands: 9 | - . $CODEBUILD_SRC_DIR/bin/switch.sh 10 | - . $CODEBUILD_SRC_DIR/codebuild/bin/install.sh 11 | build: 12 | commands: 13 | - . $CODEBUILD_SRC_DIR/codebuild/bin/destroy.sh 14 | - aws s3 rm s3://$state_bucket/$state_key --recursive 15 | post_build: 16 | commands: 17 | - echo 'None' -------------------------------------------------------------------------------- /bin/cf-login.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" 6 | 7 | source $DIR/opsman-env.sh 8 | 9 | cf_api_endpoint=$(terraform output cf_api_endpoint) 10 | cf_admin_password=$(om credentials -p cf -c .uaa.admin_credentials -t json | jq -r '.password') 11 | 12 | echo "Authenticating to CF with username 'admin' and password '$cf_admin_password'" 13 | echo "" 14 | 15 | cf login -a $cf_api_endpoint -u admin -p $cf_admin_password -o system -s system -------------------------------------------------------------------------------- /terraform/gcp/dns.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | base_domain = "${var.env_name}.${var.dns_suffix}" 3 | } 4 | 5 | resource "google_dns_record_set" "ns" { 6 | managed_zone = "${var.dns_zone_name}" 7 | name = "${local.base_domain}." 8 | type = "NS" 9 | ttl = "30" 10 | 11 | rrdatas = [ 12 | "${module.gcp.env_dns_zone_name_servers[0]}", 13 | "${module.gcp.env_dns_zone_name_servers[1]}", 14 | "${module.gcp.env_dns_zone_name_servers[2]}", 15 | "${module.gcp.env_dns_zone_name_servers[3]}" 16 | ] 17 | } -------------------------------------------------------------------------------- /terraform/azure/dns.tf: -------------------------------------------------------------------------------- 1 | data "azurerm_dns_zone" "paasify" { 2 | name = "${var.dns_suffix}" 3 | } 4 | 5 | resource "azurerm_dns_ns_record" "test" { 6 | name = "${var.env_name}" 7 | zone_name = "${data.azurerm_dns_zone.paasify.name}" 8 | resource_group_name = "${data.azurerm_dns_zone.paasify.resource_group_name}" 9 | 10 | ttl = 300 11 | 12 | records = [ 13 | "${module.azure.env_dns_zone_name_servers}", 14 | ] 15 | } 16 | 17 | locals { 18 | base_domain = "${var.env_name}.${var.dns_suffix}" 19 | } -------------------------------------------------------------------------------- /terraform/common/scripts/opsman/configure_tile.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | PRODUCT_NAME=$1 6 | 7 | source ~/.om_profile 8 | 9 | if [ -z "$PRODUCT_NAME" ]; then 10 | echo "Error setting up tile - product name not specified" 11 | exit 1 12 | fi 13 | 14 | config_path="/home/ubuntu/config/$PRODUCT_NAME-config.yml" 15 | ops_path="/home/ubuntu/config/$PRODUCT_NAME-config-ops.yml" 16 | 17 | if [ -f "$ops_path" ]; then 18 | om configure-product -c "$config_path" -o "$ops_path" 19 | else 20 | om configure-product -c "$config_path" 21 | fi -------------------------------------------------------------------------------- /terraform/gcp/iam.tf: -------------------------------------------------------------------------------- 1 | resource "google_service_account" "account" { 2 | account_id = "paasify-${var.env_name}" 3 | display_name = "Paasify ${var.env_name}" 4 | } 5 | 6 | resource "google_service_account_key" "key" { 7 | depends_on = ["google_project_iam_member.admin"] 8 | service_account_id = "${google_service_account.account.name}" 9 | } 10 | 11 | resource "google_project_iam_member" "admin" { 12 | project = "${var.project}" 13 | role = "roles/owner" 14 | member = "serviceAccount:${google_service_account.account.email}" 15 | } 16 | -------------------------------------------------------------------------------- /terraform/common/templates/tiles/metrics_config.yml: -------------------------------------------------------------------------------- 1 | product-name: apmPostgres 2 | network-properties: 3 | network: 4 | name: pas 5 | other_availability_zones: 6 | - name: ${az1} 7 | - name: ${az2} 8 | - name: ${az3} 9 | singleton_availability_zone: 10 | name: ${az1} 11 | resource-config: 12 | redis: 13 | persistent_disk: 14 | size_mb: '51200' 15 | mysql: 16 | persistent_disk: 17 | size_mb: '51200' 18 | instance_type: 19 | id: ${mysql_instance_type} 20 | postgres: 21 | instance_type: 22 | id: ${postgres_instance_type} -------------------------------------------------------------------------------- /terraform/gcp/outputs.tf: -------------------------------------------------------------------------------- 1 | output "opsman_host" { 2 | value = "${module.gcp.ops_manager_dns}" 3 | } 4 | 5 | output "opsman_user" { 6 | value = "${var.opsman_user}" 7 | } 8 | 9 | output "opsman_password" { 10 | value = "${module.common.opsman_password}" 11 | } 12 | 13 | output "opsman_ssh_private_key" { 14 | value = "${module.gcp.ops_manager_ssh_private_key}" 15 | } 16 | 17 | output "apps_domain" { 18 | value = "${module.gcp.apps_domain}" 19 | } 20 | 21 | output "sys_domain" { 22 | value = "${module.gcp.sys_domain}" 23 | } 24 | 25 | output "cf_api_endpoint" { 26 | value = "api.${module.gcp.sys_domain}" 27 | } -------------------------------------------------------------------------------- /terraform/azure/outputs.tf: -------------------------------------------------------------------------------- 1 | output "opsman_host" { 2 | value = "${module.azure.ops_manager_dns}" 3 | } 4 | 5 | output "opsman_user" { 6 | value = "${var.opsman_user}" 7 | } 8 | 9 | output "opsman_password" { 10 | value = "${module.common.opsman_password}" 11 | } 12 | 13 | output "opsman_ssh_private_key" { 14 | value = "${module.azure.ops_manager_ssh_private_key}" 15 | } 16 | 17 | output "apps_domain" { 18 | value = "${module.azure.apps_domain}" 19 | } 20 | 21 | output "sys_domain" { 22 | value = "${module.azure.sys_domain}" 23 | } 24 | 25 | output "cf_api_endpoint" { 26 | value = "api.${module.azure.sys_domain}" 27 | } -------------------------------------------------------------------------------- /terraform/gcp/nat.tf: -------------------------------------------------------------------------------- 1 | resource "google_compute_router" "router" { 2 | name = "paasify-${var.env_name}-router" 3 | region = "${var.region}" 4 | network = "${module.gcp.network_name}" 5 | 6 | bgp { 7 | asn = 64514 8 | } 9 | } 10 | 11 | resource "google_compute_router_nat" "nat" { 12 | name = "paasify-${var.env_name}-nat" 13 | router = "${google_compute_router.router.name}" 14 | region = "${var.region}" 15 | nat_ip_allocate_option = "AUTO_ONLY" 16 | source_subnetwork_ip_ranges_to_nat = "ALL_SUBNETWORKS_ALL_IP_RANGES" 17 | } -------------------------------------------------------------------------------- /bin/switch.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Switch to the correct Git branch/tag before we run anything else 4 | # Need this to make up for CodeBuild limitations 5 | 6 | set -e 7 | 8 | if [ ! -d "$CODEBUILD_SRC_DIR/.git" ]; then 9 | echo "No .git, skipping branch switch" 10 | exit 11 | fi 12 | 13 | if [ ! -z "$branch" ]; then 14 | echo "Checking out Git branch $branch" 15 | 16 | git checkout $branch 17 | elif [ ! -z "$tag" ]; then 18 | echo "Checking out Git tag $tag" 19 | 20 | git checkout tags/$TAG 21 | else 22 | echo "No Git branch/tag specified, taking no action" 23 | fi 24 | 25 | echo "Working on commit $(git rev-parse HEAD)" 26 | -------------------------------------------------------------------------------- /codebuild/bin/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | . $CODEBUILD_SRC_DIR/codebuild/bin/cloud.sh 6 | 7 | tf_state=$HOME/state/terraform.tfstate 8 | 9 | export TF_VAR_env_name=$env 10 | export TF_VAR_dns_suffix=$dns_suffix 11 | 12 | if [ ! -z "$tiles" ]; then 13 | export TF_VAR_tiles="[$tiles]" 14 | else 15 | export TF_VAR_tiles="[]" 16 | fi 17 | 18 | export TF_VAR_auto_apply=$auto_apply 19 | 20 | if [ "$command" = "plan" ]; then 21 | terraform plan -state=$tf_state $CLOUD_TF_DIR 22 | elif [ "$command" = "output" ]; then 23 | terraform output -state=$tf_state 24 | else 25 | terraform apply -auto-approve -input=false -state=$tf_state $CLOUD_TF_DIR 26 | fi -------------------------------------------------------------------------------- /codebuild/bin/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | TERRAFORM_VERSION=0.11.13 6 | 7 | # Dependencies 8 | wget -q -O terraform.zip https://releases.hashicorp.com/terraform/${TERRAFORM_VERSION}/terraform_${TERRAFORM_VERSION}_linux_amd64.zip 9 | sudo unzip terraform.zip -d /bin && rm terraform.zip 10 | 11 | apt-get update 12 | apt-get install -qq -y curl jq 13 | 14 | # Setup state directory 15 | mkdir $HOME/state 16 | 17 | echo "Pulling TF state from s3://$state_bucket/$state_key" 18 | aws s3 cp s3://$state_bucket/$state_key $HOME/state --recursive 19 | 20 | . $CODEBUILD_SRC_DIR/codebuild/bin/cloud.sh 21 | 22 | # Init Terraform 23 | terraform init -input=false $CLOUD_TF_DIR 24 | -------------------------------------------------------------------------------- /terraform/common/templates/tiles/redis_config.yml: -------------------------------------------------------------------------------- 1 | product-name: p-redis 2 | product-properties: 3 | .properties.syslog_selector: 4 | value: 'No' 5 | .properties.small_plan_selector.active.az_multi_select: 6 | value: 7 | - ${az1} 8 | .properties.medium_plan_selector.active.az_multi_select: 9 | value: 10 | - ${az1} 11 | .properties.large_plan_selector.active.az_multi_select: 12 | value: 13 | - ${az1} 14 | network-properties: 15 | network: 16 | name: pas 17 | service_network: 18 | name: services 19 | other_availability_zones: 20 | - name: ${az1} 21 | - name: ${az2} 22 | - name: ${az3} 23 | singleton_availability_zone: 24 | name: ${az1} -------------------------------------------------------------------------------- /terraform/aws/outputs.tf: -------------------------------------------------------------------------------- 1 | output "opsman_host" { 2 | value = "${module.aws.ops_manager_dns}" 3 | } 4 | 5 | output "opsman_user" { 6 | value = "${var.opsman_user}" 7 | } 8 | 9 | output "opsman_password" { 10 | value = "${module.common.opsman_password}" 11 | } 12 | 13 | output "opsman_ssh_private_key" { 14 | value = "${module.aws.ops_manager_ssh_private_key}" 15 | } 16 | 17 | output "vpc_id" { 18 | value = "${module.aws.vpc_id}" 19 | } 20 | 21 | output "apps_domain" { 22 | value = "${module.aws.apps_domain}" 23 | } 24 | 25 | output "sys_domain" { 26 | value = "${module.aws.sys_domain}" 27 | } 28 | 29 | output "cf_api_endpoint" { 30 | value = "api.${module.aws.sys_domain}" 31 | } -------------------------------------------------------------------------------- /terraform/azure/pas.tf: -------------------------------------------------------------------------------- 1 | # TODO: Enabled S3 encryption for filestore 2 | data "template_file" "pas_product_configuration" { 3 | template = "${chomp(file("${path.module}/templates/pas_config_ops.yml"))}" 4 | 5 | vars { 6 | account_name = "${module.azure.cf_storage_account_name}" 7 | access_key = "${module.azure.cf_storage_account_access_key}" 8 | droplets_container = "${module.azure.cf_droplets_storage_container}" 9 | packages_container = "${module.azure.cf_packages_storage_container}" 10 | buildpacks_container = "${module.azure.cf_buildpacks_storage_container}" 11 | resources_container = "${module.azure.cf_resources_storage_container}" 12 | } 13 | } -------------------------------------------------------------------------------- /terraform/gcp/pas.tf: -------------------------------------------------------------------------------- 1 | data "template_file" "pas_product_configuration" { 2 | template = "${chomp(file("${path.module}/templates/pas_config_ops.yml"))}" 3 | 4 | vars { 5 | project = "${module.gcp.pas_blobstore_service_account_project}" 6 | service_account_email = "${google_service_account.account.email}" 7 | service_account_key = "${jsonencode(base64decode(google_service_account_key.key.private_key))}" 8 | droplets_bucket = "${module.gcp.droplets_bucket}" 9 | packages_bucket = "${module.gcp.packages_bucket}" 10 | buildpacks_bucket = "${module.gcp.buildpacks_bucket}" 11 | resources_bucket = "${module.gcp.resources_bucket}" 12 | backup_bucket = "${google_storage_bucket.backup.name}" 13 | } 14 | } -------------------------------------------------------------------------------- /terraform/gcp/templates/opsman_config_ops.yml: -------------------------------------------------------------------------------- 1 | - type: replace 2 | path: /az-configuration? 3 | value: 4 | - name: ${az1} 5 | - name: ${az2} 6 | - name: ${az3} 7 | 8 | - type: replace 9 | path: /networks-configuration? 10 | value: 11 | icmp_checks_enabled: false 12 | networks: 13 | - name: management 14 | subnets: 15 | ${management_subnets} 16 | - name: pas 17 | subnets: 18 | ${pas_subnets} 19 | - name: services 20 | subnets: 21 | ${services_subnets} 22 | 23 | - type: replace 24 | path: /properties-configuration/iaas_configuration?/project? 25 | value: ${project} 26 | 27 | - type: replace 28 | path: /properties-configuration/iaas_configuration?/associated_service_account? 29 | value: ${service_account} 30 | -------------------------------------------------------------------------------- /terraform/common/templates/tiles/rabbitmq_config.yml: -------------------------------------------------------------------------------- 1 | product-name: p-rabbitmq 2 | product-properties: 3 | .properties.syslog_selector: 4 | value: disabled 5 | .properties.on_demand_broker_plan_1_cf_service_access: 6 | value: enable 7 | .properties.on_demand_broker_plan_1_rabbitmq_az_placement: 8 | value: 9 | - ${az1} 10 | - ${az2} 11 | - ${az3} 12 | .properties.on_demand_broker_plan_1_disk_limit_acknowledgement: 13 | value: 14 | - acknowledge 15 | .properties.on_demand_broker_plan_1_instance_quota: 16 | value: '5' 17 | network-properties: 18 | network: 19 | name: pas 20 | service_network: 21 | name: services 22 | other_availability_zones: 23 | - name: ${az1} 24 | - name: ${az2} 25 | - name: ${az3} 26 | singleton_availability_zone: 27 | name: ${az1} -------------------------------------------------------------------------------- /terraform/common/scripts/opsman/configure_opsman.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | source ~/.om_profile 6 | 7 | echo "Preparing to configure OpsMan..." 8 | 9 | if [ -f "/home/ubuntu/vm_extensions.txt" ]; then 10 | vm_extensions=$(cat /home/ubuntu/vm_extensions.txt | tr -d '[:space:]') 11 | 12 | if [ ! -z "${vm_extensions}" ]; then 13 | rm -f vm-extension* 14 | 15 | csplit --digits=2 --quiet --prefix=vm-extension --suppress-matched /home/ubuntu/vm_extensions.txt "/|/" "{*}" 16 | 17 | shopt -s nullglob 18 | for f in vm-extension* 19 | do 20 | echo "Applying VM extension $f" 21 | om create-vm-extension --config $f 22 | done 23 | fi 24 | fi 25 | 26 | echo 'Configuring OpsMan...' 27 | om configure-director -c /home/ubuntu/config/opsman-config.yml --ops-file /home/ubuntu/config/opsman-config-ops.yml 28 | 29 | om apply-changes -n p-bosh -------------------------------------------------------------------------------- /terraform/common/templates/tiles/healthwatch_config.yml: -------------------------------------------------------------------------------- 1 | product-name: p-healthwatch 2 | product-properties: 3 | .properties.opsman.enable.url: 4 | value: 'https://${om_domain}' 5 | .healthwatch-forwarder.health_check_az: 6 | value: ${bosh_az} 7 | .properties.boshtasks: 8 | value: disable 9 | network-properties: 10 | network: 11 | name: pas 12 | service_network: 13 | name: services 14 | other_availability_zones: 15 | - name: ${az1} 16 | - name: ${az2} 17 | - name: ${az3} 18 | singleton_availability_zone: 19 | name: ${az1} 20 | resource-config: 21 | healthwatch-forwarder: 22 | instances: 2 23 | persistent_disk: 24 | size_mb: '51200' 25 | instance_type: 26 | id: ${forwarder_instance_type} 27 | mysql: 28 | instances: 1 29 | persistent_disk: 30 | size_mb: '51200' 31 | instance_type: 32 | id: ${mysql_instance_type} -------------------------------------------------------------------------------- /ci/stack.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" 6 | 7 | cloud=$1 8 | 9 | if [ -z "$cloud" ]; then 10 | echo "Error: Missing cloud name" 11 | exit 1 12 | fi 13 | 14 | 15 | branch=$(git rev-parse --abbrev-ref HEAD) 16 | 17 | env_name="ci-${branch//./-}" 18 | stack_name="paasify-${env_name}-${cloud}" 19 | 20 | echo "Updating stack $stack_name for environment $env_name on $cloud" 21 | 22 | # aws cli doens't seem to like when this is formatted on to multiple lines, bleh 23 | aws cloudformation create-stack --stack-name $stack_name --capabilities CAPABILITY_NAMED_IAM --template-body file://$DIR/../codebuild/cloudformation/cf.json --parameters ParameterKey=EnvName,ParameterValue=$env_name ParameterKey=DnsSuffix,ParameterValue=$cloud.paasify.org ParameterKey=Cloud,ParameterValue=$cloud ParameterKey=SourceBranch,ParameterValue=$branch -------------------------------------------------------------------------------- /terraform/aws/pas.tf: -------------------------------------------------------------------------------- 1 | # TODO: Enabled S3 encryption for filestore 2 | data "template_file" "pas_product_configuration" { 3 | template = "${chomp(file("${path.module}/templates/pas_config_ops.yml"))}" 4 | 5 | vars { 6 | region = "${var.region}" 7 | access_key = "${aws_iam_access_key.pas_buckets.id}" 8 | secret_key = "${aws_iam_access_key.pas_buckets.secret}" 9 | 10 | droplets_bucket = "${module.aws.pas_droplets_bucket}" 11 | packages_bucket = "${module.aws.pas_packages_bucket}" 12 | buildpacks_bucket = "${module.aws.pas_buildpacks_bucket}" 13 | resources_bucket = "${module.aws.pas_resources_bucket}" 14 | bucket_encryption = "${var.encrypt_pas_buckets}" 15 | kms_id = "${var.encrypt_pas_buckets == "1" ? module.aws.blobstore_kms_key_id : ""}" 16 | 17 | web_lb_extensions = "web_lb_security_groups" 18 | ssh_lb_extensions = "ssh_lb_security_groups" 19 | } 20 | } -------------------------------------------------------------------------------- /terraform/common/pcc.tf: -------------------------------------------------------------------------------- 1 | data "template_file" "pcc_configuration" { 2 | template = "${chomp(file("${path.module}/templates/tiles/pcc_config.yml"))}" 3 | 4 | vars { 5 | az1 = "${var.azs[0]}" 6 | az2 = "${var.azs[1]}" 7 | az3 = "${var.azs[2]}" 8 | } 9 | } 10 | 11 | resource "null_resource" "setup_pcc" { 12 | depends_on = ["null_resource.setup_pas"] 13 | 14 | provisioner "remote-exec" { 15 | inline = ["wrap install_tile p-cloudcache ${lookup(var.tile_versions, "pcc")} pivotal ${var.iaas}"] 16 | } 17 | 18 | provisioner "file" { 19 | content = "${data.template_file.pcc_configuration.rendered}" 20 | destination = "~/config/p-cloudcache-config.yml" 21 | } 22 | 23 | provisioner "remote-exec" { 24 | inline = ["wrap configure_tile p-cloudcache"] 25 | } 26 | 27 | count = "${contains(var.tiles, "pcc") ? 1 : 0}" 28 | 29 | connection { 30 | host = "${var.opsman_host}" 31 | user = "ubuntu" 32 | private_key = "${var.opsman_ssh_key}" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /terraform/common/redis.tf: -------------------------------------------------------------------------------- 1 | data "template_file" "redis_configuration" { 2 | template = "${chomp(file("${path.module}/templates/tiles/redis_config.yml"))}" 3 | 4 | vars { 5 | az1 = "${var.azs[0]}" 6 | az2 = "${var.azs[1]}" 7 | az3 = "${var.azs[2]}" 8 | } 9 | } 10 | 11 | resource "null_resource" "setup_redis" { 12 | depends_on = ["null_resource.setup_pas"] 13 | 14 | provisioner "remote-exec" { 15 | inline = ["wrap install_tile p-redis ${lookup(var.tile_versions, "redis")} pivotal ${var.iaas}"] 16 | } 17 | 18 | provisioner "file" { 19 | content = "${data.template_file.redis_configuration.rendered}" 20 | destination = "~/config/p-redis-config.yml" 21 | } 22 | 23 | provisioner "remote-exec" { 24 | inline = ["wrap configure_tile p-redis"] 25 | } 26 | 27 | count = "${contains(var.tiles, "redis") ? 1 : 0}" 28 | 29 | connection { 30 | host = "${var.opsman_host}" 31 | user = "ubuntu" 32 | private_key = "${var.opsman_ssh_key}" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /terraform/common/templates/tiles/pcc_config.yml: -------------------------------------------------------------------------------- 1 | product-name: p-cloudcache 2 | product-properties: 3 | .properties.errand_plan: 4 | value: plan1 5 | .properties.plan1_enable_service_plan.enable.service_instance_azs: 6 | value: 7 | - ${az1} 8 | - ${az2} 9 | - ${az3} 10 | .properties.plan2_enable_service_plan: 11 | value: disable 12 | .properties.plan3_enable_service_plan: 13 | value: disable 14 | .properties.plan4_enable_service_plan: 15 | value: disable 16 | .properties.plan5_enable_service_plan: 17 | value: disable 18 | .properties.dev_plan_enable_service_plan: 19 | value: disable 20 | .properties.small_footprint_enable_service_plan: 21 | value: disable 22 | .properties.confirm_tls_setup: 23 | value: X 24 | network-properties: 25 | network: 26 | name: pas 27 | service_network: 28 | name: services 29 | other_availability_zones: 30 | - name: ${az1} 31 | - name: ${az2} 32 | - name: ${az3} 33 | singleton_availability_zone: 34 | name: ${az1} -------------------------------------------------------------------------------- /terraform/common/mysql.tf: -------------------------------------------------------------------------------- 1 | data "template_file" "mysql_configuration" { 2 | template = "${chomp(file("${path.module}/templates/tiles/mysql_config.yml"))}" 3 | 4 | vars { 5 | az1 = "${var.azs[0]}" 6 | az2 = "${var.azs[1]}" 7 | az3 = "${var.azs[2]}" 8 | } 9 | } 10 | 11 | resource "null_resource" "setup_mysql" { 12 | depends_on = ["null_resource.setup_pas"] 13 | 14 | provisioner "remote-exec" { 15 | inline = ["wrap install_tile pivotal-mysql ${lookup(var.tile_versions, "mysql")} .pivotal ${var.iaas}"] 16 | } 17 | 18 | provisioner "file" { 19 | content = "${data.template_file.mysql_configuration.rendered}" 20 | destination = "~/config/pivotal-mysql-config.yml" 21 | } 22 | 23 | provisioner "remote-exec" { 24 | inline = ["wrap configure_tile pivotal-mysql"] 25 | } 26 | 27 | count = "${contains(var.tiles, "mysql") || contains(var.tiles, "scdf") ? 1 : 0}" 28 | 29 | connection { 30 | host = "${var.opsman_host}" 31 | user = "ubuntu" 32 | private_key = "${var.opsman_ssh_key}" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /terraform/common/credhub.tf: -------------------------------------------------------------------------------- 1 | data "template_file" "credhub_configuration" { 2 | template = "${chomp(file("${path.module}/templates/tiles/credhub_config.yml"))}" 3 | 4 | vars { 5 | az1 = "${var.azs[0]}" 6 | az2 = "${var.azs[1]}" 7 | az3 = "${var.azs[2]}" 8 | } 9 | } 10 | 11 | resource "null_resource" "setup_credhub" { 12 | depends_on = ["null_resource.setup_pas"] 13 | 14 | provisioner "remote-exec" { 15 | inline = ["wrap install_tile credhub-service-broker ${lookup(var.tile_versions, "credhub")} pivotal ${var.iaas}"] 16 | } 17 | 18 | provisioner "file" { 19 | content = "${data.template_file.credhub_configuration.rendered}" 20 | destination = "~/config/credhub-service-broker-config.yml" 21 | } 22 | 23 | provisioner "remote-exec" { 24 | inline = ["wrap configure_tile credhub-service-broker"] 25 | } 26 | 27 | count = "${contains(var.tiles, "credhub") ? 1 : 0}" 28 | 29 | connection { 30 | host = "${var.opsman_host}" 31 | user = "ubuntu" 32 | private_key = "${var.opsman_ssh_key}" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /terraform/common/rabbitmq.tf: -------------------------------------------------------------------------------- 1 | data "template_file" "rabbitmq_configuration" { 2 | template = "${chomp(file("${path.module}/templates/tiles/rabbitmq_config.yml"))}" 3 | 4 | vars { 5 | az1 = "${var.azs[0]}" 6 | az2 = "${var.azs[1]}" 7 | az3 = "${var.azs[2]}" 8 | } 9 | } 10 | 11 | resource "null_resource" "setup_rabbitmq" { 12 | depends_on = ["null_resource.setup_pas"] 13 | 14 | provisioner "remote-exec" { 15 | inline = ["wrap install_tile p-rabbitmq ${lookup(var.tile_versions, "rabbit")} pivotal ${var.iaas}"] 16 | } 17 | 18 | provisioner "file" { 19 | content = "${data.template_file.rabbitmq_configuration.rendered}" 20 | destination = "~/config/p-rabbitmq-config.yml" 21 | } 22 | 23 | provisioner "remote-exec" { 24 | inline = ["wrap configure_tile p-rabbitmq"] 25 | } 26 | 27 | count = "${contains(var.tiles, "rabbit") || contains(var.tiles, "scdf") ? 1 : 0}" 28 | 29 | connection { 30 | host = "${var.opsman_host}" 31 | user = "ubuntu" 32 | private_key = "${var.opsman_ssh_key}" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /terraform/common/scdf.tf: -------------------------------------------------------------------------------- 1 | data "template_file" "scdf_configuration" { 2 | template = "${chomp(file("${path.module}/templates/tiles/scdf_config.yml"))}" 3 | 4 | vars { 5 | az1 = "${var.azs[0]}" 6 | az2 = "${var.azs[1]}" 7 | az3 = "${var.azs[2]}" 8 | } 9 | } 10 | 11 | resource "null_resource" "setup_scdf" { 12 | depends_on = ["null_resource.setup_pas", "null_resource.setup_mysql", "null_resource.setup_rabbitmq"] 13 | 14 | provisioner "remote-exec" { 15 | inline = ["wrap install_tile p-dataflow ${lookup(var.tile_versions, "scdf")} pivotal ${var.iaas}"] 16 | } 17 | 18 | provisioner "file" { 19 | content = "${data.template_file.scdf_configuration.rendered}" 20 | destination = "~/config/p-dataflow-config.yml" 21 | } 22 | 23 | provisioner "remote-exec" { 24 | inline = ["wrap configure_tile p-dataflow"] 25 | } 26 | 27 | count = "${contains(var.tiles, "scdf") ? 1 : 0}" 28 | 29 | connection { 30 | host = "${var.opsman_host}" 31 | user = "ubuntu" 32 | private_key = "${var.opsman_ssh_key}" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /terraform/common/sso.tf: -------------------------------------------------------------------------------- 1 | data "template_file" "sso_configuration" { 2 | template = "${chomp(file("${path.module}/templates/tiles/sso_config.yml"))}" 3 | 4 | vars { 5 | az1 = "${var.azs[0]}" 6 | az2 = "${var.azs[1]}" 7 | az3 = "${var.azs[2]}" 8 | } 9 | } 10 | 11 | resource "null_resource" "setup_sso" { 12 | depends_on = ["null_resource.setup_pas"] 13 | 14 | provisioner "remote-exec" { 15 | inline = ["wrap install_tile pivotal_single_sign-on_service ${lookup(var.tile_versions, "sso")} Pivotal ${var.iaas} Pivotal_Single_Sign-On_Service"] 16 | } 17 | 18 | provisioner "file" { 19 | content = "${data.template_file.sso_configuration.rendered}" 20 | destination = "~/config/Pivotal_Single_Sign-On_Service-config.yml" 21 | } 22 | 23 | provisioner "remote-exec" { 24 | inline = ["wrap configure_tile Pivotal_Single_Sign-On_Service"] 25 | } 26 | 27 | count = "${contains(var.tiles, "sso") ? 1 : 0}" 28 | 29 | connection { 30 | host = "${var.opsman_host}" 31 | user = "ubuntu" 32 | private_key = "${var.opsman_ssh_key}" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /terraform/common/scs3.tf: -------------------------------------------------------------------------------- 1 | data "template_file" "scs3_configuration" { 2 | template = "${chomp(file("${path.module}/templates/tiles/scs3_config.yml"))}" 3 | 4 | vars { 5 | az1 = "${var.azs[0]}" 6 | az2 = "${var.azs[1]}" 7 | az3 = "${var.azs[2]}" 8 | } 9 | } 10 | 11 | resource "null_resource" "setup_scs3" { 12 | depends_on = ["null_resource.setup_pas"] 13 | 14 | provisioner "remote-exec" { 15 | inline = ["wrap install_tile p-spring-cloud-services ${lookup(var.tile_versions, "scs")} pivotal ${var.iaas} p_spring-cloud-services"] 16 | } 17 | 18 | provisioner "file" { 19 | content = "${data.template_file.scs3_configuration.rendered}" 20 | destination = "~/config/p_spring-cloud-services-config.yml" 21 | } 22 | 23 | provisioner "remote-exec" { 24 | inline = ["wrap configure_tile p_spring-cloud-services"] 25 | } 26 | 27 | count = "${contains(var.tiles, "scs3") || contains(var.tiles, "scs") ? 1 : 0}" 28 | 29 | connection { 30 | host = "${var.opsman_host}" 31 | user = "ubuntu" 32 | private_key = "${var.opsman_ssh_key}" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /terraform/common/antivirus.tf: -------------------------------------------------------------------------------- 1 | data "template_file" "antivirus_configuration" { 2 | template = "${chomp(file("${path.module}/templates/tiles/antivirus_config.yml"))}" 3 | 4 | vars { 5 | az1 = "${var.azs[0]}" 6 | az2 = "${var.azs[1]}" 7 | az3 = "${var.azs[2]}" 8 | } 9 | } 10 | 11 | resource "null_resource" "setup_antivirus" { 12 | depends_on = ["null_resource.setup_pas"] 13 | 14 | provisioner "remote-exec" { 15 | inline = ["wrap install_tile p-clamav-addon ${lookup(var.tile_versions, "antivirus")} p-antivirus-${lookup(var.tile_versions, "antivirus")} ${var.iaas} p-antivirus"] 16 | } 17 | 18 | provisioner "file" { 19 | content = "${data.template_file.antivirus_configuration.rendered}" 20 | destination = "~/config/p-antivirus-config.yml" 21 | } 22 | 23 | provisioner "remote-exec" { 24 | inline = ["wrap configure_tile p-antivirus"] 25 | } 26 | 27 | count = "${contains(var.tiles, "antivirus") ? 1 : 0}" 28 | 29 | connection { 30 | host = "${var.opsman_host}" 31 | user = "ubuntu" 32 | private_key = "${var.opsman_ssh_key}" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /terraform/common/gateway.tf: -------------------------------------------------------------------------------- 1 | data "template_file" "gateway_configuration" { 2 | template = "${chomp(file("${path.module}/templates/tiles/gateway_config.yml"))}" 3 | 4 | vars { 5 | az1 = "${var.azs[0]}" 6 | az2 = "${var.azs[1]}" 7 | az3 = "${var.azs[2]}" 8 | } 9 | } 10 | 11 | resource "null_resource" "setup_gateway" { 12 | depends_on = ["null_resource.setup_pas"] 13 | 14 | provisioner "remote-exec" { 15 | inline = ["wrap install_tile spring-cloud-gateway ${lookup(var.tile_versions, "gateway")} pivotal ${var.iaas} p_spring-cloud-gateway-service"] 16 | } 17 | 18 | provisioner "file" { 19 | content = "${data.template_file.gateway_configuration.rendered}" 20 | destination = "~/config/p_spring-cloud-gateway-service-config.yml" 21 | } 22 | 23 | provisioner "remote-exec" { 24 | inline = ["wrap configure_tile p_spring-cloud-gateway-service"] 25 | } 26 | 27 | count = "${contains(var.tiles, "gateway") ? 1 : 0}" 28 | 29 | connection { 30 | host = "${var.opsman_host}" 31 | user = "ubuntu" 32 | private_key = "${var.opsman_ssh_key}" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /terraform/aws/ssl.tf: -------------------------------------------------------------------------------- 1 | provider "acme" { 2 | server_url = "https://acme-v02.api.letsencrypt.org/directory" 3 | 4 | version = "~> 1.2.0" 5 | } 6 | 7 | resource "tls_private_key" "private_key" { 8 | algorithm = "RSA" 9 | } 10 | 11 | resource "acme_registration" "reg" { 12 | account_key_pem = "${tls_private_key.private_key.private_key_pem}" 13 | email_address = "none@paasify.org" 14 | } 15 | 16 | resource "acme_certificate" "certificate" { 17 | account_key_pem = "${acme_registration.reg.account_key_pem}" 18 | common_name = "pcf.${local.base_domain}" 19 | subject_alternative_names = ["*.apps.${local.base_domain}", "*.sys.${local.base_domain}", "*.uaa.sys.${local.base_domain}", "*.login.sys.${local.base_domain}"] 20 | 21 | dns_challenge { 22 | provider = "route53" 23 | 24 | config = { 25 | AWS_HOSTED_ZONE_ID = "${module.aws.dns_zone_id}" 26 | } 27 | } 28 | } 29 | 30 | locals { 31 | cert_full_chain = "${acme_certificate.certificate.certificate_pem}${acme_certificate.certificate.issuer_pem}" 32 | cert_key = "${acme_certificate.certificate.private_key_pem}" 33 | } -------------------------------------------------------------------------------- /terraform/gcp/variables.tf: -------------------------------------------------------------------------------- 1 | variable "env_name" { 2 | type = "string" 3 | } 4 | 5 | variable "project" { 6 | type = "string" 7 | } 8 | 9 | variable "region" { 10 | type = "string" 11 | default = "us-central1" 12 | } 13 | 14 | variable "buckets_location" { 15 | type = "string" 16 | default = "US" 17 | } 18 | 19 | variable "dns_suffix" { 20 | type = "string" 21 | } 22 | 23 | variable "dns_zone_name" { 24 | description = "The name of the Cloud DNS zone that managed the domain specified for dns_suffix" 25 | } 26 | 27 | variable "opsman_user" { 28 | type = "string" 29 | default = "admin" 30 | } 31 | 32 | variable "opsman_password" { 33 | type = "string" 34 | default = "" 35 | } 36 | 37 | variable "pivnet_token" { 38 | type = "string" 39 | } 40 | 41 | variable "compute_instance_count" { 42 | type = "string" 43 | default = "1" 44 | } 45 | 46 | variable "tiles" { 47 | type = "list" 48 | 49 | default = [] 50 | } 51 | 52 | variable "wavefront_token" { 53 | type = "string" 54 | default = "" 55 | } 56 | 57 | variable "auto_apply" { 58 | type = "string" 59 | default = "1" 60 | } -------------------------------------------------------------------------------- /terraform/azure/variables.tf: -------------------------------------------------------------------------------- 1 | variable "env_name" { 2 | type = "string" 3 | } 4 | 5 | variable "region" { 6 | type = "string" 7 | default = "West US 2" 8 | } 9 | 10 | variable "vpc_cidr" { 11 | type = "string" 12 | description = "Azure VNet CIDR (named to be consistent with other modules)" 13 | default = "10.0.0.0/16" 14 | } 15 | 16 | variable "client_id" {} 17 | 18 | variable "client_secret" {} 19 | 20 | variable "tenant_id" {} 21 | 22 | variable "subscription_id" {} 23 | 24 | variable "dns_suffix" { 25 | type = "string" 26 | } 27 | 28 | variable "opsman_user" { 29 | type = "string" 30 | default = "admin" 31 | } 32 | 33 | variable "opsman_password" { 34 | type = "string" 35 | default = "" 36 | } 37 | 38 | variable "pivnet_token" { 39 | type = "string" 40 | } 41 | 42 | variable "compute_instance_count" { 43 | type = "string" 44 | default = "1" 45 | } 46 | 47 | variable "tiles" { 48 | type = "list" 49 | 50 | default = [] 51 | } 52 | 53 | variable "wavefront_token" { 54 | type = "string" 55 | default = "" 56 | } 57 | 58 | variable "auto_apply" { 59 | type = "string" 60 | default = "1" 61 | } -------------------------------------------------------------------------------- /terraform/azure/templates/pas_config_ops.yml: -------------------------------------------------------------------------------- 1 | - type: replace 2 | path: /product-properties/.properties.system_blobstore? 3 | value: 4 | value: external_azure 5 | - type: replace 6 | path: /product-properties/.properties.system_blobstore.external_azure.buildpacks_container? 7 | value: 8 | value: ${buildpacks_container} 9 | - type: replace 10 | path: /product-properties/.properties.system_blobstore.external_azure.droplets_container? 11 | value: 12 | value: ${droplets_container} 13 | - type: replace 14 | path: /product-properties/.properties.system_blobstore.external_azure.resources_container? 15 | value: 16 | value: ${resources_container} 17 | - type: replace 18 | path: /product-properties/.properties.system_blobstore.external_azure.packages_container? 19 | value: 20 | value: ${packages_container} 21 | - type: replace 22 | path: /product-properties/.properties.system_blobstore.external_azure.access_key? 23 | value: 24 | value: 25 | secret: ${access_key} 26 | - type: replace 27 | path: /product-properties/.properties.system_blobstore.external_azure.account_name? 28 | value: 29 | value: ${account_name} -------------------------------------------------------------------------------- /terraform/common/metrics.tf: -------------------------------------------------------------------------------- 1 | data "template_file" "metrics_configuration" { 2 | template = "${file("${path.module}/templates/tiles/metrics_config.yml")}" 3 | 4 | vars { 5 | az1 = "${var.azs[0]}" 6 | az2 = "${var.azs[1]}" 7 | az3 = "${var.azs[2]}" 8 | mysql_instance_type = "${var.metrics_mysql_instance_type}" 9 | postgres_instance_type = "${var.metrics_postgres_instance_type}" 10 | } 11 | } 12 | 13 | resource "null_resource" "setup_metrics" { 14 | depends_on = ["null_resource.setup_pas"] 15 | 16 | provisioner "remote-exec" { 17 | inline = ["wrap install_tile apm ${lookup(var.tile_versions, "metrics")} pivotal ${var.iaas} apmPostgres"] 18 | } 19 | 20 | provisioner "file" { 21 | content = "${data.template_file.metrics_configuration.rendered}" 22 | destination = "~/config/apmPostgres-config.yml" 23 | } 24 | 25 | provisioner "remote-exec" { 26 | inline = ["wrap configure_tile apmPostgres"] 27 | } 28 | 29 | count = "${contains(var.tiles, "metrics") ? 1 : 0}" 30 | 31 | connection { 32 | host = "${var.opsman_host}" 33 | user = "ubuntu" 34 | private_key = "${var.opsman_ssh_key}" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /terraform/common/scripts/opsman/post_install_opsman.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | source ~/.om_profile 6 | 7 | bosh_director_ip=$1 8 | 9 | bosh_env=default 10 | 11 | # Setup BOSH CLI 12 | bosh alias-env $bosh_env -e $bosh_director_ip --ca-cert /var/tempest/workspaces/default/root_ca_certificate 13 | 14 | uaa_login_creds=$(om curl --path /api/v0/deployed/director/credentials/uaa_login_client_credentials -s | jq -r '.credential.value.password') 15 | uaa_admin_creds=$(om curl --path /api/v0/deployed/director/credentials/uaa_admin_user_credentials -s | jq -r '.credential.value.password') 16 | 17 | bosh_password=$(date +%s | sha256sum | base64 | head -c 16 ; echo) 18 | 19 | uaac target $bosh_director_ip:8443 --skip-ssl-validation 20 | 21 | uaac token owner get login admin -s $uaa_login_creds -p $uaa_admin_creds 22 | 23 | uaac client delete bosh-login || true 24 | uaac client add bosh-login --scope uaa.none --authorized_grant_types client_credentials --authorities bosh.admin -s $bosh_password 25 | 26 | cat << EOF > ~/.bosh_profile 27 | export BOSH_CLIENT=bosh-login 28 | export BOSH_CLIENT_SECRET=$bosh_password 29 | export BOSH_ENVIRONMENT=$bosh_env 30 | EOF 31 | 32 | source ~/.bosh_profile 33 | 34 | # Test authentication 35 | bosh vms -------------------------------------------------------------------------------- /terraform/aws/variables.tf: -------------------------------------------------------------------------------- 1 | variable "env_name" { 2 | type = "string" 3 | } 4 | 5 | variable "region" { 6 | type = "string" 7 | default = "us-west-2" 8 | } 9 | 10 | 11 | variable "dns_suffix" { 12 | type = "string" 13 | } 14 | 15 | variable "opsman_user" { 16 | type = "string" 17 | default = "admin" 18 | } 19 | 20 | variable "opsman_password" { 21 | type = "string" 22 | default = "" 23 | } 24 | 25 | variable "pivnet_token" { 26 | type = "string" 27 | } 28 | 29 | variable "compute_instance_count" { 30 | type = "string" 31 | default = "1" 32 | } 33 | 34 | variable "tiles" { 35 | type = "list" 36 | 37 | default = [] 38 | } 39 | 40 | variable "wavefront_token" { 41 | type = "string" 42 | default = "" 43 | } 44 | 45 | variable "auto_apply" { 46 | type = "string" 47 | default = "1" 48 | } 49 | 50 | variable "vpc_cidr" { 51 | type = "string" 52 | default = "10.0.0.0/16" 53 | } 54 | 55 | variable "encrypt_ebs" { 56 | type = "string" 57 | description = "Enable encryption of EBS volumes created by BOSH" 58 | default = "1" 59 | } 60 | 61 | variable "encrypt_pas_buckets" { 62 | type = "string" 63 | description = "Enable encryption of S3 buckets used to store PAS objects" 64 | default = "1" 65 | } 66 | -------------------------------------------------------------------------------- /terraform/gcp/ssl.tf: -------------------------------------------------------------------------------- 1 | provider "acme" { 2 | server_url = "https://acme-v02.api.letsencrypt.org/directory" 3 | 4 | version = "~> 1.1.0" 5 | } 6 | 7 | resource "tls_private_key" "private_key" { 8 | algorithm = "RSA" 9 | } 10 | 11 | resource "acme_registration" "reg" { 12 | account_key_pem = "${tls_private_key.private_key.private_key_pem}" 13 | email_address = "none@paasify.org" 14 | } 15 | 16 | resource "acme_certificate" "certificate" { 17 | depends_on = ["null_resource.ssl_blocker"] 18 | 19 | account_key_pem = "${acme_registration.reg.account_key_pem}" 20 | common_name = "${module.gcp.ops_manager_dns}" 21 | subject_alternative_names = ["*.${module.gcp.apps_domain}", "*.${module.gcp.sys_domain}", "*.uaa.${module.gcp.sys_domain}", "*.login.sys.${local.base_domain}"] 22 | 23 | dns_challenge { 24 | provider = "gcloud" 25 | 26 | config = { 27 | GCE_PROJECT = "${var.project}" 28 | GCE_PROPAGATION_TIMEOUT = "360" 29 | } 30 | } 31 | } 32 | 33 | resource "null_resource" "ssl_blocker" { 34 | provisioner "local-exec" { 35 | command = "echo ${module.gcp.dns_managed_zone} && sleep 120" 36 | } 37 | } 38 | 39 | locals { 40 | cert_full_chain = "${acme_certificate.certificate.certificate_pem}${acme_certificate.certificate.issuer_pem}" 41 | cert_key = "${acme_certificate.certificate.private_key_pem}" 42 | } -------------------------------------------------------------------------------- /terraform/common/healthwatch.tf: -------------------------------------------------------------------------------- 1 | data "template_file" "healthwatch_configuration" { 2 | template = "${chomp(file("${path.module}/templates/tiles/healthwatch_config.yml"))}" 3 | 4 | vars { 5 | az1 = "${var.azs[0]}" 6 | az2 = "${var.azs[1]}" 7 | az3 = "${var.azs[2]}" 8 | mysql_instance_type = "${var.healthwatch_mysql_instance_type}" 9 | forwarder_instance_type = "${var.healthwatch_forwarder_instance_type}" 10 | om_domain = "${var.opsman_host}" 11 | bosh_az = "${var.azs[0]}" 12 | } 13 | } 14 | 15 | resource "null_resource" "setup_healthwatch" { 16 | depends_on = ["null_resource.setup_pas"] 17 | 18 | provisioner "remote-exec" { 19 | inline = ["wrap install_tile p-healthwatch ${lookup(var.tile_versions, "healthwatch")} pivotal ${var.iaas}"] 20 | } 21 | 22 | provisioner "file" { 23 | content = "${data.template_file.healthwatch_configuration.rendered}" 24 | destination = "~/config/p-healthwatch-config.yml" 25 | } 26 | 27 | provisioner "remote-exec" { 28 | inline = ["wrap configure_tile p-healthwatch"] 29 | } 30 | 31 | connection { 32 | host = "${var.opsman_host}" 33 | user = "ubuntu" 34 | private_key = "${var.opsman_ssh_key}" 35 | } 36 | 37 | count = "${contains(var.tiles, "healthwatch") ? 1 : 0}" 38 | } 39 | -------------------------------------------------------------------------------- /terraform/azure/ssl.tf: -------------------------------------------------------------------------------- 1 | provider "acme" { 2 | server_url = "https://acme-v02.api.letsencrypt.org/directory" 3 | 4 | version = "~> 1.2.0" 5 | } 6 | 7 | resource "tls_private_key" "private_key" { 8 | algorithm = "RSA" 9 | } 10 | 11 | resource "acme_registration" "reg" { 12 | account_key_pem = "${tls_private_key.private_key.private_key_pem}" 13 | email_address = "none@paasify.org" 14 | } 15 | 16 | resource "acme_certificate" "certificate" { 17 | account_key_pem = "${acme_registration.reg.account_key_pem}" 18 | common_name = "pcf.${local.base_domain}" 19 | subject_alternative_names = ["*.apps.${local.base_domain}", "*.sys.${local.base_domain}", "*.uaa.sys.${local.base_domain}", "*.login.sys.${local.base_domain}"] 20 | 21 | dns_challenge { 22 | provider = "azure" 23 | 24 | config = { 25 | AZURE_CLIENT_ID = "${var.client_id}" 26 | AZURE_CLIENT_SECRET = "${var.client_secret}" 27 | AZURE_SUBSCRIPTION_ID = "${var.subscription_id}" 28 | AZURE_TENANT_ID = "${var.tenant_id}" 29 | AZURE_RESOURCE_GROUP = "${module.azure.pcf_resource_group_name}" 30 | } 31 | } 32 | 33 | depends_on = ["azurerm_dns_ns_record.test"] 34 | } 35 | 36 | locals { 37 | cert_full_chain = "${acme_certificate.certificate.certificate_pem}${acme_certificate.certificate.issuer_pem}" 38 | cert_key = "${acme_certificate.certificate.private_key_pem}" 39 | } 40 | -------------------------------------------------------------------------------- /terraform/azure/templates/opsman_config_ops.yml: -------------------------------------------------------------------------------- 1 | - type: replace 2 | path: /networks-configuration? 3 | value: 4 | icmp_checks_enabled: false 5 | networks: 6 | - name: management 7 | subnets: 8 | ${management_subnets} 9 | - name: pas 10 | subnets: 11 | ${pas_subnets} 12 | - name: services 13 | subnets: 14 | ${services_subnets} 15 | 16 | - type: replace 17 | path: /properties-configuration/iaas_configuration?/subscription_id? 18 | value: ${subscription_id} 19 | 20 | - type: replace 21 | path: /properties-configuration/iaas_configuration?/tenant_id? 22 | value: ${tenant_id} 23 | 24 | - type: replace 25 | path: /properties-configuration/iaas_configuration?/client_id? 26 | value: ${client_id} 27 | 28 | - type: replace 29 | path: /properties-configuration/iaas_configuration?/client_secret? 30 | value: ${client_secret} 31 | 32 | - type: replace 33 | path: /properties-configuration/iaas_configuration?/resource_group_name? 34 | value: ${resource_group_name} 35 | 36 | - type: replace 37 | path: /properties-configuration/iaas_configuration?/bosh_storage_account_name? 38 | value: ${bosh_storage_account_name} 39 | 40 | - type: replace 41 | path: /properties-configuration/iaas_configuration?/ssh_public_key? 42 | value: "${ssh_public_key}" 43 | 44 | - type: replace 45 | path: /properties-configuration/iaas_configuration?/ssh_private_key? 46 | value: "${ssh_private_key}" -------------------------------------------------------------------------------- /terraform/common/templates/tiles/mysql_config.yml: -------------------------------------------------------------------------------- 1 | product-name: pivotal-mysql 2 | product-properties: 3 | .properties.plan1_selector.single_node.az_multi_select: 4 | value: 5 | - ${az1} 6 | - ${az2} 7 | - ${az3} 8 | .properties.plan2_selector.single_node.az_multi_select: 9 | value: 10 | - ${az1} 11 | - ${az2} 12 | - ${az3} 13 | .properties.plan3_selector.single_node.az_multi_select: 14 | value: 15 | - ${az1} 16 | - ${az2} 17 | - ${az3} 18 | .properties.syslog_migration_selector: 19 | value: disabled 20 | .properties.backups_selector: 21 | value: SCP Backups 22 | .properties.backups_selector.scp.user: 23 | value: 'null' 24 | .properties.backups_selector.scp.key: 25 | value: 26 | secret: 'null' 27 | .properties.backups_selector.scp.server: 28 | value: 'null' 29 | .properties.backups_selector.scp.destination: 30 | value: 'null' 31 | .properties.backups_selector.scp.port: 32 | value: '1234' 33 | .properties.backups_selector.scp.cron_schedule: 34 | value: 0 0 5 31 2 ? 35 | .properties.global_recipient_email: 36 | value: none@localhost 37 | .properties.deprecated_bindings_string: 38 | value: X 39 | network-properties: 40 | network: 41 | name: pas 42 | service_network: 43 | name: services 44 | other_availability_zones: 45 | - name: ${az1} 46 | - name: ${az2} 47 | - name: ${az3} 48 | singleton_availability_zone: 49 | name: ${az1} -------------------------------------------------------------------------------- /terraform/common/templates/tiles/pas_config.yml: -------------------------------------------------------------------------------- 1 | product-name: cf 2 | product-properties: 3 | .cloud_controller.system_domain: 4 | value: ${sys_domain} 5 | .cloud_controller.apps_domain: 6 | value: ${apps_domain} 7 | .ha_proxy.skip_cert_verify: 8 | value: true 9 | .properties.haproxy_forward_tls: 10 | value: disable 11 | .properties.logger_endpoint_port: 12 | value: ${logger_endpoint_port} 13 | .properties.security_acknowledgement: 14 | value: X 15 | .mysql_monitor.recipient_email: 16 | value: nobody@localhost 17 | .properties.credhub_internal_provider_keys: 18 | value: 19 | - name: default 20 | primary: true 21 | key: 22 | secret: askdnasoidansfiopanskflasf 23 | .properties.networking_poe_ssl_certs: 24 | value: 25 | - certificate: 26 | cert_pem: ${poe_cert} 27 | private_key_pem: ${poe_private_key} 28 | name: default 29 | .uaa.service_provider_key_credentials: 30 | value: 31 | cert_pem: ${uaa_cert} 32 | private_key_pem: ${uaa_private_key} 33 | network-properties: 34 | network: 35 | name: pas 36 | other_availability_zones: 37 | - name: ${az1} 38 | - name: ${az2} 39 | - name: ${az3} 40 | singleton_availability_zone: 41 | name: ${az1} 42 | resource-config: 43 | control: 44 | elb_names: 45 | - ${ssh_elb_name} 46 | router: 47 | elb_names: [${web_elb_names}] 48 | compute: 49 | instances: ${compute_instances} 50 | blobstore: 51 | instances: 0 52 | mysql_monitor: 53 | instances: 0 54 | -------------------------------------------------------------------------------- /terraform/gcp/templates/pas_config_ops.yml: -------------------------------------------------------------------------------- 1 | - type: replace 2 | path: /product-properties/.properties.system_blobstore? 3 | value: 4 | value: external_gcs_service_account 5 | - type: replace 6 | path: /product-properties/.properties.system_blobstore.external_gcs_service_account.buildpacks_bucket? 7 | value: 8 | value: ${buildpacks_bucket} 9 | - type: replace 10 | path: /product-properties/.properties.system_blobstore.external_gcs_service_account.droplets_bucket? 11 | value: 12 | value: ${droplets_bucket} 13 | - type: replace 14 | path: /product-properties/.properties.system_blobstore.external_gcs_service_account.resources_bucket? 15 | value: 16 | value: ${resources_bucket} 17 | - type: replace 18 | path: /product-properties/.properties.system_blobstore.external_gcs_service_account.packages_bucket? 19 | value: 20 | value: ${packages_bucket} 21 | - type: replace 22 | path: /product-properties/.properties.system_blobstore.external_gcs_service_account.backup_bucket? 23 | value: 24 | value: ${backup_bucket} 25 | - type: replace 26 | path: /product-properties/.properties.system_blobstore.external_gcs_service_account.project_id? 27 | value: 28 | value: ${project} 29 | - type: replace 30 | path: /product-properties/.properties.system_blobstore.external_gcs_service_account.service_account_email? 31 | value: 32 | value: ${service_account_email} 33 | - type: replace 34 | path: /product-properties/.properties.system_blobstore.external_gcs_service_account.service_account_json_key? 35 | value: 36 | value: ${service_account_key} -------------------------------------------------------------------------------- /codebuild/bin/cloud.sh: -------------------------------------------------------------------------------- 1 | # Include to validate cloud specified 2 | 3 | if [ -z "$cloud" ]; then 4 | echo "Error: Cloud provider must be specified" 5 | exit 1 6 | fi 7 | 8 | export CLOUD_TF_DIR=$CODEBUILD_SRC_DIR/terraform/$cloud 9 | 10 | if [ ! -d "$CLOUD_TF_DIR" ]; then 11 | echo "Cloud $cloud does not appear to be supported (Terraform directory not found)" 12 | exit 1 13 | fi 14 | 15 | if [ "$cloud" = "gcp" ]; then 16 | echo "Bootstrapping Google Cloud..." 17 | 18 | export TF_VAR_project=$(aws ssm get-parameter --name /paasify/gcp/project_name | jq '.Parameter.Value' -r) 19 | export TF_VAR_dns_zone_name=$(aws ssm get-parameter --name /paasify/gcp/dns_zone_name | jq '.Parameter.Value' -r) 20 | 21 | aws ssm get-parameter --name /paasify/gcp/auth.json --with-decryption | jq '.Parameter.Value' -r > /tmp/auth.json 22 | 23 | export GOOGLE_APPLICATION_CREDENTIALS=/tmp/auth.json 24 | fi 25 | 26 | if [ "$cloud" = "azure" ]; then 27 | echo "Bootstrapping Azure..." 28 | 29 | export TF_VAR_client_id=$(aws ssm get-parameter --name /paasify/azure/client_id | jq '.Parameter.Value' -r) 30 | export TF_VAR_client_secret=$(aws ssm get-parameter --name /paasify/azure/client_secret --with-decryption | jq '.Parameter.Value' -r) 31 | export TF_VAR_subscription_id=$(aws ssm get-parameter --name /paasify/azure/subscription_id | jq '.Parameter.Value' -r) 32 | export TF_VAR_tenant_id=$(aws ssm get-parameter --name /paasify/azure/tenant_id | jq '.Parameter.Value' -r) 33 | fi 34 | 35 | export TF_VAR_pivnet_token=$(aws ssm get-parameter --name /paasify/pivnet_token --with-decryption | jq '.Parameter.Value' -r) -------------------------------------------------------------------------------- /terraform/aws/iam.tf: -------------------------------------------------------------------------------- 1 | resource "aws_iam_access_key" "key" { 2 | user = "${aws_iam_user.user.name}" 3 | 4 | provisioner "local-exec" { 5 | command = "sleep 20" 6 | } 7 | 8 | depends_on = [ "aws_iam_user_policy.policy" ] 9 | } 10 | 11 | resource "aws_iam_user" "user" { 12 | name = "paasify-${var.env_name}" 13 | path = "/paasify/" 14 | } 15 | 16 | resource "aws_iam_user_policy" "policy" { 17 | name = "paasify-${var.env_name}" 18 | user = "${aws_iam_user.user.name}" 19 | 20 | policy = < ~/.om_profile 121 | export OM_TARGET=https://$om_domain 122 | export OM_USERNAME=admin 123 | export OM_PASSWORD=$om_password 124 | export PIVNET_TOKEN=$pivnet_api_token 125 | EOF 126 | 127 | cat << EOF >> ~/.bash_profile 128 | source ~/.om_profile 129 | EOF 130 | 131 | source ~/.om_profile 132 | 133 | # Setup config dir 134 | if [ ! -d ~/config ]; then 135 | mkdir -p ~/config 136 | fi 137 | 138 | sleep 20 139 | 140 | # Configure authentication in OpsMan 141 | echo 'Configuring OpsMan authentication...' 142 | om configure-authentication -u $OM_USERNAME -p $OM_PASSWORD -dp $OM_PASSWORD 143 | 144 | 145 | # Set up for file locks 146 | sudo touch /var/run/paasify.lock 147 | sudo chown ubuntu:ubuntu /var/run/paasify.lock -------------------------------------------------------------------------------- /terraform/aws/opsman.tf: -------------------------------------------------------------------------------- 1 | data "template_file" "om_management_subnets" { 2 | template = "${chomp(file("${path.module}/templates/opsman_subnet_config.yml"))}" 3 | 4 | count = "3" 5 | 6 | vars { 7 | id = "${module.aws.management_subnet_ids[count.index]}" 8 | dns = "${cidrhost(var.vpc_cidr, 2)}" 9 | cidr = "${module.aws.management_subnet_cidrs[count.index]}" 10 | gateway = "${cidrhost(module.aws.management_subnet_cidrs[count.index], 1)}" 11 | reserved = "${cidrhost(module.aws.management_subnet_cidrs[count.index], 0) }-${cidrhost(module.aws.management_subnet_cidrs[count.index], 9) }" 12 | az = "${module.aws.management_subnet_availability_zones[count.index]}" 13 | } 14 | } 15 | 16 | data "template_file" "om_pas_subnets" { 17 | template = "${chomp(file("${path.module}/templates/opsman_subnet_config.yml"))}" 18 | 19 | count = "3" 20 | 21 | vars { 22 | id = "${module.aws.pas_subnet_ids[count.index]}" 23 | dns = "${cidrhost(var.vpc_cidr, 2)}" 24 | cidr = "${module.aws.pas_subnet_cidrs[count.index]}" 25 | gateway = "${cidrhost(module.aws.pas_subnet_cidrs[count.index], 1)}" 26 | reserved = "${cidrhost(module.aws.pas_subnet_cidrs[count.index], 0) }-${cidrhost(module.aws.pas_subnet_cidrs[count.index], 9) }" 27 | az = "${module.aws.management_subnet_availability_zones[count.index]}" 28 | } 29 | } 30 | 31 | data "template_file" "om_services_subnets" { 32 | template = "${chomp(file("${path.module}/templates/opsman_subnet_config.yml"))}" 33 | 34 | count = "3" 35 | 36 | vars { 37 | id = "${module.aws.services_subnet_ids[count.index]}" 38 | dns = "${cidrhost(var.vpc_cidr, 2)}" 39 | cidr = "${module.aws.services_subnet_cidrs[count.index]}" 40 | gateway = "${cidrhost(module.aws.services_subnet_cidrs[count.index], 1)}" 41 | reserved = "${cidrhost(module.aws.services_subnet_cidrs[count.index], 0) }-${cidrhost(module.aws.services_subnet_cidrs[count.index], 9) }" 42 | az = "${module.aws.management_subnet_availability_zones[count.index]}" 43 | } 44 | } 45 | 46 | data "template_file" "om_configuration" { 47 | template = "${chomp(file("${path.module}/templates/opsman_config_ops.yml"))}" 48 | 49 | vars { 50 | az1 = "${module.aws.management_subnet_availability_zones[0]}" 51 | az2 = "${module.aws.management_subnet_availability_zones[1]}" 52 | az3 = "${module.aws.management_subnet_availability_zones[2]}" 53 | 54 | management_subnets = "${join("\n", data.template_file.om_management_subnets.*.rendered)}" 55 | pas_subnets = "${join("\n", data.template_file.om_pas_subnets.*.rendered)}" 56 | services_subnets = "${join("\n", data.template_file.om_services_subnets.*.rendered)}" 57 | 58 | iam_instance_profile = "${module.aws.ops_manager_iam_instance_profile_name}" 59 | vpc_id = "${module.aws.vpc_id}" 60 | security_group = "${module.aws.vms_security_group_id}" 61 | key_pair_name = "${module.aws.ops_manager_ssh_public_key_name}" 62 | ssh_private_key = "${substr(local.ssh_private_key_encoded, 1, length(local.ssh_private_key_encoded)-2)}" 63 | region = "${var.region}" 64 | bucket_name = "${module.aws.ops_manager_bucket}" 65 | bucket_access_key_id = "${module.aws.ops_manager_iam_user_access_key}" 66 | bucket_secret_access_key = "${module.aws.ops_manager_iam_user_secret_key}" 67 | ebs_encryption = "${var.encrypt_ebs}" 68 | } 69 | } 70 | 71 | data "template_file" "om_vm_extension_web" { 72 | template = "${chomp(file("${path.module}/templates/vm_extension.yml"))}" 73 | 74 | vars { 75 | name = "web_lb_security_groups" 76 | target_groups = "${join(", ", module.aws.web_target_groups)}" 77 | sg_name = "web_lb_security_group" 78 | } 79 | } 80 | 81 | data "template_file" "om_vm_extension_ssh" { 82 | template = "${chomp(file("${path.module}/templates/vm_extension.yml"))}" 83 | 84 | vars { 85 | name = "ssh_lb_security_groups" 86 | target_groups = "${join(", ", module.aws.ssh_target_groups)}" 87 | sg_name = "ssh_lb_security_group" 88 | } 89 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PCF Paasify 2 | 3 | Installing Pivotal Application Service for quick setups can be more complicated than it should. The goal of this project is to allow you to complete an install of PAS, with optional tiles, with nothing more than Terraform installed locally. This is being essentially being exposed as 'PCF-as-a-Terraform-module' that is compatible across all supported public clouds. It is designed for short-term, non-production setups, and is not intended to provide a PAS setup that can be upgraded over long periods of time. 4 | 5 | Note: This project requires Terraform 0.11. 6 | 7 | If you need fire-and-forget mechanism that gives you predictable, disposable PCF environments (including many popular tiles) then this is for you. 8 | 9 | Take this example: 10 | 11 | ``` 12 | module "paasify" { 13 | source = "github.com/nthomson-pivotal/pcf-paasify//terraform/aws?ref=2.8" 14 | 15 | env_name = "paasify-test" 16 | dns_suffix = "aws.paasify.org" 17 | pivnet_token = "" 18 | 19 | tiles = ["mysql", "rabbit", "scs"] 20 | } 21 | ``` 22 | 23 | This will: 24 | - Install PAS 2.8 Small Footprint 25 | - Download, stage and configure the MySQL, RabbitMQ and Spring Cloud Services tiles 26 | - Wire up DNS so that its accessible at `paasify-test.aws.paasify.org` 27 | - Provision valid SSL certificates via Lets Encrypt for every common HTTPS endpoint 28 | - Allow you to cleanly tear down all infrastructure via `terraform destroy` 29 | - Perform all PivNet product downloads/uploads on the OpsMan VM for speed 30 | 31 | When the Terraform run completes there will be a fully working PCF PAS installation, with endpoint information available from Terraform outputs. 32 | 33 | ## What? 34 | 35 | Paasify is a set of super-opinionated Terraform scripts and supporting utilities for creating Pivotal Application Service foundations in the various supported IaaS providers. Where this utility excels in the ability to quickly setup PCF foundations for experiments or testing, and is great for scenarios that involve multiple foundations (multi-region or multi-cloud) as it is capable of creating these in a single command due to its composability. Other scenarios where it becomes useful are demos or proof-of-concept exercises where the focus is not on the installation procedure itself. 36 | 37 | The main features provided by the install are: 38 | 39 | - Small Footprint PAS 40 | - Install (incrementally if needed) specific tiles, automatically adding their supported stemcells 41 | - DNS that is configured without intervention 42 | - Valid SSL certificates for both OpsManager and PAS via Lets Encrypt 43 | - All default passwords are randomly generated 44 | - Working configuration for `cf logs` and `cf ssh` 45 | - Leverages cloud-provided blobstores by default 46 | - AWS CloudFormation configurations that provide CodeBuild job templates 47 | 48 | Some of the opinions of this project are: 49 | 50 | - A very specific DNS naming convention and control structure (see below for more details) 51 | - SSL certificates generated through LetsEncrypt via DNS verification 52 | - Default to absolute minimal resource configurations 53 | - As much as possible inherited from core PCF terraforming-* projects for each IaaS 54 | - Pin to known working versions of anything everywhere possible at the expense of the latest version 55 | 56 | Pending work includes: 57 | 58 | - Support additional tiles (PCC, SSO) 59 | - Pre-install cloud-specific service brokers 60 | - Install cloud-specific logging nozzles where available 61 | - Provide easy hooks to AWS SES for outbound email support (notifications etc) 62 | - Ability to specify syslog output for log aggregation (Papertrail etc) 63 | - Support optionally hard-coded OpsManager password instead of always randomly generating 64 | 65 | ## Why Not Concourse/PCF-Pipelines/PCF-Automation? 66 | 67 | This project is *NOT* intended to illustrate best practices with regards to installing PCF as advocated by Pivotal, but rather to support a very specific set of use-cases: 68 | 69 | - I often require PCF installations on my own cloud infrastructure on short notice (demo, experiments) 70 | - I neither want to build/maintain/pay for a Concourse setup nor start/stop one whenever I need it to reduce costs 71 | - PCF Pipelines and Automation are architected for larger-scale, long-lived systems, whereas I have optimized for a "one-click" setup with less regard for upgradability 72 | 73 | Leveraging AWS CodeBuild provides a serverless solution that allows aggressive cost control with a fire-and-forget interface. 74 | 75 | ## Usage 76 | 77 | There are several ways to run Paasify. 78 | 79 | ### Running with Terraform 80 | 81 | Paasify can be run directly as a Terraform module. The pre-requisites for this are: 82 | 83 | - Terraform must be installed locally 84 | - You must have authentication setup locally for the appropriate cloud that Terraform leverage (see Terraform provider docs) 85 | 86 | The following example Terraform configuration imports Paasify as a module targeting AWS: 87 | 88 | ``` 89 | module "paasify" { 90 | source = "github.com/nthomson-pivotal/pcf-paasify//terraform/aws" 91 | 92 | env_name = "test-env" 93 | dns_suffix = "aws.paasify.org" 94 | pivnet_token = "" 95 | } 96 | ``` 97 | 98 | Currently the implementation only allows authentication already configured appropriately on the host machine. Explicitly passing in credentials for the various Terraform cloud providers is not supported. 99 | 100 | ### Running using AWS CodeBuild 101 | 102 | This repository contains a CloudFormation template for creating AWS CodeBuild projects to build and destroy a setup. In order to create a CloudFormation stack using this template execute a command like the following: 103 | 104 | ```aws cloudformation create-stack --stack-name test-env --template-body file://codebuild/cloudformation/cf.json --parameters ParameterKey=EnvName,ParameterValue=test-env ParameterKey=DnsSuffix,ParameterValue=aws.paasify.org ParameterKey=PivnetToken,ParameterValue= ParameterKey=Cloud,ParameterValue=aws``` 105 | 106 | ### Cleaning Up 107 | 108 | If you're having issues cleaning up, take a look at the [leftovers](https://github.com/genevieve/leftovers) utility. 109 | 110 | **WARNING: Use this utility with care** 111 | 112 | For example, if you created an environment with a name of `test-env` then you could run the following command: 113 | 114 | ```leftovers --filter test-env``` 115 | 116 | Its recommended to use the `--dry-run` option first to ensure you don't delete anything unintended. 117 | 118 | ## Reference 119 | 120 | The following sections provide more detailed information regarding the system which is created by the scripts 121 | 122 | ### Tiles 123 | 124 | The following table lists all tiles that can be automatically installed, along with the name that should be put in the `tiles` parameter: 125 | 126 | | Tile | Version | Name | 127 | |------|-----|-----| 128 | | MySQL | 2.7.2 | `mysql` | 129 | | RabbitMQ | 1.17.1 | `rabbit` | 130 | | Redis | 2.2.1 | `redis` | 131 | | Pivotal Cloud Cache | 1.8.0 | `pcc` | 132 | | Spring Cloud Services 3 | 3.0.5 | `scs3` or `scs` | 133 | | Spring Cloud Data Flow | 1.6.1 | `scdf` | 134 | | Metrics | 1.6.1 | `metrics` | 135 | | Healthwatch | 1.7.0 | `healthwatch` | 136 | | Credhub Service Broker | 1.3.2 | `credhub` | 137 | | Pivotal Anti-Virus | 2.6.1 | `antivirus` | 138 | 139 | The latest stemcell supported by each tile will automatically be uploaded to OpsManager. 140 | 141 | ### DNS 142 | 143 | Paasify makes some basic assumptions about DNS that allow it to function: 144 | - The DNS entries for each public cloud are managed by their respective DNS services (AWS Route53, GCP Cloud DNS, Azure DNS) 145 | - You have already set up delegation from your root domain registrar to each public cloud you wish to use 146 | - Paasify is free to create DNS entries in whatever delegated domain it is to create environments in 147 | 148 | For example, this is the structure of the domains used to test Paasify: 149 | 150 | ![architecture](docs/dns-structure.png) 151 | 152 | The DNS for `paasify.org` itself is managed in GoDaddy, with a sub-domain for each cloud delegated to that clouds DNS service. 153 | 154 | ### SSL Certificates 155 | 156 | Valid SSL certificates are generated by using Lets Encrypt via the DNS challenge protocol. If the DNS is setup as documented above then this will work transparently and will require no further input. 157 | 158 | A single SSL certificate is generated for all necessary SANs, which results in several wildcard entries. 159 | -------------------------------------------------------------------------------- /codebuild/cloudformation/cf.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion":"2010-09-09", 3 | "Description":"Deploys PAS", 4 | "Parameters":{ 5 | "EnvName":{ 6 | "Description":"Environment name", 7 | "Type":"String" 8 | }, 9 | "DnsSuffix":{ 10 | "Description":"DNS suffix (ie. aws.paasify.org)", 11 | "Type":"String" 12 | }, 13 | "Tiles": { 14 | "Description": "Comma-delimited list of tiles that should be pre-installed", 15 | "Type": "CommaDelimitedList", 16 | "Default": "" 17 | }, 18 | "SourceRepo": { 19 | "Description": "Paasify source repository", 20 | "Type":"String", 21 | "Default": "https://github.com/nthomson-pivotal/pcf-paasify" 22 | }, 23 | "SourceBranch":{ 24 | "Description":"Use this branch in above repo (overrides tag)", 25 | "Type":"String", 26 | "Default": "" 27 | }, 28 | "SourceTag":{ 29 | "Description":"Use this tag in above repo", 30 | "Type":"String", 31 | "Default": "" 32 | }, 33 | "Cloud" : { 34 | "Type" : "String", 35 | "Default" : "aws", 36 | "AllowedValues" : ["aws", "gcp", "azure"], 37 | "Description" : "The cloud provider on which the environment will be provisioned" 38 | } 39 | }, 40 | "Resources":{ 41 | "S3StateAndSoftware":{ 42 | "Type":"AWS::S3::Bucket", 43 | "Properties":{ 44 | "BucketName":{ 45 | "Fn::Join":[ 46 | "-", 47 | [ 48 | { 49 | "Ref":"AWS::StackName" 50 | }, 51 | { 52 | "Ref":"AWS::AccountId" 53 | }, 54 | "paasify-state" 55 | ] 56 | ] 57 | } 58 | } 59 | }, 60 | "CodeBuildRole":{ 61 | "Type":"AWS::IAM::Role", 62 | "Properties":{ 63 | "RoleName":{ 64 | "Fn::Join":[ 65 | "-", 66 | [ 67 | { 68 | "Ref":"AWS::StackName" 69 | }, 70 | "codebuild" 71 | ] 72 | ] 73 | }, 74 | "AssumeRolePolicyDocument":{ 75 | "Version":"2012-10-17", 76 | "Statement":[ 77 | { 78 | "Effect":"Allow", 79 | "Principal":{ 80 | "Service":[ 81 | "codebuild.amazonaws.com" 82 | ] 83 | }, 84 | "Action":[ 85 | "sts:AssumeRole" 86 | ] 87 | } 88 | ] 89 | }, 90 | "Path":"/", 91 | "Policies":[ 92 | { 93 | "PolicyName":"adminaccess", 94 | "PolicyDocument":{ 95 | "Version":"2012-10-17", 96 | "Statement":[ 97 | { 98 | "Action":"*", 99 | "Effect":"Allow", 100 | "Resource":"*" 101 | }, 102 | { 103 | "Effect": "Allow", 104 | "Resource": [ 105 | { 106 | "Fn::Join":[ 107 | ":", 108 | [ 109 | "arn:aws:logs", 110 | { 111 | "Ref":"AWS::Region" 112 | }, 113 | { 114 | "Ref":"AWS::AccountId" 115 | }, 116 | "log-group:/aws/codebuild/*" 117 | ] 118 | ] 119 | }, 120 | { 121 | "Fn::Join":[ 122 | ":", 123 | [ 124 | "arn:aws:logs", 125 | { 126 | "Ref":"AWS::Region" 127 | }, 128 | { 129 | "Ref":"AWS::AccountId" 130 | }, 131 | "log-group:/aws/codebuild/*:*" 132 | ] 133 | ] 134 | } 135 | ], 136 | "Action": [ 137 | "logs:CreateLogGroup", 138 | "logs:CreateLogStream", 139 | "logs:PutLogEvents" 140 | ] 141 | }, 142 | { 143 | "Effect": "Allow", 144 | "Resource": [ 145 | { 146 | "Fn::Join":[ 147 | "-", 148 | [ 149 | "arn:aws:s3:::codepipeline", 150 | { 151 | "Ref":"AWS::Region" 152 | }, 153 | "*" 154 | ] 155 | ] 156 | } 157 | ], 158 | "Action": [ 159 | "s3:PutObject", 160 | "s3:GetObject", 161 | "s3:GetObjectVersion", 162 | "s3:GetBucketAcl", 163 | "s3:GetBucketLocation" 164 | ] 165 | } 166 | ] 167 | } 168 | } 169 | ] 170 | } 171 | }, 172 | "BuildProject":{ 173 | "Type":"AWS::CodeBuild::Project", 174 | "Properties":{ 175 | "Name":{ 176 | "Fn::Join":[ 177 | "-", 178 | [ 179 | { 180 | "Ref":"AWS::StackName" 181 | }, 182 | "build" 183 | ] 184 | ] 185 | }, 186 | "Description":"Deploy PAS", 187 | "ServiceRole":{ 188 | "Ref":"CodeBuildRole" 189 | }, 190 | "Artifacts":{ 191 | "Type":"NO_ARTIFACTS" 192 | }, 193 | "TimeoutInMinutes": 360, 194 | "Environment":{ 195 | "Type":"LINUX_CONTAINER", 196 | "ComputeType":"BUILD_GENERAL1_SMALL", 197 | "Image":"aws/codebuild/docker:17.09.0", 198 | "EnvironmentVariables":[ 199 | { 200 | "Name":"state_bucket", 201 | "Value":{ 202 | "Ref":"S3StateAndSoftware" 203 | } 204 | }, 205 | { 206 | "Name":"env", 207 | "Value":{ 208 | "Ref":"EnvName" 209 | } 210 | }, 211 | { 212 | "Name":"dns_suffix", 213 | "Value":{ 214 | "Ref":"DnsSuffix" 215 | } 216 | }, 217 | { 218 | "Name":"tiles", 219 | "Value":{ 220 | "Fn::Sub": [ "\"${JoinedTiles}\"", { "JoinedTiles": {"Fn::Join" : [ "\",\"", { "Ref":"Tiles" } ] }} ] 221 | } 222 | }, 223 | { 224 | "Name":"cloud", 225 | "Value":{ 226 | "Ref":"Cloud" 227 | } 228 | }, 229 | { 230 | "Name":"command", 231 | "Value": "apply" 232 | }, 233 | { 234 | "Name":"auto_apply", 235 | "Value": "1" 236 | }, 237 | { 238 | "Name":"branch", 239 | "Value":{ 240 | "Ref":"SourceBranch" 241 | } 242 | }, 243 | { 244 | "Name": "tag", 245 | "Value":{ 246 | "Ref":"SourceTag" 247 | } 248 | }, 249 | { 250 | "Name": "state_key", 251 | "Value": "default" 252 | } 253 | ] 254 | }, 255 | "Source":{ 256 | "Location": { "Ref" : "SourceRepo" }, 257 | "Type":"GITHUB", 258 | "BuildSpec": "codebuild/buildspec.yml" 259 | } 260 | } 261 | }, 262 | "DestroyProject":{ 263 | "Type":"AWS::CodeBuild::Project", 264 | "Properties":{ 265 | "Name":{ 266 | "Fn::Join":[ 267 | "-", 268 | [ 269 | { 270 | "Ref":"AWS::StackName" 271 | }, 272 | "destroy" 273 | ] 274 | ] 275 | }, 276 | "Description":"Destroy PAS", 277 | "ServiceRole":{ 278 | "Ref":"CodeBuildRole" 279 | }, 280 | "Artifacts":{ 281 | "Type":"NO_ARTIFACTS" 282 | }, 283 | "TimeoutInMinutes": 60, 284 | "Environment":{ 285 | "Type":"LINUX_CONTAINER", 286 | "ComputeType":"BUILD_GENERAL1_SMALL", 287 | "Image":"aws/codebuild/docker:17.09.0", 288 | "EnvironmentVariables":[ 289 | { 290 | "Name":"state_bucket", 291 | "Value":{ 292 | "Ref":"S3StateAndSoftware" 293 | } 294 | }, 295 | { 296 | "Name":"env", 297 | "Value":{ 298 | "Ref":"EnvName" 299 | } 300 | }, 301 | { 302 | "Name":"dns_suffix", 303 | "Value":{ 304 | "Ref":"DnsSuffix" 305 | } 306 | }, 307 | { 308 | "Name":"cloud", 309 | "Value":{ 310 | "Ref":"Cloud" 311 | } 312 | }, 313 | { 314 | "Name":"branch", 315 | "Value":{ 316 | "Ref":"SourceBranch" 317 | } 318 | }, 319 | { 320 | "Name": "tag", 321 | "Value":{ 322 | "Ref":"SourceTag" 323 | } 324 | }, 325 | { 326 | "Name": "state_key", 327 | "Value": "default" 328 | } 329 | ] 330 | }, 331 | "Source":{ 332 | "Location": { "Ref" : "SourceRepo" }, 333 | "Type":"GITHUB", 334 | "BuildSpec": "codebuild/destroy-buildspec.yml" 335 | } 336 | } 337 | } 338 | } 339 | } 340 | --------------------------------------------------------------------------------