From 1740f363666e3138c6e9c4ec182b2a564588a9fa Mon Sep 17 00:00:00 2001 From: Ned Petrov Date: Tue, 25 Aug 2026 08:11:22 +0300 Subject: [PATCH 01/25] Run OpenStack CPI CI on community Concourse via DevStack on GCP - add gcp-devstack terraform module: N2 VM (nested-virt) on the bosh-concourse VPC that installs DevStack on boot and applies a SNAT hairpin so shared Concourse workers can reach DevStack floating IPs - add create-devstack/destroy-devstack jobs; run lifecycle/bats in-container on shared workers (drop the openstack worker tag) - resolve the external network by name; serve DevStack over HTTP (no per-run CA) - migrate credentials to community CredHub names; inline deterministic config; single openstack_ci_user credential flowed to the VM via metadata - add wait-for-devstack readiness task; fix concourse_external_network_cidr --- .../bosh-ecosystem-openstack-wallaby-ca.pem | 21 -- ci/ops_files/timeouts.yml | 1 - ci/pipeline.yml | 207 ++++++++-------- ci/tasks/deploy-manual-networking.sh | 2 +- ci/tasks/wait-for-devstack.sh | 22 ++ ci/tasks/wait-for-devstack.yml | 8 + ci/tasks/write-ca-file.sh | 10 - ci/tasks/write-ca-file.yml | 14 -- ci/terraform/ci/bats-manual/bats.tf | 10 +- ci/terraform/ci/gcp-devstack/.gitignore | 5 + .../ci/gcp-devstack/install-devstack.sh | 224 ++++++++++++++++++ ci/terraform/ci/gcp-devstack/main.tf | 189 +++++++++++++++ ci/terraform/ci/gcp-devstack/versions.tf | 9 + ci/terraform/ci/lifecycle/lifecycle.tf | 10 +- 14 files changed, 575 insertions(+), 157 deletions(-) delete mode 100644 ci/ca_certs/bosh-ecosystem-openstack-wallaby-ca.pem create mode 100644 ci/tasks/wait-for-devstack.sh create mode 100644 ci/tasks/wait-for-devstack.yml delete mode 100755 ci/tasks/write-ca-file.sh delete mode 100644 ci/tasks/write-ca-file.yml create mode 100644 ci/terraform/ci/gcp-devstack/.gitignore create mode 100644 ci/terraform/ci/gcp-devstack/install-devstack.sh create mode 100644 ci/terraform/ci/gcp-devstack/main.tf create mode 100644 ci/terraform/ci/gcp-devstack/versions.tf diff --git a/ci/ca_certs/bosh-ecosystem-openstack-wallaby-ca.pem b/ci/ca_certs/bosh-ecosystem-openstack-wallaby-ca.pem deleted file mode 100644 index 6a502c25e..000000000 --- a/ci/ca_certs/bosh-ecosystem-openstack-wallaby-ca.pem +++ /dev/null @@ -1,21 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDazCCAlOgAwIBAgIUN0hGX4X0rDRs0U/q678RS9qEkaMwDQYJKoZIhvcNAQEL -BQAwPTE7MDkGA1UEAxMyVmF1bHQgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkg -KGNoYXJtLXBraS1sb2NhbCkwHhcNMjUwMjEyMDQ0NzUwWhcNMzUwMjEwMDM0ODE5 -WjA9MTswOQYDVQQDEzJWYXVsdCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAo -Y2hhcm0tcGtpLWxvY2FsKTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB -ALe+TpmVOAHhhDut5EY8F7zFlMl6gmaoDUNVCW8dtnuCtpPtlMbmUSoMJB06ldFT -9FqUosJr7psgwH5UqiPNSG+sbIsU32y+U/bhC1k1EYFPTAPJVfPaktnhDkueTFjO -l/LvdKkP+Oyv74/5+pVoT3bEOHAafvId2A1Ye+ofKNlFel5jUFHTbHATpz9FIHN8 -j/QumPvy7JqaXYH6TrGUlXWMVf4i5qsghy6uutfEGmGMskNdOImuDFNa3+7sF1m7 -MHcP3gZSJvmwz6nXmUKtrBUqNLEGNjbbK5Cx44Igmh+y6lSOctjzj2+wfa4NyADr -sLXPcrC+ZNSVjU3+jKqMzekCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgEGMA8GA1Ud -EwEB/wQFMAMBAf8wHQYDVR0OBBYEFNTAYEm4BuY5PGJUs8/2oxaPnxV0MB8GA1Ud -IwQYMBaAFNTAYEm4BuY5PGJUs8/2oxaPnxV0MA0GCSqGSIb3DQEBCwUAA4IBAQAx -483kgo7iY7eCWpqjBkkrtqoQJVVAbYHQb/JU4j0ogezqbjY2l/q3r0+QFOqxwte9 -235Zb1zIxJi0M+mtPV1OEv1cD7A6YlbnT3ofb35/Hjsi9HWKJJz9s2bhwbYwVwF6 -ucga3Adsxip545D+jo0Of57FRLtEEG9XroW+HvyUIcTrNE/2KWUzsatFud4PqVk+ -RhL9qe+fq/6WoCQ7quVE15ob6XTjkDQkmtQ14mpfLkFQkA/+JraOFC5P4M2TMsaR -YFcN6bIDSb73l3ldJfjHJQCNHYlCgOuTrs7q2HN1s9vYOrwDM1z1BA4Iq5OAEHTm -Xt1nte+8zO2W0+6/2h3c ------END CERTIFICATE----- diff --git a/ci/ops_files/timeouts.yml b/ci/ops_files/timeouts.yml index cb722d771..1eb25443f 100644 --- a/ci/ops_files/timeouts.yml +++ b/ci/ops_files/timeouts.yml @@ -12,7 +12,6 @@ connect_timeout: ((openstack_connection_timeout)) read_timeout: ((openstack_read_timeout)) write_timeout: ((openstack_write_timeout)) - ca_cert: ((openstack_ca_cert)) - type: replace path: /cloud_provider/properties/openstack/connection_options? diff --git a/ci/pipeline.yml b/ci/pipeline.yml index 1d1995b02..44072ec78 100644 --- a/ci/pipeline.yml +++ b/ci/pipeline.yml @@ -35,82 +35,94 @@ jobs: file: bosh-openstack-cpi-release/ci/tasks/run-unit-specs.yml image: openstack-cpi-release-docker-image +- name: create-devstack + serial: true + serial_groups: [ devstack ] + plan: + - in_parallel: + - get: openstack-cpi-release-docker-image + - get: bosh-openstack-cpi-release + passed: [ run-specs ] + trigger: true + - put: gcp-devstack + params: + terraform_source: bosh-openstack-cpi-release/ci/terraform/ci/gcp-devstack + - task: wait-for-devstack + image: openstack-cpi-release-docker-image + file: bosh-openstack-cpi-release/ci/tasks/wait-for-devstack.yml + params: + AUTH_URL: http://10.100.30.2/identity + +- name: destroy-devstack + serial: true + serial_groups: [ devstack ] + plan: + - get: bosh-openstack-cpi-release + passed: [ lifecycle, bats-ubuntu-manual ] + trigger: true + - put: gcp-devstack + params: + terraform_source: bosh-openstack-cpi-release/ci/terraform/ci/gcp-devstack + action: destroy + - name: lifecycle serial: true serial_groups: [ cleanup-lock-lifecycle ] plan: - in_parallel: - get: bosh-openstack-cpi-release - passed: [ run-specs ] - tags: [openstack] + passed: [ create-devstack ] timeout: *timeouts-long trigger: true - get: openstack-lifecycle-stemcell resource: openstack-ubuntu-jammy-stemcell - tags: [openstack] timeout: *timeouts-long - get: terraform-image - tags: [openstack] - get: openstack-cpi-release-docker-image - tags: [openstack] - - - task: write_ca_file - tags: [openstack] - file: bosh-openstack-cpi-release/ci/tasks/write-ca-file.yml - image: openstack-cpi-release-docker-image - params: - file_content: ((concourse_openstack_auth.openstack_ca_cert)) - task: terraform-apply - tags: [openstack] timeout: *timeouts-long image: terraform-image file: bosh-openstack-cpi-release/ci/tasks/terraform-apply-lifecycle.yml params: &lifecycle-terraform-params TF_VAR_prefix: "lifecycle-openstack-tests" - TF_VAR_auth_url: ((concourse_openstack_auth.auth_url)) - TF_VAR_cacert_file: ((config-json.tf_ca_file_path)) - TF_VAR_user_name: ((config-json.openstack_username)) - TF_VAR_password: ((config-json.openstack_password)) - TF_VAR_domain_name: ((config-json.lifecycle_openstack_domain)) - TF_VAR_project_name: ((config-json.openstack_project)) - TF_VAR_ext_net_id: ((config-json.tf_external_network_id)) - TF_VAR_ext_net_name: ((config-json.tf_external_network_name)) - TF_VAR_region_name: ((config-json.tf_region_name)) - TF_VAR_openstack_default_key_public_key: ((config-json.tf_default_public_key)) - TF_VAR_dns_nameservers: ((config-json.tf_dns_nameservers)) + TF_VAR_auth_url: http://10.100.30.2/identity + TF_VAR_user_name: ((openstack_ci_user.username)) + TF_VAR_password: ((openstack_ci_user.password)) + TF_VAR_domain_name: Default + TF_VAR_project_name: bosh + TF_VAR_ext_net_name: public + TF_VAR_region_name: RegionOne + TF_VAR_openstack_default_key_public_key: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHspetaTfg96YfZO3/xjKxGcjTnFPlxp7uP1KDisvPjB devstack-ci-openstack + TF_VAR_dns_nameservers: '["8.8.8.8"]' TF_VAR_use_lbaas: false - do: - task: test - tags: [openstack] timeout: *timeouts-long file: bosh-openstack-cpi-release/ci/tasks/run-lifecycle.yml image: openstack-cpi-release-docker-image params: - BOSH_OPENSTACK_DOMAIN: ((config-json.lifecycle_openstack_domain)) - BOSH_OPENSTACK_PROJECT: ((config-json.openstack_project)) - BOSH_OPENSTACK_USERNAME_V2: ((config-json.openstack_username)) - BOSH_OPENSTACK_AUTH_URL_V3: ((concourse_openstack_auth.auth_url)) - BOSH_OPENSTACK_USERNAME_V3: ((config-json.openstack_username)) - BOSH_OPENSTACK_API_KEY_V3: ((config-json.openstack_password)) + BOSH_OPENSTACK_DOMAIN: Default + BOSH_OPENSTACK_PROJECT: bosh + BOSH_OPENSTACK_USERNAME_V2: ((openstack_ci_user.username)) + BOSH_OPENSTACK_AUTH_URL_V3: http://10.100.30.2/identity + BOSH_OPENSTACK_USERNAME_V3: ((openstack_ci_user.username)) + BOSH_OPENSTACK_API_KEY_V3: ((openstack_ci_user.password)) BOSH_CLI_SILENCE_SLOW_LOAD_WARNING: true - BOSH_OPENSTACK_VOLUME_TYPE: ((config-json.lifecycle_openstack_volume_type)) + BOSH_OPENSTACK_VOLUME_TYPE: lvmdriver-1 BOSH_OPENSTACK_CONNECT_TIMEOUT: "600" BOSH_OPENSTACK_READ_TIMEOUT: "120" BOSH_OPENSTACK_WRITE_TIMEOUT: "120" - BOSH_OPENSTACK_CA_CERT: ((concourse_openstack_auth.openstack_ca_cert)) - BOSH_OPENSTACK_FLAVOR_WITH_NO_ROOT_DISK: ((config-json.openstack_flavor_with_no_root_disk)) - BOSH_OPENSTACK_AVAILABILITY_ZONE: ((config-json.availability_zone)) + BOSH_OPENSTACK_FLAVOR_WITH_NO_ROOT_DISK: no-root-disk + BOSH_OPENSTACK_AVAILABILITY_ZONE: nova BOSH_OPENSTACK_EXCLUDE_CINDER_V1: true - put: lifecycle-log - tags: [openstack] timeout: *timeouts-long params: file: output/lifecycle.log ensure: task: terraform-destroy - tags: [openstack] timeout: *timeouts-long image: terraform-image file: bosh-openstack-cpi-release/ci/tasks/terraform-destroy-lifecycle.yml @@ -122,45 +134,36 @@ jobs: plan: - in_parallel: - get: terraform-image - tags: [openstack] - - passed: [ run-specs ] + - passed: [ create-devstack ] get: bosh-openstack-cpi-release timeout: *timeouts-long - tags: [openstack] trigger: true - get: bosh-release timeout: *timeouts-long - tags: [openstack] - get: stemcell-director resource: openstack-ubuntu-jammy-stemcell timeout: *timeouts-long - tags: [openstack] trigger: true - get: stemcell resource: openstack-ubuntu-jammy-stemcell timeout: *timeouts-long - tags: [openstack] - get: bats timeout: *timeouts-long - tags: [openstack] - get: bosh-deployment timeout: *timeouts-long - tags: [openstack] - task: terraform-apply - tags: [openstack] timeout: *timeouts-long image: terraform-image file: bosh-openstack-cpi-release/ci/tasks/terraform-apply-bats-manual.yml params: &bats-terraform-vars TF_VAR_prefix: "bats-ubuntu-manual" - TF_VAR_auth_url: ((concourse_openstack_auth.auth_url)) - TF_VAR_domain_name: ((concourse_openstack_auth.openstack_domain)) - TF_VAR_user_name: ((config-json.openstack_username)) - TF_VAR_password: ((config-json.openstack_password)) - TF_VAR_project_name: ((config-json.openstack_project)) - TF_VAR_cacert_file: ((config-json.tf_ca_file_path)) - TF_VAR_region_name: ((config-json.tf_region_name)) + TF_VAR_auth_url: http://10.100.30.2/identity + TF_VAR_domain_name: Default + TF_VAR_user_name: ((openstack_ci_user.username)) + TF_VAR_password: ((openstack_ci_user.password)) + TF_VAR_project_name: bosh + TF_VAR_region_name: RegionOne TF_VAR_primary_net_name: "bats-ubuntu-manual-primary" TF_VAR_primary_net_cidr: "10.0.4.0/24" TF_VAR_primary_net_allocation_pool_start: "10.0.4.200" @@ -169,16 +172,14 @@ jobs: TF_VAR_secondary_net_cidr: "10.0.5.0/24" TF_VAR_secondary_net_allocation_pool_start: "10.0.5.200" TF_VAR_secondary_net_allocation_pool_end: "10.0.5.254" - TF_VAR_ext_net_name: ((config-json.tf_external_network_name)) - TF_VAR_ext_net_id: ((config-json.tf_external_network_id)) - TF_VAR_ext_net_cidr: ((config-json.tf_ext_net_cidr)) - TF_VAR_dns_nameservers: ((config-json.tf_dns_nameservers)) - TF_VAR_concourse_external_network_cidr: null - TF_VAR_openstack_default_key_public_key: ((config-json.tf_default_public_key)) + TF_VAR_ext_net_name: public + TF_VAR_ext_net_cidr: 172.24.4.0/24 + TF_VAR_dns_nameservers: '["8.8.8.8"]' + TF_VAR_concourse_external_network_cidr: 0.0.0.0/0 + TF_VAR_openstack_default_key_public_key: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHspetaTfg96YfZO3/xjKxGcjTnFPlxp7uP1KDisvPjB devstack-ci-openstack - do: - task: deploy - tags: [openstack] timeout: *timeouts-long file: bosh-openstack-cpi-release/ci/tasks/deploy-manual-networking.yml params: @@ -187,41 +188,35 @@ jobs: openstack_read_timeout: "180" openstack_write_timeout: "180" openstack_state_timeout: "900" - openstack_auth_url: ((concourse_openstack_auth.auth_url)) - openstack_username: ((config-json.openstack_username)) - openstack_api_key: ((config-json.openstack_password)) - openstack_domain: ((concourse_openstack_auth.openstack_domain)) + openstack_auth_url: http://10.100.30.2/identity + openstack_username: ((openstack_ci_user.username)) + openstack_api_key: ((openstack_ci_user.password)) + openstack_domain: Default distro: "ubuntu" - availability_zone: ((config-json.availability_zone)) - #NOTE: The CA cert path here is currently relative to the 'bosh-director-deployment' directory. - openstack_ca_file_path: ((concourse_openstack_auth.openstack_ca_cert_path)) + availability_zone: nova DEBUG_BATS: *debug_bats - task: test - tags: [openstack] timeout: *timeouts-long file: bosh-openstack-cpi-release/ci/tasks/run-manual-networking-bats.yml params: stemcell_name: "bosh-openstack-kvm-ubuntu-jammy-go_agent" - openstack_flavor_with_ephemeral_disk: ((config-json.openstack_flavor_with_ephemeral_disk)) - openstack_flavor_with_no_ephemeral_disk: ((config-json.openstack_flavor_with_no_ephemeral_disk)) - availability_zone: ((config-json.availability_zone)) + openstack_flavor_with_ephemeral_disk: with-ephemeral-disk + openstack_flavor_with_no_ephemeral_disk: without-ephemeral-disk + availability_zone: nova DEBUG_BATS: *debug_bats bats_rspec_tags: "--tag ~ssh" ensure: task: print-task-errors - tags: [openstack] timeout: *timeouts-long file: bosh-openstack-cpi-release/ci/tasks/print_task_errors.yml ensure: do: - task: teardown-director - tags: [openstack] timeout: *timeouts-long file: bosh-openstack-cpi-release/ci/tasks/teardown-director.yml ensure: task: terraform-destroy - tags: [openstack] timeout: *timeouts-long image: terraform-image file: bosh-openstack-cpi-release/ci/tasks/terraform-destroy-bats-manual.yml @@ -326,7 +321,6 @@ jobs: trigger: true - get: release-notes - task: promote - tags: [openstack] timeout: *timeouts-long file: bosh-shared-ci/tasks/release/create-final-release.yml input_mapping: @@ -339,7 +333,7 @@ jobs: provider: gcs options: credentials_source: static - json_key: '((cloud-foundry-gcp-credentials))' + json_key: '((gcp_json_key))' - put: bosh-openstack-cpi-release timeout: *timeouts-long params: @@ -410,7 +404,7 @@ jobs: provider: gcs options: credentials_source: static - json_key: '((cloud-foundry-gcp-credentials))' + json_key: '((gcp_json_key))' - task: bump-ruby-package image: bosh-ecosystem-concourse-image file: ruby-release/ci/tasks/shared/bump-ruby-package.yml @@ -428,7 +422,7 @@ jobs: provider: gcs options: credentials_source: static - json_key: '((cloud-foundry-gcp-credentials))' + json_key: '((gcp_json_key))' RUBY_VERSION_PATH: src/bosh_openstack_cpi/.ruby-version - task: run-unit-specs file: bosh-openstack-cpi-release/ci/tasks/run-unit-specs.yml @@ -444,31 +438,45 @@ resource_types: check_every: 168h source: &terraform-resource-source-keys repository: ljfranklin/terraform-resource - username: ((docker.username)) - password: ((docker.password)) + username: ((dockerhub_username)) + password: ((dockerhub_password)) - name: bosh-release type: registry-image check_every: 168h source: repository: dpb587/bosh-release-resource - username: ((docker.username)) - password: ((docker.password)) + username: ((dockerhub_username)) + password: ((dockerhub_password)) - name: gcs type: registry-image check_every: 168h source: repository: frodenas/gcs-resource - username: ((docker.username)) - password: ((docker.password)) + username: ((dockerhub_username)) + password: ((dockerhub_password)) - name: semver type: registry-image check_every: 168h source: repository: concourse/semver-resource - username: ((docker.username)) - password: ((docker.password)) + username: ((dockerhub_username)) + password: ((dockerhub_password)) resources: +- name: gcp-devstack + type: terraform + source: + env_name: openstack-cpi-devstack + backend_type: gcs + backend_config: + bucket: bosh-openstack-cpi-blobs + prefix: terraform/gcp-devstack + credentials: ((gcp_json_key)) + vars: + gcp_credentials_json: ((gcp_json_key)) + os_username: ((openstack_ci_user.username)) + os_password: ((openstack_ci_user.password)) + - name: bosh-shared-ci type: git source: @@ -476,24 +484,23 @@ resources: - name: lifecycle-log type: gcs - tags: [openstack] source: bucket: bosh-openstack-cpi-blobs versioned_file: lifecycle.log - json_key: ((cloud-foundry-gcp-credentials)) + json_key: ((gcp_json_key)) - name: release-notes type: gcs source: bucket: bosh-openstack-cpi-blobs versioned_file: release-notes - json_key: ((cloud-foundry-gcp-credentials)) + json_key: ((gcp_json_key)) - name: bosh-openstack-cpi-release type: git source: uri: git@github.com:cloudfoundry/bosh-openstack-cpi-release.git - branch: master + branch: devstack-gcp-ci private_key: ((github_deploy_key_bosh-openstack-cpi-release.private_key)) - name: bosh-openstack-cpi-release-github-release @@ -511,8 +518,8 @@ resources: uri: https://github.com/cloudfoundry/bosh-openstack-cpi-release.git branch: release-semver file: VERSION - username: bosh-admin-bot - password: ((github_read_write_token)) + username: ((github_read_write_packages.username)) + password: ((github_read_write_packages.password)) - name: bats type: git @@ -540,24 +547,24 @@ resources: type: docker-image check_every: never source: - username: ((docker.username)) - password: ((docker.password)) + username: ((dockerhub_username)) + password: ((dockerhub_password)) repository: boshcpi/openstack-cpi-release - name: bosh-ruby-release-registry-image type: registry-image check_every: 168h source: repository: bosh/ruby-release - username: ((docker.username)) - password: ((docker.password)) + username: ((dockerhub_username)) + password: ((dockerhub_password)) - name: bosh-security-scanner-registry-image type: registry-image check_every: 24h source: repository: bosh/security-scanner - username: ((docker.username)) - password: ((docker.password)) + username: ((dockerhub_username)) + password: ((dockerhub_password)) - name: golang-release type: git @@ -572,8 +579,8 @@ resources: check_every: 168h source: repository: bosh/bosh-ecosystem-concourse - username: ((docker.username)) - password: ((docker.password)) + username: ((dockerhub_username)) + password: ((dockerhub_password)) - name: weekly type: time source: diff --git a/ci/tasks/deploy-manual-networking.sh b/ci/tasks/deploy-manual-networking.sh index e45348edd..9f63c131e 100755 --- a/ci/tasks/deploy-manual-networking.sh +++ b/ci/tasks/deploy-manual-networking.sh @@ -14,7 +14,7 @@ source bosh-openstack-cpi-release/ci/tasks/utils.sh : ${openstack_username:?} : ${openstack_api_key:?} : ${openstack_domain:?} -: ${openstack_ca_file_path:?} +openstack_ca_file_path="${openstack_ca_file_path:-}" : ${DEBUG_BATS:?} : ${distro:?} optional_value availability_zone diff --git a/ci/tasks/wait-for-devstack.sh b/ci/tasks/wait-for-devstack.sh new file mode 100644 index 000000000..2e89604e5 --- /dev/null +++ b/ci/tasks/wait-for-devstack.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash + +# Polls the DevStack Keystone endpoint until it answers, so downstream jobs don't start against a +# half-installed cloud. DevStack is installed on the VM via its startup-script (~30-40 min from scratch). + +set -euo pipefail + +: ${AUTH_URL:?} + +echo "waiting for DevStack Keystone at ${AUTH_URL} ..." +for i in $(seq 1 100); do + code="$(curl -s -o /dev/null -m 5 -w '%{http_code}' "${AUTH_URL}" || echo 000)" + if [ "${code}" = "200" ] || [ "${code}" = "300" ]; then + echo "DevStack is up (HTTP ${code})" + exit 0 + fi + echo "not ready yet (attempt ${i}, HTTP ${code}); sleeping 30s" + sleep 30 +done + +echo "timed out waiting for DevStack at ${AUTH_URL}" +exit 1 diff --git a/ci/tasks/wait-for-devstack.yml b/ci/tasks/wait-for-devstack.yml new file mode 100644 index 000000000..7cfe97d0b --- /dev/null +++ b/ci/tasks/wait-for-devstack.yml @@ -0,0 +1,8 @@ +--- +platform: linux +inputs: + - name: bosh-openstack-cpi-release +run: + path: bosh-openstack-cpi-release/ci/tasks/wait-for-devstack.sh +params: + AUTH_URL: replace-me diff --git a/ci/tasks/write-ca-file.sh b/ci/tasks/write-ca-file.sh deleted file mode 100755 index 6ca4ae7df..000000000 --- a/ci/tasks/write-ca-file.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env bash - -set -e - -source bosh-openstack-cpi-release/ci/tasks/utils.sh - -optional_value file_content - -mkdir -p write-file -echo "$(echo ${file_content} | sed -r -e 's/ /\n/g ' -e 's/\nCERTIFICATE-----/ CERTIFICATE-----/g')" > write-file/file diff --git a/ci/tasks/write-ca-file.yml b/ci/tasks/write-ca-file.yml deleted file mode 100644 index bff753317..000000000 --- a/ci/tasks/write-ca-file.yml +++ /dev/null @@ -1,14 +0,0 @@ ---- -platform: linux -image_resource: - type: docker-image - source: - repository: boshcpi/openstack-cpi-release -inputs: - - name: bosh-openstack-cpi-release -outputs: - - name: write-file -run: - path: bosh-openstack-cpi-release/ci/tasks/write-ca-file.sh -params: - file_content: replace-me diff --git a/ci/terraform/ci/bats-manual/bats.tf b/ci/terraform/ci/bats-manual/bats.tf index cb51ab528..24e869b00 100644 --- a/ci/terraform/ci/bats-manual/bats.tf +++ b/ci/terraform/ci/bats-manual/bats.tf @@ -8,11 +8,15 @@ provider "openstack" { cacert_file = var.cacert_file } +data "openstack_networking_network_v2" "ext" { + name = var.ext_net_name +} + module "base" { source = "../modules/base" region_name = var.region_name project_name = var.project_name - ext_net_id = var.ext_net_id + ext_net_id = data.openstack_networking_network_v2.ext.id ext_net_cidr = var.ext_net_cidr concourse_external_network_cidr = var.concourse_external_network_cidr openstack_default_key_public_key = var.openstack_default_key_public_key @@ -102,10 +106,6 @@ variable "ext_net_name" { description = "OpenStack external network name to register floating IP" } -variable "ext_net_id" { - description = "OpenStack external network id to create router interface port" -} - variable "ext_net_cidr" { description = "OpenStack external network cidr to define ingress security group rules" } diff --git a/ci/terraform/ci/gcp-devstack/.gitignore b/ci/terraform/ci/gcp-devstack/.gitignore new file mode 100644 index 000000000..283d84eb5 --- /dev/null +++ b/ci/terraform/ci/gcp-devstack/.gitignore @@ -0,0 +1,5 @@ +.terraform/ +*.tfstate +*.tfstate.backup +*.tfplan +.terraform.lock.hcl diff --git a/ci/terraform/ci/gcp-devstack/install-devstack.sh b/ci/terraform/ci/gcp-devstack/install-devstack.sh new file mode 100644 index 000000000..10b84bca2 --- /dev/null +++ b/ci/terraform/ci/gcp-devstack/install-devstack.sh @@ -0,0 +1,224 @@ +#!/usr/bin/env bash + +# Provisions a self-contained DevStack OpenStack on a fresh Ubuntu 24.04 (Noble) GCP VM, +# configured deterministically so the community Concourse pipeline's static CredHub values +# (concourse_openstack_auth / config-json) resolve against it. Runs AS the VM, as a sudo-capable +# non-root user. Intended to be invoked by ci/pipeline.yml's create-devstack-worker job. +# +# The worker-registration step (join community Concourse tagged `openstack`) is a separate concern; +# see start-concourse-worker.sh which this script calls at the end if worker creds are provided. + +set -euo pipefail + +### ----- Tunables (override via env from the create-devstack-worker task) ----------------------- +DEVSTACK_BRANCH="${DEVSTACK_BRANCH:-stable/2025.1}" # Epoxy; matches the validated POC +STACK_USER="${STACK_USER:-stack}" +STACK_DIR="${STACK_DIR:-/opt/stack}" + +# Deterministic OpenStack identity. These MUST match the CredHub concourse_openstack_auth/config-json. +OS_PROJECT="${OS_PROJECT:-bosh}" +OS_USERNAME="${OS_USERNAME:-bosh}" +OS_PASSWORD="${OS_PASSWORD:-bosh-ci-password}" +OS_DOMAIN="${OS_DOMAIN:-Default}" +OS_REGION="${OS_REGION:-RegionOne}" + +# HOST_IP is pinned so auth_url is stable across per-run VMs. create-vm.sh should assign this as the +# VM's static internal IP (gcloud --private-network-ip). Falls back to the primary NIC address. +HOST_IP="${HOST_IP:-$(ip -4 route get 1.1.1.1 | awk '{print $7; exit}')}" + +# DevStack external ("public") network. DevStack always regenerates the neutron network UUID, so +# downstream terraform must resolve it BY NAME (see note in the pipeline plan). We only pin name/CIDR. +EXT_NET_NAME="${EXT_NET_NAME:-public}" +FLOATING_RANGE="${FLOATING_RANGE:-172.24.4.0/24}" +PUBLIC_NETWORK_GATEWAY="${PUBLIC_NETWORK_GATEWAY:-172.24.4.1}" + +# CIDR of the Concourse workers that must reach DevStack floating IPs off-box (bosh-concourse subnet). +WORKER_SOURCE_CIDR="${WORKER_SOURCE_CIDR:-10.0.0.0/24}" + +METADATA_OUT="${METADATA_OUT:-/opt/openstack-metadata.json}" +### --------------------------------------------------------------------------------------------- + +log() { echo "[install-devstack] $*"; } + +metadata_attr() { + curl -s -H "Metadata-Flavor: Google" \ + "http://metadata.google.internal/computeMetadata/v1/instance/attributes/$1" 2>/dev/null || true +} + +# The OpenStack user/password are supplied via VM metadata (from CredHub, out of the repo). Falls back +# to the defaults above for manual runs. Called after install_prereqs so curl is present. +load_credentials() { + local u p + u="$(metadata_attr os-username)"; [ -n "$u" ] && OS_USERNAME="$u" + p="$(metadata_attr os-password)"; [ -n "$p" ] && OS_PASSWORD="$p" +} + +require_nested_kvm() { + # POC: N2D (AMD) in europe-west4 does not expose SVM to the guest, so nested virt silently fails. + # Use N2 (Intel). Fail fast here rather than deep inside a BATS deploy. + if [[ ! -e /dev/kvm ]]; then + log "ERROR: /dev/kvm missing — VM has no nested virtualization. Use an N2 (Intel) machine type." + exit 1 + fi + if ! grep -Eq 'vmx|svm' /proc/cpuinfo; then + log "ERROR: no vmx/svm CPU flag exposed to guest — nested KVM will not work. Use N2 (Intel)." + exit 1 + fi +} + +install_prereqs() { + export DEBIAN_FRONTEND=noninteractive + sudo apt-get update -y + sudo apt-get install -y git python3 python3-venv python3-pip jq curl net-tools qemu-kvm + if ! id "${STACK_USER}" &>/dev/null; then + sudo useradd -s /bin/bash -d "${STACK_DIR}" -m "${STACK_USER}" + echo "${STACK_USER} ALL=(ALL) NOPASSWD: ALL" | sudo tee "/etc/sudoers.d/${STACK_USER}" >/dev/null + fi + sudo chmod 0755 "${STACK_DIR}" +} + +clone_devstack() { + if [[ ! -d "${STACK_DIR}/devstack" ]]; then + sudo -u "${STACK_USER}" git clone https://opendev.org/openstack/devstack "${STACK_DIR}/devstack" \ + --branch "${DEVSTACK_BRANCH}" --depth 1 + fi +} + +write_local_conf() { + # DevStack serves plain HTTP (no tls-proxy): this is a disposable test cloud reached only over the + # bosh-concourse VPC, so no CA to juggle per run. + sudo -u "${STACK_USER}" tee "${STACK_DIR}/devstack/local.conf" >/dev/null </dev/null 2>&1 && openstack $*" +} + +configure_offbox_floating_access() { + # Let Concourse workers (off the VM) reach DevStack floating IPs. Traffic is routed here by a GCP VPC + # route (dest=floating_range, next-hop=this VM) + can_ip_forward. OVN's external network drops + # off-subnet sources, so hairpin-SNAT the worker source to the br-ex gateway IP. Validated in BOSH-1744. + sudo sysctl -w net.ipv4.ip_forward=1 + sudo iptables -t nat -C POSTROUTING -s "${WORKER_SOURCE_CIDR}" -d "${FLOATING_RANGE}" -j SNAT --to-source "${PUBLIC_NETWORK_GATEWAY}" 2>/dev/null || \ + sudo iptables -t nat -A POSTROUTING -s "${WORKER_SOURCE_CIDR}" -d "${FLOATING_RANGE}" -j SNAT --to-source "${PUBLIC_NETWORK_GATEWAY}" +} + +register_volumev3_alias() { + # POC: Cinder registers as service type `block-storage`; the fog-openstack gem the CPI/tests use + # expects a `volumev3` service. Register an alias endpoint pointing at the same Cinder URL. + local cinder_url + cinder_url="$(os_admin endpoint list --service block-storage --interface public -f value -c URL | head -1)" + if [[ -n "${cinder_url}" ]]; then + if ! os_admin service list -f value -c Type | grep -qx volumev3; then + os_admin service create --name cinderv3-alias volumev3 + os_admin endpoint create volumev3 public "${cinder_url}" --region "${OS_REGION}" + os_admin endpoint create volumev3 internal "${cinder_url}" --region "${OS_REGION}" + os_admin endpoint create volumev3 admin "${cinder_url}" --region "${OS_REGION}" + fi + fi +} + +create_project_and_user() { + os_admin project show "${OS_PROJECT}" >/dev/null 2>&1 || os_admin project create --domain "${OS_DOMAIN}" "${OS_PROJECT}" + os_admin user show "${OS_USERNAME}" >/dev/null 2>&1 || \ + os_admin user create --domain "${OS_DOMAIN}" --password "${OS_PASSWORD}" "${OS_USERNAME}" + os_admin role add --project "${OS_PROJECT}" --user "${OS_USERNAME}" member || true + os_admin role add --project "${OS_PROJECT}" --user "${OS_USERNAME}" admin || true +} + +create_flavors() { + # Test flavors referenced by config-json. Names must match the CredHub values. + # with_no_root_disk: root disk = 0 (boot-from-volume path). with/without ephemeral disk vary swap/ephemeral. + os_admin flavor show m1.small >/dev/null 2>&1 || os_admin flavor create --ram 2048 --disk 20 --vcpus 1 m1.small + os_admin flavor show no-root-disk >/dev/null 2>&1 || \ + os_admin flavor create --ram 1024 --disk 0 --vcpus 1 no-root-disk + os_admin flavor show with-ephemeral-disk >/dev/null 2>&1 || \ + os_admin flavor create --ram 1024 --disk 5 --ephemeral 5 --vcpus 1 with-ephemeral-disk + os_admin flavor show without-ephemeral-disk >/dev/null 2>&1 || \ + os_admin flavor create --ram 1024 --disk 5 --ephemeral 0 --vcpus 1 without-ephemeral-disk +} + +bump_quotas() { + os_admin quota set --instances 20 --cores 20 --ram 40960 \ + --volumes 20 --gigabytes 200 \ + --networks 20 --subnets 40 --ports 100 --routers 20 --floating-ips 20 --secgroups 40 \ + "${OS_PROJECT}" +} + +upload_jammy_stemcell() { + # The lifecycle suite uploads its own stemcell via the CPI; BATS needs the Jammy image present in + # Glance for the director/deployment stemcell. Skip if STEMCELL_IMAGE_PATH not provided. + if [[ -n "${STEMCELL_IMAGE_PATH:-}" && -f "${STEMCELL_IMAGE_PATH}" ]]; then + os_admin image show bosh-openstack-kvm-ubuntu-jammy-go_agent >/dev/null 2>&1 || \ + os_admin image create --disk-format qcow2 --container-format bare \ + --file "${STEMCELL_IMAGE_PATH}" bosh-openstack-kvm-ubuntu-jammy-go_agent + else + log "STEMCELL_IMAGE_PATH not set; skipping Glance stemcell upload (lifecycle uploads its own)." + fi +} + +emit_metadata() { + # Values the pipeline may want for debugging; ext net is resolved by NAME downstream (UUID is per-run). + local ext_net_id + ext_net_id="$(os_admin network show "${EXT_NET_NAME}" -f value -c id || true)" + cat </dev/null +{ + "auth_url": "http://${HOST_IP}/identity", + "openstack_domain": "${OS_DOMAIN}", + "openstack_project": "${OS_PROJECT}", + "openstack_username": "${OS_USERNAME}", + "region": "${OS_REGION}", + "ext_net_name": "${EXT_NET_NAME}", + "ext_net_id": "${ext_net_id}", + "ext_net_cidr": "${FLOATING_RANGE}" +} +EOF + log "Wrote ${METADATA_OUT}:"; sudo cat "${METADATA_OUT}" +} + +main() { + require_nested_kvm + install_prereqs + load_credentials + clone_devstack + write_local_conf + run_stack + configure_offbox_floating_access + register_volumev3_alias + create_project_and_user + create_flavors + bump_quotas + upload_jammy_stemcell + emit_metadata + log "DevStack ready. Start the Concourse worker next (start-concourse-worker.sh)." +} + +main "$@" diff --git a/ci/terraform/ci/gcp-devstack/main.tf b/ci/terraform/ci/gcp-devstack/main.tf new file mode 100644 index 000000000..53639c7b7 --- /dev/null +++ b/ci/terraform/ci/gcp-devstack/main.tf @@ -0,0 +1,189 @@ +# GCP-side infrastructure for the OpenStack CPI test environment: an N2 VM that runs DevStack, +# attached to the community Concourse VPC (bosh-concourse) so the Concourse workers can reach it. +# +# Two reachability paths for the workers (both validated): +# 1. DevStack API -> the VM's internal IP (allowed by google_compute_firewall.devstack). +# 2. Floating IPs -> routed to the VM (google_compute_route.floating) + can_ip_forward, plus a +# SNAT hairpin rule applied by install-devstack.sh so OVN accepts the off-subnet worker source. +# The VM has no external IP; bosh-concourse Cloud NAT provides egress for the DevStack install. + +provider "google" { + project = var.project + region = var.region + credentials = var.gcp_credentials_json != "" ? var.gcp_credentials_json : null +} + +variable "gcp_credentials_json" { + description = "Service-account JSON key. Leave empty to use GOOGLE_CREDENTIALS / ADC." + type = string + default = "" + sensitive = true +} + +variable "project" { + default = "cloud-foundry-310819" +} + +variable "region" { + default = "europe-west2" +} + +variable "zone" { + default = "europe-west2-a" # same zone as the Concourse workers +} + +variable "network" { + description = "Existing VPC the Concourse workers live in." + default = "bosh-concourse" +} + +variable "subnet_name" { + default = "devstack-openstack-cpi" +} + +variable "subnet_cidr" { + description = "Dedicated subnet; must not overlap 10.0.0.0/24 or the 10.100.x integration ranges." + default = "10.100.30.0/24" +} + +variable "vm_name" { + default = "devstack-openstack-cpi" +} + +variable "machine_type" { + description = "MUST be a nested-virt-capable Intel type. NOT E2 (worker type) and NOT N2D (no vmx in europe-west4 per POC)." + default = "n2-standard-4" +} + +variable "boot_disk_size" { + default = 120 +} + +variable "boot_disk_type" { + default = "pd-balanced" +} + +variable "image" { + default = "projects/ubuntu-os-cloud/global/images/family/ubuntu-2404-lts-amd64" +} + +variable "private_ip" { + description = "Pinned internal IP so concourse_openstack_auth.auth_url stays stable across per-run VMs." + type = string + default = "10.100.30.2" +} + +variable "floating_range" { + description = "DevStack external/floating network CIDR; routed to the VM for worker reachability." + default = "172.24.4.0/24" +} + +variable "worker_source_ranges" { + description = "CIDRs allowed to reach the VM + floating range (the Concourse worker subnet)." + type = list(string) + default = ["10.0.0.0/24"] +} + +variable "network_tag" { + default = "devstack-openstack-cpi" +} + +variable "os_username" { + description = "OpenStack user the pipeline authenticates as; created inside DevStack by install-devstack.sh." + type = string + default = "bosh" +} + +variable "os_password" { + description = "Password for os_username. Sourced from CredHub (openstack_ci_user) via the pipeline; passed to the VM as metadata, never committed." + type = string + sensitive = true +} + +resource "google_compute_subnetwork" "devstack" { + name = var.subnet_name + ip_cidr_range = var.subnet_cidr + region = var.region + network = var.network +} + +resource "google_compute_instance" "devstack" { + name = var.vm_name + machine_type = var.machine_type + zone = var.zone + tags = [var.network_tag] + + boot_disk { + initialize_params { + image = var.image + size = var.boot_disk_size + type = var.boot_disk_type + } + } + + # Nested KVM for BOSH stemcells. Requires an Intel (N2) type; the flag adds the vmx license. + advanced_machine_features { + enable_nested_virtualization = true + } + + can_ip_forward = true # required to forward floating-range traffic to br-ex + + network_interface { + subnetwork = google_compute_subnetwork.devstack.self_link + network_ip = var.private_ip != "" ? var.private_ip : null + # No access_config: internal-only. Egress via bosh-concourse Cloud NAT. + } + + # DevStack is installed on boot; install-devstack.sh also applies the SNAT hairpin rule. + # os-username/os-password are read by the script from the metadata server (kept out of the repo). + metadata = { + startup-script = file("${path.module}/install-devstack.sh") + os-username = var.os_username + os-password = var.os_password + } +} + +# Route floating IPs to the DevStack VM (longest-prefix match beats the default route). +resource "google_compute_route" "floating" { + name = "${var.vm_name}-floating" + network = var.network + dest_range = var.floating_range + next_hop_instance = google_compute_instance.devstack.self_link + priority = 900 +} + +# Allow the workers to reach the VM (API) and, via the route, the floating range. Target-tagged so it +# cannot affect anything else in the shared VPC. +resource "google_compute_firewall" "devstack" { + name = "${var.vm_name}-access" + network = var.network + direction = "INGRESS" + source_ranges = var.worker_source_ranges + target_tags = [var.network_tag] + + allow { + protocol = "tcp" + } + allow { + protocol = "udp" + } + allow { + protocol = "icmp" + } +} + +output "vm_internal_ip" { + value = google_compute_instance.devstack.network_interface[0].network_ip +} + +output "auth_url" { + value = "http://${google_compute_instance.devstack.network_interface[0].network_ip}/identity" +} + +output "floating_range" { + value = var.floating_range +} + +output "subnet" { + value = google_compute_subnetwork.devstack.self_link +} diff --git a/ci/terraform/ci/gcp-devstack/versions.tf b/ci/terraform/ci/gcp-devstack/versions.tf new file mode 100644 index 000000000..2e5ed753c --- /dev/null +++ b/ci/terraform/ci/gcp-devstack/versions.tf @@ -0,0 +1,9 @@ +terraform { + required_version = ">= 1.0" + required_providers { + google = { + source = "hashicorp/google" + version = ">= 5.0, < 7.0" + } + } +} diff --git a/ci/terraform/ci/lifecycle/lifecycle.tf b/ci/terraform/ci/lifecycle/lifecycle.tf index 90a4f2a5f..76cc3f8b2 100644 --- a/ci/terraform/ci/lifecycle/lifecycle.tf +++ b/ci/terraform/ci/lifecycle/lifecycle.tf @@ -8,11 +8,15 @@ provider "openstack" { cacert_file = var.cacert_file } +data "openstack_networking_network_v2" "ext" { + name = var.ext_net_name +} + module "base" { source = "../modules/base" region_name = var.region_name project_name = var.project_name - ext_net_id = var.ext_net_id + ext_net_id = data.openstack_networking_network_v2.ext.id ext_net_cidr = "" concourse_external_network_cidr = "" openstack_default_key_public_key = var.openstack_default_key_public_key @@ -63,10 +67,6 @@ variable "region_name" { description = "OpenStack region name" } -variable "ext_net_id" { - description = "OpenStack external network id to create router interface port" -} - variable "ext_net_name" { description = "OpenStack external network name to create router interface port" } From e009e06b31101a93c06ac43d53a4ef480a8b0175 Mon Sep 17 00:00:00 2001 From: Ned Petrov Date: Tue, 25 Aug 2026 08:56:48 +0300 Subject: [PATCH 02/25] Fetch release repo over HTTPS with public-repo token (no deploy key needed) --- ci/pipeline.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ci/pipeline.yml b/ci/pipeline.yml index 44072ec78..a7aebf9e4 100644 --- a/ci/pipeline.yml +++ b/ci/pipeline.yml @@ -499,9 +499,10 @@ resources: - name: bosh-openstack-cpi-release type: git source: - uri: git@github.com:cloudfoundry/bosh-openstack-cpi-release.git + uri: https://github.com/cloudfoundry/bosh-openstack-cpi-release.git branch: devstack-gcp-ci - private_key: ((github_deploy_key_bosh-openstack-cpi-release.private_key)) + username: bosh-admin-bot + password: ((github_public_repo_token)) - name: bosh-openstack-cpi-release-github-release type: github-release From 383641793ad687ccb7780cdd98c026803ff9b543 Mon Sep 17 00:00:00 2001 From: Ned Petrov Date: Tue, 25 Aug 2026 09:09:50 +0300 Subject: [PATCH 03/25] fix: make wait-for-devstack.sh executable --- ci/tasks/wait-for-devstack.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 ci/tasks/wait-for-devstack.sh diff --git a/ci/tasks/wait-for-devstack.sh b/ci/tasks/wait-for-devstack.sh old mode 100644 new mode 100755 From 778aaae7db3ec7559fe4172b1ea5c63808e5f837 Mon Sep 17 00:00:00 2001 From: Ned Petrov Date: Tue, 25 Aug 2026 09:12:30 +0300 Subject: [PATCH 04/25] quote AUTH_URL parameter expansion --- ci/tasks/wait-for-devstack.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) mode change 100755 => 100644 ci/tasks/wait-for-devstack.sh diff --git a/ci/tasks/wait-for-devstack.sh b/ci/tasks/wait-for-devstack.sh old mode 100755 new mode 100644 index 2e89604e5..5968cbb6a --- a/ci/tasks/wait-for-devstack.sh +++ b/ci/tasks/wait-for-devstack.sh @@ -5,7 +5,7 @@ set -euo pipefail -: ${AUTH_URL:?} +: "${AUTH_URL:?}" echo "waiting for DevStack Keystone at ${AUTH_URL} ..." for i in $(seq 1 100); do From c97cf25cf861090810e9165a5b65949a50866817 Mon Sep 17 00:00:00 2001 From: Ned Petrov Date: Tue, 25 Aug 2026 09:21:02 +0300 Subject: [PATCH 05/25] make wait-for-devstack.sh executable --- ci/tasks/wait-for-devstack.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 ci/tasks/wait-for-devstack.sh diff --git a/ci/tasks/wait-for-devstack.sh b/ci/tasks/wait-for-devstack.sh old mode 100644 new mode 100755 From 06f2c949bdebc67283e01bd981665df354a3c941 Mon Sep 17 00:00:00 2001 From: Ned Petrov Date: Tue, 25 Aug 2026 09:30:25 +0300 Subject: [PATCH 06/25] Improve DevStack readiness check and cleanup gate - wait-for-devstack: add CI user auth check after Keystone responds, ensuring create_project_and_user has completed before tests start - pipeline: pass OS_USERNAME/PASSWORD/PROJECT to wait-for-devstack task - destroy-devstack: remove passed gate on lifecycle/bats so cleanup always runs; add devstack serial_group to lifecycle and bats jobs - configure.sh: default CONCOURSE_TARGET to bosh (community Concourse) --- ci/configure.sh | 2 +- ci/pipeline.yml | 9 ++++++--- ci/tasks/wait-for-devstack.sh | 28 +++++++++++++++++++++++++--- ci/tasks/wait-for-devstack.yml | 3 +++ 4 files changed, 35 insertions(+), 7 deletions(-) diff --git a/ci/configure.sh b/ci/configure.sh index 810afc45c..be88209b2 100755 --- a/ci/configure.sh +++ b/ci/configure.sh @@ -2,5 +2,5 @@ set -eu -fly -t "${CONCOURSE_TARGET:-bosh-ecosystem}" set-pipeline -p "bosh-openstack-cpi" \ +fly -t "${CONCOURSE_TARGET:-bosh}" set-pipeline -p "bosh-openstack-cpi" \ -c ci/pipeline.yml diff --git a/ci/pipeline.yml b/ci/pipeline.yml index a7aebf9e4..613169637 100644 --- a/ci/pipeline.yml +++ b/ci/pipeline.yml @@ -52,13 +52,16 @@ jobs: file: bosh-openstack-cpi-release/ci/tasks/wait-for-devstack.yml params: AUTH_URL: http://10.100.30.2/identity + OS_USERNAME: ((openstack_ci_user.username)) + OS_PASSWORD: ((openstack_ci_user.password)) + OS_PROJECT: bosh - name: destroy-devstack serial: true serial_groups: [ devstack ] plan: - get: bosh-openstack-cpi-release - passed: [ lifecycle, bats-ubuntu-manual ] + passed: [ create-devstack ] trigger: true - put: gcp-devstack params: @@ -67,7 +70,7 @@ jobs: - name: lifecycle serial: true - serial_groups: [ cleanup-lock-lifecycle ] + serial_groups: [ devstack, cleanup-lock-lifecycle ] plan: - in_parallel: - get: bosh-openstack-cpi-release @@ -130,7 +133,7 @@ jobs: - name: bats-ubuntu-manual serial: true - serial_groups: [ cleanup-lock-bats-ubuntu-manual ] + serial_groups: [ devstack, cleanup-lock-bats-ubuntu-manual ] plan: - in_parallel: - get: terraform-image diff --git a/ci/tasks/wait-for-devstack.sh b/ci/tasks/wait-for-devstack.sh index 5968cbb6a..54895ffa5 100755 --- a/ci/tasks/wait-for-devstack.sh +++ b/ci/tasks/wait-for-devstack.sh @@ -6,17 +6,39 @@ set -euo pipefail : "${AUTH_URL:?}" +: "${OS_USERNAME:?}" +: "${OS_PASSWORD:?}" +: "${OS_PROJECT:?}" echo "waiting for DevStack Keystone at ${AUTH_URL} ..." for i in $(seq 1 100); do code="$(curl -s -o /dev/null -m 5 -w '%{http_code}' "${AUTH_URL}" || echo 000)" if [ "${code}" = "200" ] || [ "${code}" = "300" ]; then - echo "DevStack is up (HTTP ${code})" - exit 0 + echo "Keystone is up (HTTP ${code})" + break fi echo "not ready yet (attempt ${i}, HTTP ${code}); sleeping 30s" sleep 30 + if [ "${i}" -eq 100 ]; then + echo "timed out waiting for DevStack at ${AUTH_URL}" + exit 1 + fi +done + +echo "waiting for CI user auth (confirms create_project_and_user has run) ..." +for i in $(seq 1 20); do + code="$(curl -s -o /dev/null -m 10 -w '%{http_code}' \ + -X POST "${AUTH_URL}/v3/auth/tokens" \ + -H "Content-Type: application/json" \ + -d "{\"auth\":{\"identity\":{\"methods\":[\"password\"],\"password\":{\"user\":{\"name\":\"${OS_USERNAME}\",\"domain\":{\"name\":\"Default\"},\"password\":\"${OS_PASSWORD}\"}}},\"scope\":{\"project\":{\"name\":\"${OS_PROJECT}\",\"domain\":{\"name\":\"Default\"}}}}}" \ + || echo 000)" + if [ "${code}" = "201" ]; then + echo "CI user auth OK — DevStack fully ready" + exit 0 + fi + echo "auth not ready yet (attempt ${i}, HTTP ${code}); sleeping 30s" + sleep 30 done -echo "timed out waiting for DevStack at ${AUTH_URL}" +echo "timed out waiting for CI user auth at ${AUTH_URL}" exit 1 diff --git a/ci/tasks/wait-for-devstack.yml b/ci/tasks/wait-for-devstack.yml index 7cfe97d0b..5ea8b65df 100644 --- a/ci/tasks/wait-for-devstack.yml +++ b/ci/tasks/wait-for-devstack.yml @@ -6,3 +6,6 @@ run: path: bosh-openstack-cpi-release/ci/tasks/wait-for-devstack.sh params: AUTH_URL: replace-me + OS_USERNAME: replace-me + OS_PASSWORD: replace-me + OS_PROJECT: replace-me From d5d146144978c3f3b5b64d0062fd0ddc6aa913c3 Mon Sep 17 00:00:00 2001 From: Ned Petrov Date: Tue, 25 Aug 2026 09:32:24 +0300 Subject: [PATCH 07/25] remove pipeline-locator vanity job --- ci/pipeline.yml | 10 ---------- ci/tasks/pipeline-locator.yml | 25 ------------------------- 2 files changed, 35 deletions(-) delete mode 100644 ci/tasks/pipeline-locator.yml diff --git a/ci/pipeline.yml b/ci/pipeline.yml index 613169637..d8a214bde 100644 --- a/ci/pipeline.yml +++ b/ci/pipeline.yml @@ -13,16 +13,6 @@ jobs: params: build: bosh-openstack-cpi-release/ci/docker/openstack-cpi-release -- name: pipeline-locator - plan: - - get: bosh-openstack-cpi-release - - task: pipeline-location - file: bosh-openstack-cpi-release/ci/tasks/pipeline-locator.yml - params: - PIPELINE_FILENAME: "pipeline.yml" - input_mapping: - pipeline-repo: bosh-openstack-cpi-release - - name: run-specs serial: true plan: diff --git a/ci/tasks/pipeline-locator.yml b/ci/tasks/pipeline-locator.yml deleted file mode 100644 index 1584a3c5d..000000000 --- a/ci/tasks/pipeline-locator.yml +++ /dev/null @@ -1,25 +0,0 @@ ---- -platform: linux - -image_resource: - type: docker-image - source: - repository: bosh/bosh-ecosystem-concourse - -inputs: - - name: pipeline-repo - -params: - PIPELINE_FILENAME: "" - -run: - path: sh - args: - - -ec - - | - echo -n "My pipeline is located in the repo: " - cd pipeline-repo - git remote -v | awk 'NR==1{gsub(/.git/, "", $2); gsub(/github.com:/, "https://github.com/", $2); print $2}' - echo "and it is called: ${PIPELINE_FILENAME}" - echo - echo "Happy continuous thing-doing!" \ No newline at end of file From a1f6d64bb7907e4c8632c39d25a59fefeb58ff8f Mon Sep 17 00:00:00 2001 From: Ned Petrov Date: Tue, 25 Aug 2026 09:41:33 +0300 Subject: [PATCH 08/25] add bump-go-deps job for Go module vendor updates --- ci/pipeline.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/ci/pipeline.yml b/ci/pipeline.yml index d8a214bde..59e8dabf6 100644 --- a/ci/pipeline.yml +++ b/ci/pipeline.yml @@ -344,6 +344,30 @@ jobs: params: file: release_metadata/empty-file +- name: bump-go-deps + plan: + - in_parallel: + - get: bosh-openstack-cpi-release + - get: golang-release + - get: openstack-cpi-release-docker-image + - get: weekly + trigger: true + - task: bump-deps + file: golang-release/ci/tasks/shared/bump-deps.yml + input_mapping: + input_repo: bosh-openstack-cpi-release + output_mapping: + output_repo: bosh-openstack-cpi-release + params: + SOURCE_PATH: src/openstack_cpi_golang + - task: run-unit-specs + file: bosh-openstack-cpi-release/ci/tasks/run-unit-specs.yml + image: openstack-cpi-release-docker-image + - put: bosh-openstack-cpi-release + params: + repository: bosh-openstack-cpi-release + rebase: true + - name: bump-deps plan: - in_parallel: From 2f84c2e4213647933a23a4e68d36417593664001 Mon Sep 17 00:00:00 2001 From: Ned Petrov Date: Tue, 25 Aug 2026 09:52:28 +0300 Subject: [PATCH 09/25] Rename bump task for ruby --- ci/pipeline.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/pipeline.yml b/ci/pipeline.yml index 59e8dabf6..1d4e2b41f 100644 --- a/ci/pipeline.yml +++ b/ci/pipeline.yml @@ -368,7 +368,7 @@ jobs: repository: bosh-openstack-cpi-release rebase: true -- name: bump-deps +- name: bump-ruby-deps plan: - in_parallel: - get: bosh-openstack-cpi-release From ef7f1757b0d3563a272d0a6aaab9d9213ec80c6c Mon Sep 17 00:00:00 2001 From: Ned Petrov Date: Tue, 25 Aug 2026 09:56:48 +0300 Subject: [PATCH 10/25] fix cpi_logging_spec regex for modern OpenStack endpoint paths DevStack Epoxy serves compute at /compute/v2.1/ and Cinder at /volume/v3/ instead of the older /v2.1/ and /v3/ root paths. Update regex patterns to allow an optional path prefix. --- src/bosh_openstack_cpi/spec/integration/cpi_logging_spec.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/bosh_openstack_cpi/spec/integration/cpi_logging_spec.rb b/src/bosh_openstack_cpi/spec/integration/cpi_logging_spec.rb index 5840032d4..903d2b2ed 100644 --- a/src/bosh_openstack_cpi/spec/integration/cpi_logging_spec.rb +++ b/src/bosh_openstack_cpi/spec/integration/cpi_logging_spec.rb @@ -25,14 +25,14 @@ 'ephemeral_disk_size' => 2 * 1024, ) - expect(log.string).to match(%r{excon\.request GET https?://.*:\d+/v\d\.\d/}) - expect(log.string).to match(%r{excon\.response HTTP/.*:\d+/v\d\.\d/}) + expect(log.string).to match(%r{excon\.request GET https?://.*:\d+(?:/\w+)*/v\d\.\d/}) + expect(log.string).to match(%r{excon\.response HTTP/.*:\d+(?:/\w+)*/v\d\.\d/}) end it 'logs excon exceptions' do cpi.delete_disk('123') - expect(log.string).to match(%r{excon\.error HTTP/1\.1 404 Not Found /v3/.*/volumes/123 params: .*}) + expect(log.string).to match(%r{excon\.error HTTP/1\.1 404 Not Found \S*/v3/\S*volumes/123 params: .*}) expect(log.string).not_to include('excon.error.response') end From de3e9402174ff1afe09a9c48e9e3419aecf09022 Mon Sep 17 00:00:00 2001 From: Ned Petrov Date: Tue, 25 Aug 2026 10:04:42 +0300 Subject: [PATCH 11/25] ci: fix shellcheck issues in deploy-manual-networking.sh MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Quote all unquoted variable expansions (SC2086) - Convert maybe_use_custom_ca_ops_file/maybe_load_custom_ca_file to arrays so they expand correctly when empty (SC2086, SC2105 fix) - Remove break from case empty branch — not valid outside loops (SC2105) - Replace echo $(md5sum ...) with direct md5sum invocation (SC2005/SC2046) - Suppress SC1091/SC2154 where dynamic sourcing prevents static analysis --- ci/tasks/deploy-manual-networking.sh | 87 ++++++++++++++-------------- 1 file changed, 44 insertions(+), 43 deletions(-) diff --git a/ci/tasks/deploy-manual-networking.sh b/ci/tasks/deploy-manual-networking.sh index 9f63c131e..bcebc287b 100755 --- a/ci/tasks/deploy-manual-networking.sh +++ b/ci/tasks/deploy-manual-networking.sh @@ -2,21 +2,22 @@ set -ex +# shellcheck disable=SC1091 source bosh-openstack-cpi-release/ci/tasks/utils.sh # Variables from pipeline.yml -: ${openstack_flavor:?} -: ${openstack_connection_timeout:?} -: ${openstack_read_timeout:?} -: ${openstack_write_timeout:?} -: ${openstack_state_timeout:?} -: ${openstack_auth_url:?} -: ${openstack_username:?} -: ${openstack_api_key:?} -: ${openstack_domain:?} +: "${openstack_flavor:?}" +: "${openstack_connection_timeout:?}" +: "${openstack_read_timeout:?}" +: "${openstack_write_timeout:?}" +: "${openstack_state_timeout:?}" +: "${openstack_auth_url:?}" +: "${openstack_username:?}" +: "${openstack_api_key:?}" +: "${openstack_domain:?}" +: "${DEBUG_BATS:?}" +: "${distro:?}" openstack_ca_file_path="${openstack_ca_file_path:-}" -: ${DEBUG_BATS:?} -: ${distro:?} optional_value availability_zone # Variables from TF @@ -35,16 +36,15 @@ export_terraform_variable terraform-cpi/metadata "director_public_ip" cpi_release_name="bosh-openstack-cpi" deployment_dir="${PWD}/bosh-director-deployment" -maybe_use_custom_ca_ops_file="" -maybe_load_custom_ca_file="" +maybe_use_custom_ca_ops_file=() +maybe_load_custom_ca_file=() case "$openstack_ca_file_path" in "") - break ;; *) - maybe_use_custom_ca_ops_file="-o ../bosh-deployment/openstack/custom-ca.yml" - maybe_load_custom_ca_file="--var-file=openstack_ca_cert=${openstack_ca_file_path}" + maybe_use_custom_ca_ops_file=(-o ../bosh-deployment/openstack/custom-ca.yml) + maybe_load_custom_ca_file=(--var-file=openstack_ca_cert="${openstack_ca_file_path}") ;; esac @@ -55,26 +55,27 @@ pushd bosh-openstack-cpi-release --tarball "../bosh-openstack-cpi-dev.tgz" popd -cp ./bosh-openstack-cpi-dev.tgz ${deployment_dir}/${cpi_release_name}.tgz -cp ./stemcell-director/*.tgz ${deployment_dir}/stemcell.tgz -prepare_bosh_release ${distro} +cp ./bosh-openstack-cpi-dev.tgz "${deployment_dir}/${cpi_release_name}.tgz" +cp ./stemcell-director/*.tgz "${deployment_dir}/stemcell.tgz" +prepare_bosh_release "${distro}" echo "Calculating MD5 of original stemcell:" -echo $(md5sum stemcell-director/*.tgz) +md5sum stemcell-director/*.tgz echo "Calculating MD5 of copied stemcell:" -echo $(md5sum ${deployment_dir}/stemcell.tgz) +md5sum "${deployment_dir}/stemcell.tgz" -cd ${deployment_dir} +cd "${deployment_dir}" echo "using bosh CLI version..." bosh-go --version echo "check bosh deployment interpolation" +# shellcheck disable=SC2154 bosh-go int ../bosh-deployment/bosh.yml \ --var-errs --var-errs-unused \ --vars-store ./credentials.yml \ -o ../bosh-deployment/openstack/cpi.yml \ - ${maybe_use_custom_ca_ops_file} \ + "${maybe_use_custom_ca_ops_file[@]}" \ -o ../bosh-deployment/external-ip-not-recommended.yml \ -o ../bosh-deployment/misc/source-releases/bosh.yml \ -o ../bosh-deployment/jumpbox-user.yml \ @@ -83,27 +84,27 @@ bosh-go int ../bosh-deployment/bosh.yml \ -o ../bosh-openstack-cpi-release/ci/ops_files/timeouts.yml \ -o ../bosh-openstack-cpi-release/ci/ops_files/remove-registry.yml \ -o ../bosh-openstack-cpi-release/ci/ops_files/move-agent-properties-to-env-for-create-env.yml \ - -v auth_url=${openstack_auth_url} \ - -v availability_zone=${availability_zone:-'~'} \ - -v default_security_groups=[${security_group}] \ - -v default_key_name=${default_key_name} \ + -v auth_url="${openstack_auth_url}" \ + -v availability_zone="${availability_zone:-'~'}" \ + -v default_security_groups="[${security_group}]" \ + -v default_key_name="${default_key_name}" \ -v director_name='bosh' \ - -v dns=${dns} \ - -v internal_ip=${director_private_ip} \ - -v external_ip=${director_public_ip} \ - -v primary_net_id=${primary_net_id} \ - -v internal_cidr=${primary_net_cidr} \ - -v internal_gw=${primary_net_gateway} \ - -v openstack_connection_timeout=${openstack_connection_timeout} \ - -v openstack_project=${openstack_project} \ - -v openstack_domain=${openstack_domain} \ - -v openstack_flavor=${openstack_flavor} \ - -v openstack_password=${openstack_api_key} \ - -v openstack_read_timeout=${openstack_read_timeout} \ - -v openstack_state_timeout=${openstack_state_timeout} \ - -v openstack_username=${openstack_username} \ - -v openstack_write_timeout=${openstack_write_timeout} \ - ${maybe_load_custom_ca_file} \ + -v dns="${dns}" \ + -v internal_ip="${director_private_ip}" \ + -v external_ip="${director_public_ip}" \ + -v primary_net_id="${primary_net_id}" \ + -v internal_cidr="${primary_net_cidr}" \ + -v internal_gw="${primary_net_gateway}" \ + -v openstack_connection_timeout="${openstack_connection_timeout}" \ + -v openstack_project="${openstack_project}" \ + -v openstack_domain="${openstack_domain}" \ + -v openstack_flavor="${openstack_flavor}" \ + -v openstack_password="${openstack_api_key}" \ + -v openstack_read_timeout="${openstack_read_timeout}" \ + -v openstack_state_timeout="${openstack_state_timeout}" \ + -v openstack_username="${openstack_username}" \ + -v openstack_write_timeout="${openstack_write_timeout}" \ + "${maybe_load_custom_ca_file[@]}" \ -v region=null | tee bosh.yml echo "deploying BOSH..." From 2b34973e23ded5075d6d78623734993b85bb4fe1 Mon Sep 17 00:00:00 2001 From: Ned Petrov Date: Tue, 25 Aug 2026 10:08:13 +0300 Subject: [PATCH 12/25] ci: rename deploy task to deploy-director; hide credentials from bosh int output Rename the bats deploy task label from 'deploy' to 'deploy-director' for clarity in the Concourse UI. Redirect bosh int output to a file instead of tee-ing it to stdout to prevent credentials appearing in build logs. --- ci/pipeline.yml | 2 +- ci/tasks/deploy-manual-networking.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ci/pipeline.yml b/ci/pipeline.yml index 1d4e2b41f..4cfad19d4 100644 --- a/ci/pipeline.yml +++ b/ci/pipeline.yml @@ -172,7 +172,7 @@ jobs: TF_VAR_openstack_default_key_public_key: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHspetaTfg96YfZO3/xjKxGcjTnFPlxp7uP1KDisvPjB devstack-ci-openstack - do: - - task: deploy + - task: deploy-director timeout: *timeouts-long file: bosh-openstack-cpi-release/ci/tasks/deploy-manual-networking.yml params: diff --git a/ci/tasks/deploy-manual-networking.sh b/ci/tasks/deploy-manual-networking.sh index bcebc287b..c470fc10a 100755 --- a/ci/tasks/deploy-manual-networking.sh +++ b/ci/tasks/deploy-manual-networking.sh @@ -105,7 +105,7 @@ bosh-go int ../bosh-deployment/bosh.yml \ -v openstack_username="${openstack_username}" \ -v openstack_write_timeout="${openstack_write_timeout}" \ "${maybe_load_custom_ca_file[@]}" \ - -v region=null | tee bosh.yml + -v region=null > bosh.yml echo "deploying BOSH..." bosh-go create-env bosh.yml \ From 129e589f738b3c63e359f8f684735918e19489a4 Mon Sep 17 00:00:00 2001 From: Ned Petrov Date: Tue, 25 Aug 2026 10:31:07 +0300 Subject: [PATCH 13/25] ci: fix shellcheck issues and improve utils.sh - Use bash indirect expansion (${!name}) instead of eval in optional_value - Quote all unquoted variable expansions (SC2086) - Separate local declarations from assignments (SC2155) - Replace backticks with $(...) (SC2006) - Use [ -n ] instead of [ ! -z ] - Replace cat | jq with jq file (UUOC) - Use export "name=val" pattern in export_terraform_variable - Remove dead wget+sha1 branch (old_bosh_with_registry_sha1 was never defined) --- ci/tasks/utils.sh | 51 +++++++++++++++++++++++++---------------------- 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/ci/tasks/utils.sh b/ci/tasks/utils.sh index e4a8a8669..9e8303c92 100644 --- a/ci/tasks/utils.sh +++ b/ci/tasks/utils.sh @@ -10,19 +10,20 @@ creds_path() { optional_value() { local name=$1 - local value=$(eval echo '$'$name) + local value + value="${!name}" if [ "$value" == 'replace-me' ] || [ "$value" == 'null' ]; then echo "unsetting optional environment variable $name" - unset $name + unset "$name" fi } init_openstack_cli_env(){ - : ${BOSH_OPENSTACK_AUTH_URL:?} - : ${BOSH_OPENSTACK_USERNAME:?} - : ${BOSH_OPENSTACK_API_KEY:?} - : ${BOSH_OPENSTACK_PROJECT:?} - : ${BOSH_OPENSTACK_DOMAIN_NAME:?} + : "${BOSH_OPENSTACK_AUTH_URL:?}" + : "${BOSH_OPENSTACK_USERNAME:?}" + : "${BOSH_OPENSTACK_API_KEY:?}" + : "${BOSH_OPENSTACK_PROJECT:?}" + : "${BOSH_OPENSTACK_DOMAIN_NAME:?}" optional_value BOSH_OPENSTACK_CA_CERT export OS_DEFAULT_DOMAIN=$BOSH_OPENSTACK_DOMAIN_NAME @@ -35,10 +36,10 @@ init_openstack_cli_env(){ export OS_INTERFACE=$BOSH_OPENSTACK_INTERFACE if [ -n "$BOSH_OPENSTACK_CA_CERT" ]; then - tmpdir=$(mktemp -dt "$(basename $0).XXXXXXXXXX") + tmpdir=$(mktemp -dt "$(basename "$0").XXXXXXXXXX") cacert="$tmpdir/cacert.pem" echo "Writing cacert.pem to $cacert" - echo "$BOSH_OPENSTACK_CA_CERT" > $cacert + echo "$BOSH_OPENSTACK_CA_CERT" > "$cacert" export OS_CACERT=$cacert fi @@ -51,38 +52,40 @@ prepare_bosh_release() { use_compiled_release=true - local s3_path_to_bosh_release=$(find_bosh_compiled_release ${distribution} ${bosh_release_version} ${stemcell_version}) + local s3_path_to_bosh_release + s3_path_to_bosh_release=$(find_bosh_compiled_release "${distribution}" "${bosh_release_version}" "${stemcell_version}") - if [ ! -z ${s3_path_to_bosh_release} ];then + if [ -n "${s3_path_to_bosh_release}" ]; then echo "Using compiled BOSH release: s3://bosh-compiled-release-tarballs/$s3_path_to_bosh_release" - aws --no-sign-request s3 cp s3://bosh-compiled-release-tarballs/${s3_path_to_bosh_release} ${deployment_dir}/bosh-release.tgz + # shellcheck disable=SC2154 + aws --no-sign-request s3 cp "s3://bosh-compiled-release-tarballs/${s3_path_to_bosh_release}" "${deployment_dir}/bosh-release.tgz" else use_compiled_release=false fi - if [ "${use_compiled_release}" = "false" ];then + if [ "${use_compiled_release}" = "false" ]; then echo "Using BOSH release from sources" - if [ -z ${bosh_release_version} ];then - cp ./bosh-release/*.tgz ${deployment_dir}/bosh-release.tgz - else - wget https://bosh.io/d/github.com/cloudfoundry/bosh?v=${bosh_release_version} -O ${deployment_dir}/bosh-release.tgz - echo "$old_bosh_with_registry_sha1 $deployment_dir/bosh-release.tgz" | sha1sum -c - - fi + # shellcheck disable=SC2154 + cp ./bosh-release/*.tgz "${deployment_dir}/bosh-release.tgz" fi } find_bosh_compiled_release(){ local distribution=$1 - local bosh_release_version=${2:-`cat ./bosh-release/version`} - local stemcell_version=${3:-`cat ./stemcell-director/version`} + local bosh_release_version=${2:-$(cat ./bosh-release/version)} + local stemcell_version=${3:-$(cat ./stemcell-director/version)} - local s3_path_to_bosh_release=`aws --no-sign-request s3 ls s3://bosh-compiled-release-tarballs | grep -oE "[^ ](\w|-)*$bosh_release_version.+$distribution.+$stemcell_version.*\.tgz" | sort -r | head -1` - echo ${s3_path_to_bosh_release} + local s3_path_to_bosh_release + s3_path_to_bosh_release=$(aws --no-sign-request s3 ls s3://bosh-compiled-release-tarballs | grep -oE "[^ ](\w|-)*$bosh_release_version.+$distribution.+$stemcell_version.*\.tgz" | sort -r | head -1) + echo "${s3_path_to_bosh_release}" } export_terraform_variable() { local metadata=$1 local variable_name=$2 local prefix=$3 - export ${prefix}${variable_name}=$(cat ${metadata} | jq -c --raw-output ".${variable_name}") + local varname="${prefix}${variable_name}" + local varval + varval=$(jq -c --raw-output ".${variable_name}" "${metadata}") + export "${varname}=${varval}" } From fca075aa181428c7b8ad87ff6bc42646bf8d40d8 Mon Sep 17 00:00:00 2001 From: Ned Petrov Date: Tue, 25 Aug 2026 12:07:16 +0300 Subject: [PATCH 14/25] ci: remove teardown-director and terraform-destroy from bats ensure block All OpenStack resources are ephemeral within the DevStack VM, which is destroyed by destroy-devstack regardless of test outcome. The explicit cleanup steps are redundant and add failure surface area. --- ci/pipeline.yml | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/ci/pipeline.yml b/ci/pipeline.yml index 4cfad19d4..1c444fb0d 100644 --- a/ci/pipeline.yml +++ b/ci/pipeline.yml @@ -203,17 +203,6 @@ jobs: task: print-task-errors timeout: *timeouts-long file: bosh-openstack-cpi-release/ci/tasks/print_task_errors.yml - ensure: - do: - - task: teardown-director - timeout: *timeouts-long - file: bosh-openstack-cpi-release/ci/tasks/teardown-director.yml - ensure: - task: terraform-destroy - timeout: *timeouts-long - image: terraform-image - file: bosh-openstack-cpi-release/ci/tasks/terraform-destroy-bats-manual.yml - params: *bats-terraform-vars - name: bump-major serial_groups: [ version ] From 75c194adfad98bc8734098cdadef386d77b3f970 Mon Sep 17 00:00:00 2001 From: Ned Petrov Date: Tue, 25 Aug 2026 13:44:26 +0300 Subject: [PATCH 15/25] ci: fix destroy-devstack implicit get after terraform destroy Add get_params.action: destroy so the post-put get step handles a non-existent workspace instead of erroring. --- ci/pipeline.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ci/pipeline.yml b/ci/pipeline.yml index 1c444fb0d..a0b0de63d 100644 --- a/ci/pipeline.yml +++ b/ci/pipeline.yml @@ -57,6 +57,8 @@ jobs: params: terraform_source: bosh-openstack-cpi-release/ci/terraform/ci/gcp-devstack action: destroy + get_params: + action: destroy - name: lifecycle serial: true From 42afd7e59879e1e5f857292965654acbd0e1a5f4 Mon Sep 17 00:00:00 2001 From: Ned Petrov Date: Tue, 25 Aug 2026 15:06:54 +0300 Subject: [PATCH 16/25] ci: merge lifecycle+bats into run-devstack-tests with always-cleanup ensure Replace separate lifecycle, bats-ubuntu-manual, and destroy-devstack jobs with a single run-devstack-tests job. The outer do: block's ensure always destroys the DevStack VM (wrapped in try: to handle already-gone state), regardless of test success or failure. Also fix GIT_USER_EMAIL in bump-bosh-packages to bots@cloudfoundry.org. --- ci/pipeline.yml | 293 +++++++++++++++++++++++------------------------- 1 file changed, 138 insertions(+), 155 deletions(-) diff --git a/ci/pipeline.yml b/ci/pipeline.yml index a0b0de63d..aea77558f 100644 --- a/ci/pipeline.yml +++ b/ci/pipeline.yml @@ -46,165 +46,150 @@ jobs: OS_PASSWORD: ((openstack_ci_user.password)) OS_PROJECT: bosh -- name: destroy-devstack +- name: run-devstack-tests serial: true serial_groups: [ devstack ] plan: - - get: bosh-openstack-cpi-release - passed: [ create-devstack ] - trigger: true - - put: gcp-devstack - params: - terraform_source: bosh-openstack-cpi-release/ci/terraform/ci/gcp-devstack - action: destroy - get_params: - action: destroy - -- name: lifecycle - serial: true - serial_groups: [ devstack, cleanup-lock-lifecycle ] - plan: - - in_parallel: - - get: bosh-openstack-cpi-release - passed: [ create-devstack ] - timeout: *timeouts-long - trigger: true - - get: openstack-lifecycle-stemcell - resource: openstack-ubuntu-jammy-stemcell - timeout: *timeouts-long - - get: terraform-image - - get: openstack-cpi-release-docker-image - - - task: terraform-apply - timeout: *timeouts-long - image: terraform-image - file: bosh-openstack-cpi-release/ci/tasks/terraform-apply-lifecycle.yml - params: &lifecycle-terraform-params - TF_VAR_prefix: "lifecycle-openstack-tests" - TF_VAR_auth_url: http://10.100.30.2/identity - TF_VAR_user_name: ((openstack_ci_user.username)) - TF_VAR_password: ((openstack_ci_user.password)) - TF_VAR_domain_name: Default - TF_VAR_project_name: bosh - TF_VAR_ext_net_name: public - TF_VAR_region_name: RegionOne - TF_VAR_openstack_default_key_public_key: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHspetaTfg96YfZO3/xjKxGcjTnFPlxp7uP1KDisvPjB devstack-ci-openstack - TF_VAR_dns_nameservers: '["8.8.8.8"]' - TF_VAR_use_lbaas: false - - do: - - task: test - timeout: *timeouts-long - file: bosh-openstack-cpi-release/ci/tasks/run-lifecycle.yml - image: openstack-cpi-release-docker-image - params: - BOSH_OPENSTACK_DOMAIN: Default - BOSH_OPENSTACK_PROJECT: bosh - BOSH_OPENSTACK_USERNAME_V2: ((openstack_ci_user.username)) - BOSH_OPENSTACK_AUTH_URL_V3: http://10.100.30.2/identity - BOSH_OPENSTACK_USERNAME_V3: ((openstack_ci_user.username)) - BOSH_OPENSTACK_API_KEY_V3: ((openstack_ci_user.password)) - BOSH_CLI_SILENCE_SLOW_LOAD_WARNING: true - BOSH_OPENSTACK_VOLUME_TYPE: lvmdriver-1 - BOSH_OPENSTACK_CONNECT_TIMEOUT: "600" - BOSH_OPENSTACK_READ_TIMEOUT: "120" - BOSH_OPENSTACK_WRITE_TIMEOUT: "120" - BOSH_OPENSTACK_FLAVOR_WITH_NO_ROOT_DISK: no-root-disk - BOSH_OPENSTACK_AVAILABILITY_ZONE: nova - BOSH_OPENSTACK_EXCLUDE_CINDER_V1: true - - put: lifecycle-log - timeout: *timeouts-long - params: - file: output/lifecycle.log - ensure: - task: terraform-destroy - timeout: *timeouts-long - image: terraform-image - file: bosh-openstack-cpi-release/ci/tasks/terraform-destroy-lifecycle.yml - params: *lifecycle-terraform-params + - in_parallel: + - get: bosh-openstack-cpi-release + passed: [ create-devstack ] + timeout: *timeouts-long + trigger: true + - get: openstack-lifecycle-stemcell + resource: openstack-ubuntu-jammy-stemcell + timeout: *timeouts-long + - get: terraform-image + - get: openstack-cpi-release-docker-image + - get: stemcell-director + resource: openstack-ubuntu-jammy-stemcell + timeout: *timeouts-long + trigger: true + - get: stemcell + resource: openstack-ubuntu-jammy-stemcell + timeout: *timeouts-long + - get: bosh-release + timeout: *timeouts-long + - get: bats + timeout: *timeouts-long + - get: bosh-deployment + timeout: *timeouts-long -- name: bats-ubuntu-manual - serial: true - serial_groups: [ devstack, cleanup-lock-bats-ubuntu-manual ] - plan: - - in_parallel: - - get: terraform-image - - passed: [ create-devstack ] - get: bosh-openstack-cpi-release - timeout: *timeouts-long - trigger: true - - get: bosh-release - timeout: *timeouts-long - - get: stemcell-director - resource: openstack-ubuntu-jammy-stemcell - timeout: *timeouts-long - trigger: true - - get: stemcell - resource: openstack-ubuntu-jammy-stemcell - timeout: *timeouts-long - - get: bats + - task: lifecycle-terraform-apply timeout: *timeouts-long - - get: bosh-deployment - timeout: *timeouts-long - - - task: terraform-apply - timeout: *timeouts-long - image: terraform-image - file: bosh-openstack-cpi-release/ci/tasks/terraform-apply-bats-manual.yml - params: &bats-terraform-vars - TF_VAR_prefix: "bats-ubuntu-manual" - TF_VAR_auth_url: http://10.100.30.2/identity - TF_VAR_domain_name: Default - TF_VAR_user_name: ((openstack_ci_user.username)) - TF_VAR_password: ((openstack_ci_user.password)) - TF_VAR_project_name: bosh - TF_VAR_region_name: RegionOne - TF_VAR_primary_net_name: "bats-ubuntu-manual-primary" - TF_VAR_primary_net_cidr: "10.0.4.0/24" - TF_VAR_primary_net_allocation_pool_start: "10.0.4.200" - TF_VAR_primary_net_allocation_pool_end: "10.0.4.254" - TF_VAR_secondary_net_name: "bats-ubuntu-manual-secondary" - TF_VAR_secondary_net_cidr: "10.0.5.0/24" - TF_VAR_secondary_net_allocation_pool_start: "10.0.5.200" - TF_VAR_secondary_net_allocation_pool_end: "10.0.5.254" - TF_VAR_ext_net_name: public - TF_VAR_ext_net_cidr: 172.24.4.0/24 - TF_VAR_dns_nameservers: '["8.8.8.8"]' - TF_VAR_concourse_external_network_cidr: 0.0.0.0/0 - TF_VAR_openstack_default_key_public_key: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHspetaTfg96YfZO3/xjKxGcjTnFPlxp7uP1KDisvPjB devstack-ci-openstack + image: terraform-image + file: bosh-openstack-cpi-release/ci/tasks/terraform-apply-lifecycle.yml + params: &lifecycle-terraform-params + TF_VAR_prefix: "lifecycle-openstack-tests" + TF_VAR_auth_url: http://10.100.30.2/identity + TF_VAR_user_name: ((openstack_ci_user.username)) + TF_VAR_password: ((openstack_ci_user.password)) + TF_VAR_domain_name: Default + TF_VAR_project_name: bosh + TF_VAR_ext_net_name: public + TF_VAR_region_name: RegionOne + TF_VAR_openstack_default_key_public_key: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHspetaTfg96YfZO3/xjKxGcjTnFPlxp7uP1KDisvPjB devstack-ci-openstack + TF_VAR_dns_nameservers: '["8.8.8.8"]' + TF_VAR_use_lbaas: false + + - do: + - task: lifecycle-test + timeout: *timeouts-long + file: bosh-openstack-cpi-release/ci/tasks/run-lifecycle.yml + image: openstack-cpi-release-docker-image + params: + BOSH_OPENSTACK_DOMAIN: Default + BOSH_OPENSTACK_PROJECT: bosh + BOSH_OPENSTACK_USERNAME_V2: ((openstack_ci_user.username)) + BOSH_OPENSTACK_AUTH_URL_V3: http://10.100.30.2/identity + BOSH_OPENSTACK_USERNAME_V3: ((openstack_ci_user.username)) + BOSH_OPENSTACK_API_KEY_V3: ((openstack_ci_user.password)) + BOSH_CLI_SILENCE_SLOW_LOAD_WARNING: true + BOSH_OPENSTACK_VOLUME_TYPE: lvmdriver-1 + BOSH_OPENSTACK_CONNECT_TIMEOUT: "600" + BOSH_OPENSTACK_READ_TIMEOUT: "120" + BOSH_OPENSTACK_WRITE_TIMEOUT: "120" + BOSH_OPENSTACK_FLAVOR_WITH_NO_ROOT_DISK: no-root-disk + BOSH_OPENSTACK_AVAILABILITY_ZONE: nova + BOSH_OPENSTACK_EXCLUDE_CINDER_V1: true + - put: lifecycle-log + timeout: *timeouts-long + params: + file: output/lifecycle.log + ensure: + task: lifecycle-terraform-destroy + timeout: *timeouts-long + image: terraform-image + file: bosh-openstack-cpi-release/ci/tasks/terraform-destroy-lifecycle.yml + params: *lifecycle-terraform-params - - do: - - task: deploy-director - timeout: *timeouts-long - file: bosh-openstack-cpi-release/ci/tasks/deploy-manual-networking.yml - params: - openstack_flavor: "m1.small" - openstack_connection_timeout: "600" - openstack_read_timeout: "180" - openstack_write_timeout: "180" - openstack_state_timeout: "900" - openstack_auth_url: http://10.100.30.2/identity - openstack_username: ((openstack_ci_user.username)) - openstack_api_key: ((openstack_ci_user.password)) - openstack_domain: Default - distro: "ubuntu" - availability_zone: nova - DEBUG_BATS: *debug_bats - - - task: test + - task: bats-terraform-apply timeout: *timeouts-long - file: bosh-openstack-cpi-release/ci/tasks/run-manual-networking-bats.yml - params: - stemcell_name: "bosh-openstack-kvm-ubuntu-jammy-go_agent" - openstack_flavor_with_ephemeral_disk: with-ephemeral-disk - openstack_flavor_with_no_ephemeral_disk: without-ephemeral-disk - availability_zone: nova - DEBUG_BATS: *debug_bats - bats_rspec_tags: "--tag ~ssh" - ensure: - task: print-task-errors + image: terraform-image + file: bosh-openstack-cpi-release/ci/tasks/terraform-apply-bats-manual.yml + params: &bats-terraform-vars + TF_VAR_prefix: "bats-ubuntu-manual" + TF_VAR_auth_url: http://10.100.30.2/identity + TF_VAR_domain_name: Default + TF_VAR_user_name: ((openstack_ci_user.username)) + TF_VAR_password: ((openstack_ci_user.password)) + TF_VAR_project_name: bosh + TF_VAR_region_name: RegionOne + TF_VAR_primary_net_name: "bats-ubuntu-manual-primary" + TF_VAR_primary_net_cidr: "10.0.4.0/24" + TF_VAR_primary_net_allocation_pool_start: "10.0.4.200" + TF_VAR_primary_net_allocation_pool_end: "10.0.4.254" + TF_VAR_secondary_net_name: "bats-ubuntu-manual-secondary" + TF_VAR_secondary_net_cidr: "10.0.5.0/24" + TF_VAR_secondary_net_allocation_pool_start: "10.0.5.200" + TF_VAR_secondary_net_allocation_pool_end: "10.0.5.254" + TF_VAR_ext_net_name: public + TF_VAR_ext_net_cidr: 172.24.4.0/24 + TF_VAR_dns_nameservers: '["8.8.8.8"]' + TF_VAR_concourse_external_network_cidr: 0.0.0.0/0 + TF_VAR_openstack_default_key_public_key: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHspetaTfg96YfZO3/xjKxGcjTnFPlxp7uP1KDisvPjB devstack-ci-openstack + + - do: + - task: bats-deploy-director timeout: *timeouts-long - file: bosh-openstack-cpi-release/ci/tasks/print_task_errors.yml + file: bosh-openstack-cpi-release/ci/tasks/deploy-manual-networking.yml + params: + openstack_flavor: "m1.small" + openstack_connection_timeout: "600" + openstack_read_timeout: "180" + openstack_write_timeout: "180" + openstack_state_timeout: "900" + openstack_auth_url: http://10.100.30.2/identity + openstack_username: ((openstack_ci_user.username)) + openstack_api_key: ((openstack_ci_user.password)) + openstack_domain: Default + distro: "ubuntu" + availability_zone: nova + DEBUG_BATS: *debug_bats + + - task: bats-test + timeout: *timeouts-long + file: bosh-openstack-cpi-release/ci/tasks/run-manual-networking-bats.yml + params: + stemcell_name: "bosh-openstack-kvm-ubuntu-jammy-go_agent" + openstack_flavor_with_ephemeral_disk: with-ephemeral-disk + openstack_flavor_with_no_ephemeral_disk: without-ephemeral-disk + availability_zone: nova + DEBUG_BATS: *debug_bats + bats_rspec_tags: "--tag ~ssh" + ensure: + task: print-task-errors + timeout: *timeouts-long + file: bosh-openstack-cpi-release/ci/tasks/print_task_errors.yml + + ensure: + try: + put: gcp-devstack + params: + terraform_source: bosh-openstack-cpi-release/ci/terraform/ci/gcp-devstack + action: destroy + get_params: + action: destroy - name: bump-major serial_groups: [ version ] @@ -234,8 +219,7 @@ jobs: - get: bosh-openstack-cpi-release trigger: true passed: - - lifecycle - - bats-ubuntu-manual + - run-devstack-tests - get: bosh-shared-ci - get: version resource: release-version-semver @@ -298,8 +282,7 @@ jobs: - get: bosh-shared-ci - get: bosh-openstack-cpi-release passed: - - lifecycle - - bats-ubuntu-manual + - run-devstack-tests - get: version resource: release-version-semver trigger: true @@ -422,7 +405,7 @@ jobs: bosh-release: bosh-openstack-cpi-release params: GIT_USER_NAME: CI Bot - GIT_USER_EMAIL: cf-bosh-eng@pivotal.io + GIT_USER_EMAIL: bots@cloudfoundry.org PACKAGE: ruby-3.1 PACKAGE_PREFIX: "openstack" PRIVATE_YML: | From 72c5e5f1950b4e9a3e7080c5f848a89eada1772d Mon Sep 17 00:00:00 2001 From: Ned Petrov Date: Tue, 25 Aug 2026 16:16:00 +0300 Subject: [PATCH 17/25] fix cpi_logging_spec: drop port requirement from response regex Excon 1.2.8 no longer includes host:port in response log lines, so :\d+ never matches. Remove it; the path-version pattern is sufficient to identify the right log line. --- src/bosh_openstack_cpi/spec/integration/cpi_logging_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bosh_openstack_cpi/spec/integration/cpi_logging_spec.rb b/src/bosh_openstack_cpi/spec/integration/cpi_logging_spec.rb index 903d2b2ed..f2ccf8772 100644 --- a/src/bosh_openstack_cpi/spec/integration/cpi_logging_spec.rb +++ b/src/bosh_openstack_cpi/spec/integration/cpi_logging_spec.rb @@ -26,7 +26,7 @@ ) expect(log.string).to match(%r{excon\.request GET https?://.*:\d+(?:/\w+)*/v\d\.\d/}) - expect(log.string).to match(%r{excon\.response HTTP/.*:\d+(?:/\w+)*/v\d\.\d/}) + expect(log.string).to match(%r{excon\.response HTTP/.*(?:/\w+)*/v\d\.\d/}) end it 'logs excon exceptions' do From 004b2c21f88bad751710227261409261abe0e469 Mon Sep 17 00:00:00 2001 From: Ned Petrov Date: Tue, 25 Aug 2026 17:20:13 +0300 Subject: [PATCH 18/25] ci: clean up scripts, fix shellcheck issues, reduce log noise - Merge run-devstack-tests task names to be more descriptive - Fix shellcheck issues in run-lifecycle.sh (SC2086, SC2155, SC2157 bug) - Suppress fog warnings and raise log level to INFO in integration specs - Remove set -x from deploy-manual-networking.sh - Remove POC mentions, timing estimates, internal references from install-devstack.sh, main.tf, and wait-for-devstack.sh --- ci/pipeline.yml | 15 ++-- ci/tasks/deploy-manual-networking.sh | 2 +- ci/tasks/run-lifecycle.sh | 74 +++++++++++-------- ci/tasks/wait-for-devstack.sh | 2 +- .../ci/gcp-devstack/install-devstack.sh | 44 +++-------- ci/terraform/ci/gcp-devstack/main.tf | 8 +- .../spec/integration/spec_helper.rb | 2 + .../integration/support/integration_config.rb | 1 + 8 files changed, 72 insertions(+), 76 deletions(-) diff --git a/ci/pipeline.yml b/ci/pipeline.yml index aea77558f..875ab2c30 100644 --- a/ci/pipeline.yml +++ b/ci/pipeline.yml @@ -75,7 +75,7 @@ jobs: - get: bosh-deployment timeout: *timeouts-long - - task: lifecycle-terraform-apply + - task: lifecycle-apply-terraform timeout: *timeouts-long image: terraform-image file: bosh-openstack-cpi-release/ci/tasks/terraform-apply-lifecycle.yml @@ -93,7 +93,7 @@ jobs: TF_VAR_use_lbaas: false - do: - - task: lifecycle-test + - task: run-lifecycle timeout: *timeouts-long file: bosh-openstack-cpi-release/ci/tasks/run-lifecycle.yml image: openstack-cpi-release-docker-image @@ -117,13 +117,13 @@ jobs: params: file: output/lifecycle.log ensure: - task: lifecycle-terraform-destroy + task: lifecycle-destroy-terraform timeout: *timeouts-long image: terraform-image file: bosh-openstack-cpi-release/ci/tasks/terraform-destroy-lifecycle.yml params: *lifecycle-terraform-params - - task: bats-terraform-apply + - task: bats-apply-terraform timeout: *timeouts-long image: terraform-image file: bosh-openstack-cpi-release/ci/tasks/terraform-apply-bats-manual.yml @@ -167,7 +167,7 @@ jobs: availability_zone: nova DEBUG_BATS: *debug_bats - - task: bats-test + - task: run-bats timeout: *timeouts-long file: bosh-openstack-cpi-release/ci/tasks/run-manual-networking-bats.yml params: @@ -178,13 +178,14 @@ jobs: DEBUG_BATS: *debug_bats bats_rspec_tags: "--tag ~ssh" ensure: - task: print-task-errors + task: bats-print-errors timeout: *timeouts-long file: bosh-openstack-cpi-release/ci/tasks/print_task_errors.yml ensure: try: - put: gcp-devstack + put: destroy-devstack + resource: gcp-devstack params: terraform_source: bosh-openstack-cpi-release/ci/terraform/ci/gcp-devstack action: destroy diff --git a/ci/tasks/deploy-manual-networking.sh b/ci/tasks/deploy-manual-networking.sh index c470fc10a..b3dea494b 100755 --- a/ci/tasks/deploy-manual-networking.sh +++ b/ci/tasks/deploy-manual-networking.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -set -ex +set -eo pipefail # shellcheck disable=SC1091 source bosh-openstack-cpi-release/ci/tasks/utils.sh diff --git a/ci/tasks/run-lifecycle.sh b/ci/tasks/run-lifecycle.sh index d8fa017d5..75175acf6 100755 --- a/ci/tasks/run-lifecycle.sh +++ b/ci/tasks/run-lifecycle.sh @@ -4,39 +4,50 @@ set -euo pipefail source bosh-openstack-cpi-release/ci/tasks/utils.sh -: ${BOSH_OPENSTACK_DOMAIN:?} -: ${BOSH_OPENSTACK_AUTH_URL_V3:?} -: ${BOSH_OPENSTACK_USERNAME_V3:?} -: ${BOSH_OPENSTACK_API_KEY_V3:?} -: ${BOSH_OPENSTACK_PROJECT:?} -: ${BOSH_CLI_SILENCE_SLOW_LOAD_WARNING:?} -: ${BOSH_OPENSTACK_CONNECT_TIMEOUT:?} -: ${BOSH_OPENSTACK_READ_TIMEOUT:?} -: ${BOSH_OPENSTACK_WRITE_TIMEOUT:?} -: ${BOSH_OPENSTACK_FLAVOR_WITH_NO_ROOT_DISK:?} -: ${BOSH_OPENSTACK_EXCLUDE_CINDER_V1:-""} -: ${BOSH_OPENSTACK_AUTH_URL_V2:-""} -: ${BOSH_OPENSTACK_USERNAME_V2:-""} -: ${BOSH_OPENSTACK_API_KEY_V2:-""} -: ${BOSH_OPENSTACK_CA_CERT:-""} -: ${BOSH_OPENSTACK_VOLUME_TYPE:-""} +: "${BOSH_OPENSTACK_DOMAIN:?}" +: "${BOSH_OPENSTACK_AUTH_URL_V3:?}" +: "${BOSH_OPENSTACK_USERNAME_V3:?}" +: "${BOSH_OPENSTACK_API_KEY_V3:?}" +: "${BOSH_OPENSTACK_PROJECT:?}" +: "${BOSH_CLI_SILENCE_SLOW_LOAD_WARNING:?}" +: "${BOSH_OPENSTACK_CONNECT_TIMEOUT:?}" +: "${BOSH_OPENSTACK_READ_TIMEOUT:?}" +: "${BOSH_OPENSTACK_WRITE_TIMEOUT:?}" +: "${BOSH_OPENSTACK_FLAVOR_WITH_NO_ROOT_DISK:?}" +: "${BOSH_OPENSTACK_EXCLUDE_CINDER_V1:-""}" +: "${BOSH_OPENSTACK_AUTH_URL_V2:-""}" +: "${BOSH_OPENSTACK_USERNAME_V2:-""}" +: "${BOSH_OPENSTACK_API_KEY_V2:-""}" +: "${BOSH_OPENSTACK_CA_CERT:-""}" +: "${BOSH_OPENSTACK_VOLUME_TYPE:-""}" optional_value BOSH_OPENSTACK_AVAILABILITY_ZONE metadata=terraform-cpi/metadata -export BOSH_OPENSTACK_MANUAL_IP=$(cat ${metadata} | jq --raw-output ".manual_ip") -export BOSH_OPENSTACK_ALLOWED_ADDRESS_PAIRS=$(cat ${metadata} | jq --raw-output ".allowed_address_pairs") -export BOSH_OPENSTACK_NO_DHCP_MANUAL_IP_1=$(cat ${metadata} | jq --raw-output ".no_dhcp_manual_ip_1") -export BOSH_OPENSTACK_NO_DHCP_MANUAL_IP_2=$(cat ${metadata} | jq --raw-output ".no_dhcp_manual_ip_2") -export BOSH_OPENSTACK_NET_ID=$(cat ${metadata} | jq --raw-output ".net_id") -export BOSH_OPENSTACK_NET_ID_NO_DHCP_1=$(cat ${metadata} | jq --raw-output ".net_id_no_dhcp_1") -export BOSH_OPENSTACK_NET_ID_NO_DHCP_2=$(cat ${metadata} | jq --raw-output ".net_id_no_dhcp_2") -export BOSH_OPENSTACK_DEFAULT_KEY_NAME=$(cat ${metadata} | jq --raw-output ".default_key_name") -export BOSH_OPENSTACK_FLOATING_IP=$(cat ${metadata} | jq --raw-output ".floating_ip") -export BOSH_OPENSTACK_SECURITY_GROUP_NAME=$(cat ${metadata} | jq --raw-output ".security_group_name") -export BOSH_OPENSTACK_SECURITY_GROUP_ID=$(cat ${metadata} | jq --raw-output ".security_group_id") -pool_name=$(cat ${metadata} | jq --raw-output ".loadbalancer_pool_name") +BOSH_OPENSTACK_MANUAL_IP=$(jq --raw-output ".manual_ip" "${metadata}") +export BOSH_OPENSTACK_MANUAL_IP +BOSH_OPENSTACK_ALLOWED_ADDRESS_PAIRS=$(jq --raw-output ".allowed_address_pairs" "${metadata}") +export BOSH_OPENSTACK_ALLOWED_ADDRESS_PAIRS +BOSH_OPENSTACK_NO_DHCP_MANUAL_IP_1=$(jq --raw-output ".no_dhcp_manual_ip_1" "${metadata}") +export BOSH_OPENSTACK_NO_DHCP_MANUAL_IP_1 +BOSH_OPENSTACK_NO_DHCP_MANUAL_IP_2=$(jq --raw-output ".no_dhcp_manual_ip_2" "${metadata}") +export BOSH_OPENSTACK_NO_DHCP_MANUAL_IP_2 +BOSH_OPENSTACK_NET_ID=$(jq --raw-output ".net_id" "${metadata}") +export BOSH_OPENSTACK_NET_ID +BOSH_OPENSTACK_NET_ID_NO_DHCP_1=$(jq --raw-output ".net_id_no_dhcp_1" "${metadata}") +export BOSH_OPENSTACK_NET_ID_NO_DHCP_1 +BOSH_OPENSTACK_NET_ID_NO_DHCP_2=$(jq --raw-output ".net_id_no_dhcp_2" "${metadata}") +export BOSH_OPENSTACK_NET_ID_NO_DHCP_2 +BOSH_OPENSTACK_DEFAULT_KEY_NAME=$(jq --raw-output ".default_key_name" "${metadata}") +export BOSH_OPENSTACK_DEFAULT_KEY_NAME +BOSH_OPENSTACK_FLOATING_IP=$(jq --raw-output ".floating_ip" "${metadata}") +export BOSH_OPENSTACK_FLOATING_IP +BOSH_OPENSTACK_SECURITY_GROUP_NAME=$(jq --raw-output ".security_group_name" "${metadata}") +export BOSH_OPENSTACK_SECURITY_GROUP_NAME +BOSH_OPENSTACK_SECURITY_GROUP_ID=$(jq --raw-output ".security_group_id" "${metadata}") +export BOSH_OPENSTACK_SECURITY_GROUP_ID +pool_name=$(jq --raw-output ".loadbalancer_pool_name" "${metadata}") if [ "${pool_name}" != "" ]; then export BOSH_OPENSTACK_LBAAS_POOL_NAME=${pool_name} fi @@ -49,14 +60,15 @@ cd bosh-openstack-cpi-release/src/bosh_openstack_cpi bundle install -if [ -n "BOSH_OPENSTACK_EXCLUDE_CINDER_V1" ]; then +rspec_args="" +if [ -n "${BOSH_OPENSTACK_EXCLUDE_CINDER_V1}" ]; then rspec_args="--tag ~cinder_v1" fi if [ -n "${BOSH_OPENSTACK_AUTH_URL_V2}" ]; then - bundle exec rspec -f d $rspec_args spec/integration 2>&1 | tee ../../../output/lifecycle.log + bundle exec rspec -f d "${rspec_args}" spec/integration 2>&1 | tee ../../../output/lifecycle.log else echo "Excluding Keystone V2 tests." - bundle exec rspec -f d $rspec_args spec/integration --exclude-pattern spec/integration/lifecycle_v2_spec.rb 2>&1 | tee ../../../output/lifecycle.log + bundle exec rspec -f d "${rspec_args}" spec/integration --exclude-pattern spec/integration/lifecycle_v2_spec.rb 2>&1 | tee ../../../output/lifecycle.log fi diff --git a/ci/tasks/wait-for-devstack.sh b/ci/tasks/wait-for-devstack.sh index 54895ffa5..d0f31283f 100755 --- a/ci/tasks/wait-for-devstack.sh +++ b/ci/tasks/wait-for-devstack.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash # Polls the DevStack Keystone endpoint until it answers, so downstream jobs don't start against a -# half-installed cloud. DevStack is installed on the VM via its startup-script (~30-40 min from scratch). +# half-installed cloud. set -euo pipefail diff --git a/ci/terraform/ci/gcp-devstack/install-devstack.sh b/ci/terraform/ci/gcp-devstack/install-devstack.sh index 10b84bca2..a4100f5de 100644 --- a/ci/terraform/ci/gcp-devstack/install-devstack.sh +++ b/ci/terraform/ci/gcp-devstack/install-devstack.sh @@ -1,33 +1,28 @@ #!/usr/bin/env bash -# Provisions a self-contained DevStack OpenStack on a fresh Ubuntu 24.04 (Noble) GCP VM, -# configured deterministically so the community Concourse pipeline's static CredHub values -# (concourse_openstack_auth / config-json) resolve against it. Runs AS the VM, as a sudo-capable -# non-root user. Intended to be invoked by ci/pipeline.yml's create-devstack-worker job. -# -# The worker-registration step (join community Concourse tagged `openstack`) is a separate concern; -# see start-concourse-worker.sh which this script calls at the end if worker creds are provided. +# Provisions a self-contained DevStack OpenStack on a fresh Ubuntu 24.04 (Noble) GCP VM. +# Runs as a sudo-capable non-root user. Invoked by the create-devstack CI task. set -euo pipefail -### ----- Tunables (override via env from the create-devstack-worker task) ----------------------- -DEVSTACK_BRANCH="${DEVSTACK_BRANCH:-stable/2025.1}" # Epoxy; matches the validated POC +### ----- Tunables (can be overridden via environment variables) --------------------------------- +DEVSTACK_BRANCH="${DEVSTACK_BRANCH:-stable/2025.1}" # Epoxy STACK_USER="${STACK_USER:-stack}" STACK_DIR="${STACK_DIR:-/opt/stack}" -# Deterministic OpenStack identity. These MUST match the CredHub concourse_openstack_auth/config-json. +# OpenStack identity defaults — override via environment or VM metadata. OS_PROJECT="${OS_PROJECT:-bosh}" OS_USERNAME="${OS_USERNAME:-bosh}" OS_PASSWORD="${OS_PASSWORD:-bosh-ci-password}" OS_DOMAIN="${OS_DOMAIN:-Default}" OS_REGION="${OS_REGION:-RegionOne}" -# HOST_IP is pinned so auth_url is stable across per-run VMs. create-vm.sh should assign this as the -# VM's static internal IP (gcloud --private-network-ip). Falls back to the primary NIC address. +# HOST_IP is pinned so auth_url is stable across per-run VMs. Should be set to the VM's static +# internal IP. Falls back to the primary NIC address. HOST_IP="${HOST_IP:-$(ip -4 route get 1.1.1.1 | awk '{print $7; exit}')}" # DevStack external ("public") network. DevStack always regenerates the neutron network UUID, so -# downstream terraform must resolve it BY NAME (see note in the pipeline plan). We only pin name/CIDR. +# downstream terraform resolves it by name. Only name and CIDR are pinned here. EXT_NET_NAME="${EXT_NET_NAME:-public}" FLOATING_RANGE="${FLOATING_RANGE:-172.24.4.0/24}" PUBLIC_NETWORK_GATEWAY="${PUBLIC_NETWORK_GATEWAY:-172.24.4.1}" @@ -53,19 +48,6 @@ load_credentials() { p="$(metadata_attr os-password)"; [ -n "$p" ] && OS_PASSWORD="$p" } -require_nested_kvm() { - # POC: N2D (AMD) in europe-west4 does not expose SVM to the guest, so nested virt silently fails. - # Use N2 (Intel). Fail fast here rather than deep inside a BATS deploy. - if [[ ! -e /dev/kvm ]]; then - log "ERROR: /dev/kvm missing — VM has no nested virtualization. Use an N2 (Intel) machine type." - exit 1 - fi - if ! grep -Eq 'vmx|svm' /proc/cpuinfo; then - log "ERROR: no vmx/svm CPU flag exposed to guest — nested KVM will not work. Use N2 (Intel)." - exit 1 - fi -} - install_prereqs() { export DEBIAN_FRONTEND=noninteractive sudo apt-get update -y @@ -125,14 +107,14 @@ os_admin() { configure_offbox_floating_access() { # Let Concourse workers (off the VM) reach DevStack floating IPs. Traffic is routed here by a GCP VPC # route (dest=floating_range, next-hop=this VM) + can_ip_forward. OVN's external network drops - # off-subnet sources, so hairpin-SNAT the worker source to the br-ex gateway IP. Validated in BOSH-1744. + # off-subnet sources, so hairpin-SNAT the worker source to the br-ex gateway IP. sudo sysctl -w net.ipv4.ip_forward=1 sudo iptables -t nat -C POSTROUTING -s "${WORKER_SOURCE_CIDR}" -d "${FLOATING_RANGE}" -j SNAT --to-source "${PUBLIC_NETWORK_GATEWAY}" 2>/dev/null || \ sudo iptables -t nat -A POSTROUTING -s "${WORKER_SOURCE_CIDR}" -d "${FLOATING_RANGE}" -j SNAT --to-source "${PUBLIC_NETWORK_GATEWAY}" } register_volumev3_alias() { - # POC: Cinder registers as service type `block-storage`; the fog-openstack gem the CPI/tests use + # Cinder registers as service type `block-storage`; the fog-openstack gem the CPI/tests use # expects a `volumev3` service. Register an alias endpoint pointing at the same Cinder URL. local cinder_url cinder_url="$(os_admin endpoint list --service block-storage --interface public -f value -c URL | head -1)" @@ -155,8 +137,7 @@ create_project_and_user() { } create_flavors() { - # Test flavors referenced by config-json. Names must match the CredHub values. - # with_no_root_disk: root disk = 0 (boot-from-volume path). with/without ephemeral disk vary swap/ephemeral. + # Test flavors required by the lifecycle and BATS suites. os_admin flavor show m1.small >/dev/null 2>&1 || os_admin flavor create --ram 2048 --disk 20 --vcpus 1 m1.small os_admin flavor show no-root-disk >/dev/null 2>&1 || \ os_admin flavor create --ram 1024 --disk 0 --vcpus 1 no-root-disk @@ -205,7 +186,6 @@ EOF } main() { - require_nested_kvm install_prereqs load_credentials clone_devstack @@ -218,7 +198,7 @@ main() { bump_quotas upload_jammy_stemcell emit_metadata - log "DevStack ready. Start the Concourse worker next (start-concourse-worker.sh)." + log "DevStack ready." } main "$@" diff --git a/ci/terraform/ci/gcp-devstack/main.tf b/ci/terraform/ci/gcp-devstack/main.tf index 53639c7b7..506dbd9ed 100644 --- a/ci/terraform/ci/gcp-devstack/main.tf +++ b/ci/terraform/ci/gcp-devstack/main.tf @@ -1,7 +1,7 @@ # GCP-side infrastructure for the OpenStack CPI test environment: an N2 VM that runs DevStack, # attached to the community Concourse VPC (bosh-concourse) so the Concourse workers can reach it. # -# Two reachability paths for the workers (both validated): +# Two reachability paths for the workers: # 1. DevStack API -> the VM's internal IP (allowed by google_compute_firewall.devstack). # 2. Floating IPs -> routed to the VM (google_compute_route.floating) + can_ip_forward, plus a # SNAT hairpin rule applied by install-devstack.sh so OVN accepts the off-subnet worker source. @@ -51,7 +51,7 @@ variable "vm_name" { } variable "machine_type" { - description = "MUST be a nested-virt-capable Intel type. NOT E2 (worker type) and NOT N2D (no vmx in europe-west4 per POC)." + description = "Must be a nested-virt-capable Intel type (e.g. N2). NOT E2 and NOT N2D (no vmx exposed)." default = "n2-standard-4" } @@ -68,7 +68,7 @@ variable "image" { } variable "private_ip" { - description = "Pinned internal IP so concourse_openstack_auth.auth_url stays stable across per-run VMs." + description = "Pinned internal IP so the OpenStack auth_url stays stable across per-run VMs." type = string default = "10.100.30.2" } @@ -95,7 +95,7 @@ variable "os_username" { } variable "os_password" { - description = "Password for os_username. Sourced from CredHub (openstack_ci_user) via the pipeline; passed to the VM as metadata, never committed." + description = "Password for os_username. Passed to the VM as metadata, never committed to the repo." type = string sensitive = true } diff --git a/src/bosh_openstack_cpi/spec/integration/spec_helper.rb b/src/bosh_openstack_cpi/spec/integration/spec_helper.rb index 253c4700a..ffde55d20 100644 --- a/src/bosh_openstack_cpi/spec/integration/spec_helper.rb +++ b/src/bosh_openstack_cpi/spec/integration/spec_helper.rb @@ -12,6 +12,8 @@ config.before(:all) { WebMock.allow_net_connect! } end +Fog::Logger[:warning] = nil + def upload_stemcell(cpi, stemcell_path) stemcell_manifest = Psych.load_file(File.join(stemcell_path, 'stemcell.MF')) stemcell_id = cpi.create_stemcell(File.join(stemcell_path, 'image'), stemcell_manifest['cloud_properties']) diff --git a/src/bosh_openstack_cpi/spec/integration/support/integration_config.rb b/src/bosh_openstack_cpi/spec/integration/support/integration_config.rb index 096969fa5..e17fcb5ae 100644 --- a/src/bosh_openstack_cpi/spec/integration/support/integration_config.rb +++ b/src/bosh_openstack_cpi/spec/integration/support/integration_config.rb @@ -45,6 +45,7 @@ def initialize(identity_version = :v3) end @logger = Bosh::Cpi::Logger.new(STDERR) + @logger.level = ::Logger::INFO @logger.set_request_id("fake-#{Random.rand(100000..999999)}") @ca_cert_content = LifecycleHelper.get_config(:ca_cert, nil) From b8e1f0bf93abff592be6623fa4bc61b79ace6ec5 Mon Sep 17 00:00:00 2001 From: Ned Petrov Date: Tue, 25 Aug 2026 17:22:50 +0300 Subject: [PATCH 19/25] ci: restore fog warning logger in integration specs --- src/bosh_openstack_cpi/spec/integration/spec_helper.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/bosh_openstack_cpi/spec/integration/spec_helper.rb b/src/bosh_openstack_cpi/spec/integration/spec_helper.rb index ffde55d20..253c4700a 100644 --- a/src/bosh_openstack_cpi/spec/integration/spec_helper.rb +++ b/src/bosh_openstack_cpi/spec/integration/spec_helper.rb @@ -12,8 +12,6 @@ config.before(:all) { WebMock.allow_net_connect! } end -Fog::Logger[:warning] = nil - def upload_stemcell(cpi, stemcell_path) stemcell_manifest = Psych.load_file(File.join(stemcell_path, 'stemcell.MF')) stemcell_id = cpi.create_stemcell(File.join(stemcell_path, 'image'), stemcell_manifest['cloud_properties']) From ca5bd1707e876c76850db26cc2fd6a387157310b Mon Sep 17 00:00:00 2001 From: Ned Petrov Date: Tue, 25 Aug 2026 19:47:33 +0300 Subject: [PATCH 20/25] ci: fix rspec_args splitting by using array instead of string --- ci/tasks/run-lifecycle.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ci/tasks/run-lifecycle.sh b/ci/tasks/run-lifecycle.sh index 75175acf6..182152e48 100755 --- a/ci/tasks/run-lifecycle.sh +++ b/ci/tasks/run-lifecycle.sh @@ -60,15 +60,15 @@ cd bosh-openstack-cpi-release/src/bosh_openstack_cpi bundle install -rspec_args="" +rspec_args=() if [ -n "${BOSH_OPENSTACK_EXCLUDE_CINDER_V1}" ]; then - rspec_args="--tag ~cinder_v1" + rspec_args=(--tag ~cinder_v1) fi if [ -n "${BOSH_OPENSTACK_AUTH_URL_V2}" ]; then - bundle exec rspec -f d "${rspec_args}" spec/integration 2>&1 | tee ../../../output/lifecycle.log + bundle exec rspec -f d "${rspec_args[@]}" spec/integration 2>&1 | tee ../../../output/lifecycle.log else echo "Excluding Keystone V2 tests." - bundle exec rspec -f d "${rspec_args}" spec/integration --exclude-pattern spec/integration/lifecycle_v2_spec.rb 2>&1 | tee ../../../output/lifecycle.log + bundle exec rspec -f d "${rspec_args[@]}" spec/integration --exclude-pattern spec/integration/lifecycle_v2_spec.rb 2>&1 | tee ../../../output/lifecycle.log fi From 065082e92fc70b28771b26560c2596098d18c427 Mon Sep 17 00:00:00 2001 From: Ned Petrov Date: Wed, 26 Aug 2026 07:13:39 +0300 Subject: [PATCH 21/25] ci: point git resource back to master --- ci/pipeline.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/pipeline.yml b/ci/pipeline.yml index 875ab2c30..835876cf4 100644 --- a/ci/pipeline.yml +++ b/ci/pipeline.yml @@ -492,7 +492,7 @@ resources: type: git source: uri: https://github.com/cloudfoundry/bosh-openstack-cpi-release.git - branch: devstack-gcp-ci + branch: master username: bosh-admin-bot password: ((github_public_repo_token)) From 02770fa2b87885abfb35b6bcdb60e73c49e819ba Mon Sep 17 00:00:00 2001 From: Ned Petrov Date: Wed, 26 Aug 2026 08:40:22 +0300 Subject: [PATCH 22/25] ci: collapse devstack provisioning into run-devstack-tests Move the gcp-devstack put and wait-for-devstack task from the former create-devstack job into the run-devstack-tests do block, so provisioning, tests, and teardown are always part of the same build. The outer ensure now covers a leaked VM on provisioning failure too. Remove the try wrapper from the destroy ensure so a failed terraform destroy fails the job visibly rather than silently leaking the GCP VM. --- ci/pipeline.yml | 50 ++++++++++++++++++++----------------------------- 1 file changed, 20 insertions(+), 30 deletions(-) diff --git a/ci/pipeline.yml b/ci/pipeline.yml index 835876cf4..3c7d22664 100644 --- a/ci/pipeline.yml +++ b/ci/pipeline.yml @@ -25,27 +25,6 @@ jobs: file: bosh-openstack-cpi-release/ci/tasks/run-unit-specs.yml image: openstack-cpi-release-docker-image -- name: create-devstack - serial: true - serial_groups: [ devstack ] - plan: - - in_parallel: - - get: openstack-cpi-release-docker-image - - get: bosh-openstack-cpi-release - passed: [ run-specs ] - trigger: true - - put: gcp-devstack - params: - terraform_source: bosh-openstack-cpi-release/ci/terraform/ci/gcp-devstack - - task: wait-for-devstack - image: openstack-cpi-release-docker-image - file: bosh-openstack-cpi-release/ci/tasks/wait-for-devstack.yml - params: - AUTH_URL: http://10.100.30.2/identity - OS_USERNAME: ((openstack_ci_user.username)) - OS_PASSWORD: ((openstack_ci_user.password)) - OS_PROJECT: bosh - - name: run-devstack-tests serial: true serial_groups: [ devstack ] @@ -53,7 +32,7 @@ jobs: - do: - in_parallel: - get: bosh-openstack-cpi-release - passed: [ create-devstack ] + passed: [ run-specs ] timeout: *timeouts-long trigger: true - get: openstack-lifecycle-stemcell @@ -75,6 +54,18 @@ jobs: - get: bosh-deployment timeout: *timeouts-long + - put: gcp-devstack + params: + terraform_source: bosh-openstack-cpi-release/ci/terraform/ci/gcp-devstack + - task: wait-for-devstack + image: openstack-cpi-release-docker-image + file: bosh-openstack-cpi-release/ci/tasks/wait-for-devstack.yml + params: + AUTH_URL: http://10.100.30.2/identity + OS_USERNAME: ((openstack_ci_user.username)) + OS_PASSWORD: ((openstack_ci_user.password)) + OS_PROJECT: bosh + - task: lifecycle-apply-terraform timeout: *timeouts-long image: terraform-image @@ -183,14 +174,13 @@ jobs: file: bosh-openstack-cpi-release/ci/tasks/print_task_errors.yml ensure: - try: - put: destroy-devstack - resource: gcp-devstack - params: - terraform_source: bosh-openstack-cpi-release/ci/terraform/ci/gcp-devstack - action: destroy - get_params: - action: destroy + put: destroy-devstack + resource: gcp-devstack + params: + terraform_source: bosh-openstack-cpi-release/ci/terraform/ci/gcp-devstack + action: destroy + get_params: + action: destroy - name: bump-major serial_groups: [ version ] From e7c9df6af0ff18ce82af483891a67793a2fa4c44 Mon Sep 17 00:00:00 2001 From: Ned Petrov Date: Wed, 26 Aug 2026 08:42:30 +0300 Subject: [PATCH 23/25] terraform: set region on ext-network data sources --- ci/terraform/ci/bats-manual/bats.tf | 3 ++- ci/terraform/ci/lifecycle/lifecycle.tf | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/ci/terraform/ci/bats-manual/bats.tf b/ci/terraform/ci/bats-manual/bats.tf index 24e869b00..4c9640a08 100644 --- a/ci/terraform/ci/bats-manual/bats.tf +++ b/ci/terraform/ci/bats-manual/bats.tf @@ -9,7 +9,8 @@ provider "openstack" { } data "openstack_networking_network_v2" "ext" { - name = var.ext_net_name + name = var.ext_net_name + region = var.region_name } module "base" { diff --git a/ci/terraform/ci/lifecycle/lifecycle.tf b/ci/terraform/ci/lifecycle/lifecycle.tf index 76cc3f8b2..1ca3a1490 100644 --- a/ci/terraform/ci/lifecycle/lifecycle.tf +++ b/ci/terraform/ci/lifecycle/lifecycle.tf @@ -9,7 +9,8 @@ provider "openstack" { } data "openstack_networking_network_v2" "ext" { - name = var.ext_net_name + name = var.ext_net_name + region = var.region_name } module "base" { From c372b0c58ca2cad42f95d83576c801ace81f2115 Mon Sep 17 00:00:00 2001 From: Ned Petrov Date: Wed, 26 Aug 2026 08:44:50 +0300 Subject: [PATCH 24/25] ci: fix argument splitting and JSON injection in devstack scripts install-devstack.sh: pass os_admin args as bash -c positional parameters so arguments with spaces (e.g. passwords) are not word-split inside the -c string. wait-for-devstack.sh: build the Keystone auth payload with jq --arg so credential values are properly JSON-encoded regardless of their content. --- ci/tasks/wait-for-devstack.sh | 7 ++++++- ci/terraform/ci/gcp-devstack/install-devstack.sh | 3 ++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/ci/tasks/wait-for-devstack.sh b/ci/tasks/wait-for-devstack.sh index d0f31283f..1577700b7 100755 --- a/ci/tasks/wait-for-devstack.sh +++ b/ci/tasks/wait-for-devstack.sh @@ -27,10 +27,15 @@ done echo "waiting for CI user auth (confirms create_project_and_user has run) ..." for i in $(seq 1 20); do + payload=$(jq -n \ + --arg user "${OS_USERNAME}" \ + --arg pass "${OS_PASSWORD}" \ + --arg project "${OS_PROJECT}" \ + '{"auth":{"identity":{"methods":["password"],"password":{"user":{"name":$user,"domain":{"name":"Default"},"password":$pass}}},"scope":{"project":{"name":$project,"domain":{"name":"Default"}}}}}') code="$(curl -s -o /dev/null -m 10 -w '%{http_code}' \ -X POST "${AUTH_URL}/v3/auth/tokens" \ -H "Content-Type: application/json" \ - -d "{\"auth\":{\"identity\":{\"methods\":[\"password\"],\"password\":{\"user\":{\"name\":\"${OS_USERNAME}\",\"domain\":{\"name\":\"Default\"},\"password\":\"${OS_PASSWORD}\"}}},\"scope\":{\"project\":{\"name\":\"${OS_PROJECT}\",\"domain\":{\"name\":\"Default\"}}}}}" \ + -d "${payload}" \ || echo 000)" if [ "${code}" = "201" ]; then echo "CI user auth OK — DevStack fully ready" diff --git a/ci/terraform/ci/gcp-devstack/install-devstack.sh b/ci/terraform/ci/gcp-devstack/install-devstack.sh index a4100f5de..58b1b4c1a 100644 --- a/ci/terraform/ci/gcp-devstack/install-devstack.sh +++ b/ci/terraform/ci/gcp-devstack/install-devstack.sh @@ -101,7 +101,8 @@ run_stack() { os_admin() { # openstack CLI as admin against the freshly-installed cloud. sudo -u "${STACK_USER}" bash -c \ - "source ${STACK_DIR}/devstack/openrc admin admin >/dev/null 2>&1 && openstack $*" + "source ${STACK_DIR}/devstack/openrc admin admin >/dev/null 2>&1 && openstack \"\$@\"" \ + -- "$@" } configure_offbox_floating_access() { From de392e9e79da9e35f1b809eb8a771a0fa78da5f6 Mon Sep 17 00:00:00 2001 From: Ned Petrov Date: Wed, 26 Aug 2026 09:37:07 +0300 Subject: [PATCH 25/25] ci: fix devstack readiness signal and password shell-injection install-devstack.sh: reorder main() so create_project_and_user runs after create_flavors, bump_quotas, and upload_jammy_stemcell. CI-user auth success in wait-for-devstack now means all setup steps have completed, not just user creation. install-devstack.sh: shell-escape ADMIN_PASSWORD with printf %q before writing it into local.conf so passwords with $, backticks, or other metacharacters are not interpreted when stack.sh sources the file. --- ci/terraform/ci/gcp-devstack/install-devstack.sh | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ci/terraform/ci/gcp-devstack/install-devstack.sh b/ci/terraform/ci/gcp-devstack/install-devstack.sh index 58b1b4c1a..d6d1ff61a 100644 --- a/ci/terraform/ci/gcp-devstack/install-devstack.sh +++ b/ci/terraform/ci/gcp-devstack/install-devstack.sh @@ -69,12 +69,14 @@ clone_devstack() { write_local_conf() { # DevStack serves plain HTTP (no tls-proxy): this is a disposable test cloud reached only over the # bosh-concourse VPC, so no CA to juggle per run. + local admin_password + admin_password=$(printf '%q' "${OS_PASSWORD}") sudo -u "${STACK_USER}" tee "${STACK_DIR}/devstack/local.conf" >/dev/null <