Skip to content

Commit 399168b

Browse files
colinsaramprice
authored andcommitted
Add fly:bats rake task to run BATs from local branch via Concourse
Introduces `bundle exec rake fly:bats` (from src/) so developers can run the full BOSH Acceptance Test suite against their local branch without waiting for the shared CI pipeline. How it works ------------ The rake task sets a standalone Concourse pipeline (ci/fly-bats.yml), then triggers and streams the single `bats` job. The pipeline: 1. Checks out the current branch from GitHub (branch is pushed automatically before set-pipeline). 2. Runs make-candidate.yml to build a dev release tarball. 3. Compiles the tarball via bosh-deployment/ci/tasks/shared/bosh-agent-compile.yml. 4. Runs ci/tasks/run-bats-pipeline.sh which provisions a GCP environment with terraform, deploys the BOSH director, runs BATs, and tears down. All GCP credentials are resolved from the shared Concourse credential store (((gcp_json_key)) / ((gcp_project_id))). Files added ----------- - ci/fly-bats.yml — standalone Concourse pipeline (replaces the two separate fly-execute task files) - ci/tasks/run-bats-pipeline.sh — orchestrates the full BATs lifecycle in GCP Env-var overrides (all optional) --------------------------------- BATS_ENV_NAME terraform env name (default: bats-local) STEMCELL_NAME override GCP stemcell DEPLOY_ARGS extra ops-files for bosh create-env BAT_RSPEC_FLAGS extra RSpec flags (e.g. "--tag wip") CONCOURSE_TARGET target alias (default: bosh) Cherry-picked from fly-bats-pipeline branch (PR #2753).
1 parent f3efe17 commit 399168b

3 files changed

Lines changed: 333 additions & 0 deletions

File tree

ci/fly-bats.yml

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
---
2+
# Standalone pipeline for running the BOSH Acceptance Test suite (BATs)
3+
# against a specific branch.
4+
#
5+
# Intended to be driven by `bundle exec rake fly:bats` from src/, which
6+
# pushes the current branch and sets this pipeline automatically.
7+
#
8+
# Manual usage:
9+
# fly -t bosh set-pipeline -p bats-local \
10+
# -c ci/fly-bats.yml \
11+
# --var bosh_repo=https://github.com/cloudfoundry/bosh.git \
12+
# --var bosh_branch=my-feature-branch \
13+
# --var env_name=bats-local \
14+
# --var stemcell_name=bosh-google-kvm-ubuntu-noble \
15+
# --var deploy_args="-o bosh-deployment/external-ip-not-recommended.yml" \
16+
# --var bat_rspec_flags=""
17+
# fly -t bosh unpause-pipeline -p bats-local
18+
# fly -t bosh trigger-job -j bats-local/bats -w
19+
20+
resources:
21+
- name: bosh
22+
type: git
23+
source:
24+
uri: ((bosh_repo))
25+
branch: ((bosh_branch))
26+
27+
# bosh-ci is the same repo as bosh but filtered to ci/ paths so that
28+
# scripts under ci/bats/ are available at bosh-ci/ in the task workspace.
29+
- name: bosh-ci
30+
type: git
31+
source:
32+
uri: ((bosh_repo))
33+
branch: ((bosh_branch))
34+
paths: [ci]
35+
36+
- name: bosh-cli
37+
type: github-release
38+
source:
39+
owner: cloudfoundry
40+
repository: bosh-cli
41+
access_token: ((github_public_repo_token))
42+
43+
- name: stemcell
44+
type: bosh-io-stemcell
45+
source:
46+
name: bosh-google-kvm-ubuntu-noble
47+
48+
- name: bats
49+
type: git
50+
source:
51+
uri: https://github.com/cloudfoundry/bosh-acceptance-tests.git
52+
branch: master
53+
54+
- name: bosh-deployment
55+
type: git
56+
source:
57+
uri: https://github.com/cloudfoundry/bosh-deployment
58+
branch: master
59+
60+
- name: integration-image
61+
type: registry-image
62+
source:
63+
repository: ghcr.io/cloudfoundry/bosh/integration
64+
tag: main
65+
username: ((github_read_write_packages.username))
66+
password: ((github_read_write_packages.password))
67+
68+
jobs:
69+
- name: bats
70+
serial: true
71+
plan:
72+
- do:
73+
- in_parallel:
74+
- get: bosh
75+
- get: bosh-ci
76+
- get: bosh-cli
77+
params:
78+
globs: [bosh-cli-*-linux-amd64]
79+
- get: stemcell
80+
- get: bats
81+
- get: bosh-deployment
82+
- get: integration-image
83+
84+
- task: make-candidate
85+
image: integration-image
86+
file: bosh-ci/ci/tasks/make-candidate.yml
87+
88+
- task: compile-bosh-release
89+
file: bosh-deployment/ci/tasks/shared/bosh-agent-compile.yml
90+
91+
- task: run-bats
92+
image: integration-image
93+
input_mapping:
94+
bosh-release: compiled-release
95+
config:
96+
platform: linux
97+
inputs:
98+
- name: bosh
99+
- name: bosh-ci
100+
- name: bosh-cli
101+
- name: bosh-deployment
102+
- name: stemcell
103+
- name: bats
104+
- name: bosh-release
105+
caches:
106+
- path: cache-dot-bosh-dir
107+
params:
108+
BAT_INFRASTRUCTURE: gcp
109+
GCP_JSON_KEY: ((gcp_json_key))
110+
GCP_PROJECT_ID: ((gcp_project_id))
111+
STEMCELL_NAME: ((stemcell_name))
112+
ENV_NAME: ((env_name))
113+
DEPLOY_ARGS: ((deploy_args))
114+
BAT_RSPEC_FLAGS: ((bat_rspec_flags))
115+
run:
116+
path: bosh/ci/tasks/run-bats-pipeline.sh

ci/tasks/run-bats-pipeline.sh

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
#!/usr/bin/env bash
2+
# run-bats-pipeline.sh
3+
#
4+
# Chains together the full BATs pipeline in a single Concourse task:
5+
# terraform apply → deploy-director → prepare-bats-config → run-bats
6+
# terraform destroy ← destroy-director ← (EXIT trap, always runs)
7+
#
8+
# Required env vars (set via run-bats-pipeline.yml params):
9+
# GCP_JSON_KEY – GCP service account JSON (resolved from Concourse creds)
10+
# GCP_PROJECT_ID – GCP project ID
11+
# ENV_NAME – Unique terraform env name, e.g. "bats-local"
12+
# BAT_INFRASTRUCTURE – "gcp"
13+
# STEMCELL_NAME – Stemcell name for bats-config.yml
14+
# DEPLOY_ARGS – Extra ops-file args for bosh create-env
15+
# BAT_RSPEC_FLAGS – Extra flags appended to the BAT run (optional)
16+
17+
set -eu
18+
19+
ROOT_DIR="$PWD"
20+
TERRAFORM_DIR="${ROOT_DIR}/bosh-ci/ci/bats/iaas/gcp/terraform"
21+
TERRAFORM_VERSION="1.9.8"
22+
23+
# ── Shared working directories ───────────────────────────────────────────────
24+
mkdir -p director-state bats-config environment
25+
# cache-dot-bosh-dir is provided as a Concourse cache volume; create if absent
26+
mkdir -p cache-dot-bosh-dir/.bosh
27+
28+
# prepare-bats-config.sh expects its terraform metadata at terraform/metadata
29+
# but deploy-director.sh expects it at environment/metadata.
30+
# Symlink terraform/ → environment/ so both scripts find what they need.
31+
ln -sf "${ROOT_DIR}/environment" "${ROOT_DIR}/terraform"
32+
33+
# ── Install terraform ────────────────────────────────────────────────────────
34+
if ! command -v terraform &>/dev/null; then
35+
echo "--- Installing terraform ${TERRAFORM_VERSION} ---"
36+
curl -sSL \
37+
"https://releases.hashicorp.com/terraform/${TERRAFORM_VERSION}/terraform_${TERRAFORM_VERSION}_linux_amd64.zip" \
38+
-o /tmp/terraform.zip
39+
unzip -qo /tmp/terraform.zip -d /usr/local/bin terraform
40+
chmod +x /usr/local/bin/terraform
41+
fi
42+
43+
# ── GCP credentials file (used by the GCS backend and the Google provider) ──
44+
GCP_CREDS_FILE="$(mktemp /tmp/gcp-creds-XXXXXX.json)"
45+
echo "${GCP_JSON_KEY}" > "${GCP_CREDS_FILE}"
46+
chmod 600 "${GCP_CREDS_FILE}"
47+
48+
# ── Provision GCP environment via terraform ──────────────────────────────────
49+
echo "--- Provisioning GCP environment (env: ${ENV_NAME}) ---"
50+
pushd "${TERRAFORM_DIR}" >/dev/null
51+
52+
terraform init \
53+
-input=false \
54+
-reconfigure \
55+
-backend-config="bucket=bosh-director-pipeline" \
56+
-backend-config="prefix=bats-terraform/${ENV_NAME}" \
57+
-backend-config="credentials=${GCP_CREDS_FILE}"
58+
59+
terraform apply \
60+
-input=false \
61+
-auto-approve \
62+
-var "project_id=${GCP_PROJECT_ID}" \
63+
-var "gcp_credentials_json=${GCP_JSON_KEY}" \
64+
-var "name=${ENV_NAME}"
65+
66+
# Convert terraform outputs to the flat metadata JSON consumed by director-vars
67+
# and prepare-bats-config.sh.
68+
terraform output -json \
69+
| jq 'with_entries(.value = .value.value)' \
70+
> "${ROOT_DIR}/environment/metadata"
71+
72+
popd >/dev/null
73+
74+
# ── Teardown trap (always runs on EXIT) ─────────────────────────────────────
75+
function collect_director_diagnostics {
76+
# Only collect when we have a deployed director and bosh-cli is available.
77+
[[ -f director-state/director-creds.yml ]] || return 0
78+
[[ -f "${ROOT_DIR}/environment/metadata" ]] || return 0
79+
command -v bosh-cli &>/dev/null || return 0
80+
81+
local director_ip
82+
director_ip="$(jq -r '.director_public_ip // empty' "${ROOT_DIR}/environment/metadata")"
83+
[[ -n "${director_ip}" ]] || return 0
84+
85+
echo "--- Collecting director diagnostics (IP: ${director_ip}) ---"
86+
87+
# Extract jumpbox SSH private key from the vars-store.
88+
local jumpbox_key_file
89+
jumpbox_key_file="$(mktemp /tmp/jumpbox-key-XXXXXX)"
90+
bosh-cli interpolate director-state/director-creds.yml \
91+
--path /jumpbox_ssh/private_key > "${jumpbox_key_file}" 2>/dev/null || { rm -f "${jumpbox_key_file}"; return 0; }
92+
chmod 600 "${jumpbox_key_file}"
93+
94+
ssh -o StrictHostKeyChecking=no \
95+
-o UserKnownHostsFile=/dev/null \
96+
-o ConnectTimeout=10 \
97+
-i "${jumpbox_key_file}" \
98+
"jumpbox@${director_ip}" \
99+
'echo "=== monit status ===" && sudo /var/vcap/bosh/bin/monit status;
100+
echo "=== bosh_nats_sync log (last 100 lines) ===" && sudo tail -100 /var/vcap/sys/log/nats/bosh-nats-sync.log 2>/dev/null || true;
101+
echo "=== bosh_nats_sync bpm stdout ===" && sudo cat /var/vcap/sys/log/bpm/nats/bosh_nats_sync.stdout.log 2>/dev/null || true;
102+
echo "=== bosh_nats_sync bpm stderr ===" && sudo cat /var/vcap/sys/log/bpm/nats/bosh_nats_sync.stderr.log 2>/dev/null || true;
103+
echo "=== nats log (last 50 lines) ===" && sudo tail -50 /var/vcap/sys/log/nats/nats.log 2>/dev/null || true;
104+
echo "=== nats bpm stdout (last 50 lines) ===" && sudo tail -50 /var/vcap/sys/log/bpm/nats/nats.stdout.log 2>/dev/null || true;
105+
echo "=== health_monitor log (last 100 lines) ===" && sudo tail -100 /var/vcap/sys/log/health_monitor/health_monitor.log 2>/dev/null || true;
106+
echo "=== health_monitor bpm stdout ===" && sudo tail -50 /var/vcap/sys/log/bpm/health_monitor/health_monitor.stdout.log 2>/dev/null || true;
107+
echo "=== health_monitor bpm stderr ===" && sudo tail -50 /var/vcap/sys/log/bpm/health_monitor/health_monitor.stderr.log 2>/dev/null || true' \
108+
2>&1 || echo "(SSH diagnostics failed — VM may not be reachable)"
109+
110+
rm -f "${jumpbox_key_file}"
111+
}
112+
113+
function teardown {
114+
local exit_code=$?
115+
set +e
116+
117+
# Always collect diagnostics – on success this helps correlate logs with
118+
# passing runs; on failure it captures the state at the point of failure.
119+
collect_director_diagnostics
120+
121+
echo "--- Tearing down BOSH director ---"
122+
if [[ -f director-state/director-state.json ]]; then
123+
# destroy-director.sh expects bosh-cli/bosh-cli-* to exist; restore it
124+
# because deploy-director.sh already moved the original binary away.
125+
cp /usr/local/bin/bosh-cli bosh-cli/bosh-cli-restore 2>/dev/null || true
126+
bosh-ci/ci/bats/tasks/destroy-director.sh || true
127+
fi
128+
129+
echo "--- Destroying GCP environment (env: ${ENV_NAME}) ---"
130+
pushd "${TERRAFORM_DIR}" >/dev/null
131+
terraform destroy \
132+
-input=false \
133+
-auto-approve \
134+
-var "project_id=${GCP_PROJECT_ID}" \
135+
-var "gcp_credentials_json=${GCP_JSON_KEY}" \
136+
-var "name=${ENV_NAME}" || true
137+
popd >/dev/null
138+
139+
rm -f "${GCP_CREDS_FILE}"
140+
141+
exit "${exit_code}"
142+
}
143+
trap teardown EXIT
144+
145+
# ── Deploy BOSH director ─────────────────────────────────────────────────────
146+
echo "--- Deploying BOSH director ---"
147+
# deploy-director.sh moves bosh-cli/bosh-cli-* to /usr/local/bin/bosh-cli.
148+
# After this call bosh-cli is installed system-wide as 'bosh-cli'.
149+
bosh-ci/ci/bats/tasks/deploy-director.sh
150+
151+
# ── Prepare BATs config ──────────────────────────────────────────────────────
152+
echo "--- Preparing BATs config ---"
153+
bosh-ci/ci/bats/iaas/gcp/prepare-bats-config.sh
154+
155+
# ── Run BATs ─────────────────────────────────────────────────────────────────
156+
echo "--- Running BATs ---"
157+
# Source the environment file that prepare-bats-config.sh wrote; this exports
158+
# BOSH_ENVIRONMENT, BOSH_CLIENT, BOSH_CLIENT_SECRET, BOSH_CA_CERT,
159+
# BOSH_ALL_PROXY, and the default BAT_RSPEC_FLAGS.
160+
# shellcheck source=/dev/null
161+
source bats-config/bats.env
162+
163+
# Allow the caller to append extra RSpec flags (e.g. "--tag wip").
164+
if [[ -n "${BAT_RSPEC_FLAGS:-}" ]]; then
165+
export BAT_RSPEC_FLAGS="${BAT_RSPEC_FLAGS}"
166+
fi
167+
168+
bats/ci/tasks/run-bats.sh

src/tasks/fly.rake

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
require 'shellwords'
2+
13
namespace :fly do
24
desc 'Fly unit specs'
35
task :unit do
@@ -9,6 +11,53 @@ namespace :fly do
911
COVERAGE: ENV.fetch('COVERAGE', false))
1012
end
1113

14+
desc 'Run BATs (BOSH Acceptance Tests) against the current branch via Concourse'
15+
#
16+
# Sets the ci/fly-bats.yml pipeline on Concourse, then triggers and watches
17+
# the bats job. The current branch is pushed to origin automatically so
18+
# Concourse can fetch it.
19+
#
20+
# GCP credentials are resolved from the Concourse credential store
21+
# (((gcp_json_key)) and ((gcp_project_id))).
22+
#
23+
# Useful env vars:
24+
# BATS_ENV_NAME – terraform env name, must be unique per concurrent run
25+
# (default: "bats-local")
26+
# STEMCELL_NAME – GCP stemcell name override
27+
# DEPLOY_ARGS – extra ops-files passed to bosh create-env
28+
# BAT_RSPEC_FLAGS – extra flags appended to the RSpec BATs run
29+
task :bats do
30+
env_name = ENV.fetch('BATS_ENV_NAME', 'bats-local')
31+
32+
branch = `git -C .. rev-parse --abbrev-ref HEAD`.strip
33+
repo = `git -C .. remote get-url origin`.strip
34+
.sub(/\Agit@github\.com:/, 'https://github.com/')
35+
.sub(/\.git\z/, '.git')
36+
37+
# Push the current branch so Concourse can check it out.
38+
sh "git -C .. push origin HEAD"
39+
40+
# ── Set the pipeline ─────────────────────────────────────────────────────
41+
sh [
42+
"fly #{concourse_target}",
43+
'set-pipeline',
44+
'--non-interactive',
45+
'--pipeline bats-local',
46+
'--config ../ci/fly-bats.yml',
47+
"--var bosh_repo=#{Shellwords.escape(repo)}",
48+
"--var bosh_branch=#{Shellwords.escape(branch)}",
49+
"--var env_name=#{Shellwords.escape(env_name)}",
50+
"--var stemcell_name=#{Shellwords.escape(ENV.fetch('STEMCELL_NAME', 'bosh-google-kvm-ubuntu-noble'))}",
51+
"--var deploy_args=#{Shellwords.escape(ENV.fetch('DEPLOY_ARGS', '-o bosh-deployment/external-ip-not-recommended.yml'))}",
52+
"--var bat_rspec_flags=#{Shellwords.escape(ENV.fetch('BAT_RSPEC_FLAGS', ''))}",
53+
].compact.join(' ')
54+
55+
sh "fly #{concourse_target} unpause-pipeline --pipeline bats-local"
56+
57+
# ── Trigger and stream the job output ────────────────────────────────────
58+
sh "fly #{concourse_target} trigger-job --job bats-local/bats --watch"
59+
end
60+
1261
desc 'Fly integration specs'
1362
task :integration, [:cli_dir] do |_, args|
1463
db, db_version = fetch_db_and_version('postgresql')

0 commit comments

Comments
 (0)