Skip to content

Commit 9e36f48

Browse files
committed
feat(ci): add unified "Vagrant: Build, Test and Publish" workflow
Single workflow_dispatch that builds the Vagrant boxes with Packer, runs the `vagrant up` smoke test (shared-steps, gated by run_test), and publishes each box to the HCP Vagrant Registry - all on the same runner. The publish stage runs in-job on the locally-built .box (no S3 round-trip), right after the box is built and tested, and is gated by a new release_to_hcp input (default true). Publishes run in parallel across the provider/variant matrix. Build runners are pinned on-demand (spot=false) to avoid spot-reclaim cancellations mid-build. Providers are selected by vagrant_type (ALL / vagrant_libvirt / vagrant_virtualbox / vagrant_vmware / vagrant_hyperv). libvirt / virtualbox / vmware use the same gh-hosted + self-hosted matrix as vagrant-build.yml. Hyper-V builds as a hyperv-x86_64 leg of build-gh-hosted (no separate job): a Hyper-V guest can't boot on the Linux runner, so that leg forces run_test=false (only shared-steps' offline validation runs) and the box is then published like the other providers. The publish logic lives in a new vagrant-publish-steps composite, adapted from vagrant-publish.yml minus dry-run: it sources the box from the local file, installs the hcp CLI OS-aware (apt on the Ubuntu legs, dnf/HashiCorp RPM repo on the EL9 vmware leg; vagrant is already present on every build runner), and retries `vagrant cloud publish` with backoff so sibling providers racing to create/modify the same shared box version self-heal instead of failing. The standalone vagrant-publish.yml is left unchanged. Inputs are vagrant-build.yml's plus release_to_hcp; dry-run-mode is omitted.
1 parent aeb69ac commit 9e36f48

2 files changed

Lines changed: 635 additions & 0 deletions

File tree

Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
1+
name: "Vagrant box publish steps"
2+
description: >
3+
Publish a locally-built AlmaLinux Vagrant .box to the HCP Vagrant
4+
Registry from the build runner: parse the box filename, install the hcp
5+
CLI (apt on Ubuntu, dnf/HashiCorp RPM repo on EL9 - vagrant itself is
6+
already present on every build runner), and run `vagrant cloud publish`.
7+
Adapted from the publish steps of .github/workflows/vagrant-publish.yml,
8+
minus dry-run, and sourcing the box from a local file instead of a URL.
9+
10+
inputs:
11+
image_file:
12+
description: "Path to the locally-built .box file"
13+
required: true
14+
image_url:
15+
description: "Public URL of the box (cosmetic, for the summary / notification link)"
16+
required: false
17+
default: ''
18+
notify_mattermost:
19+
description: "Send notification to Mattermost (true/false)"
20+
required: true
21+
default: 'true'
22+
HCP_CLIENT_ID:
23+
description: "HCP service-principal client ID"
24+
required: true
25+
HCP_CLIENT_SECRET:
26+
description: "HCP service-principal client secret"
27+
required: true
28+
HCP_ORG:
29+
description: "HCP Vagrant Registry organization"
30+
required: true
31+
MATTERMOST_WEBHOOK_URL:
32+
description: "Mattermost webhook URL"
33+
required: false
34+
default: ''
35+
MATTERMOST_CHANNEL:
36+
description: "Mattermost channel"
37+
required: false
38+
default: ''
39+
40+
runs:
41+
using: composite
42+
steps:
43+
- name: Prepare environment
44+
shell: bash
45+
env:
46+
IMAGE_FILE: ${{ inputs.image_file }}
47+
run: |
48+
# Parse the box filename to derive the HCP box name, provider,
49+
# version, and architecture (same logic as vagrant-publish.yml).
50+
image_file=$(basename "${IMAGE_FILE}")
51+
52+
# Regex for the modern and "Kitten" filename formats.
53+
REGEX_MODERN="^AlmaLinux-(Kitten|[0-9]+)-Vagrant-([a-z]+)-([0-9\.]+)-([0-9\.]+)\.(aarch64|x86_64)(_v2)?\.box$"
54+
55+
# Regex for the legacy (AlmaLinux 8) filename format.
56+
REGEX_LEGACY="^AlmaLinux-([0-9]+)-Vagrant-([0-9\.]+)-([0-9]+)\.(aarch64|x86_64)(_v2)?\.(.+)\.box$"
57+
58+
if [[ "$image_file" =~ $REGEX_MODERN ]]; then
59+
major_or_kitten="${BASH_REMATCH[1]}"
60+
provider="${BASH_REMATCH[2]}"
61+
release_version="${BASH_REMATCH[3]}"
62+
date_stamp="${BASH_REMATCH[4]}"
63+
architecture="${BASH_REMATCH[5]}"
64+
65+
if [[ "$major_or_kitten" == "Kitten" ]]; then
66+
major_version="10" # Kitten is based on version 10
67+
kitten_suffix="-kitten"
68+
else
69+
major_version="$major_or_kitten"
70+
# For non-Kitten files, remove the trailing .<digit> from the date stamp.
71+
date_stamp="${date_stamp%.*}"
72+
fi
73+
74+
if [[ -n "${BASH_REMATCH[6]}" ]]; then
75+
is_v2_suffix="-x86_64_v2"
76+
fi
77+
78+
elif [[ "$image_file" =~ $REGEX_LEGACY ]]; then
79+
major_version="${BASH_REMATCH[1]}"
80+
release_version="${BASH_REMATCH[2]}"
81+
date_stamp="${BASH_REMATCH[3]}"
82+
architecture="${BASH_REMATCH[4]}"
83+
provider="${BASH_REMATCH[6]}"
84+
85+
if [[ -n "${BASH_REMATCH[5]}" ]]; then
86+
is_v2_suffix="-x86_64_v2"
87+
fi
88+
89+
else
90+
echo "[Error]: No pattern matched for '$image_file'" && exit 1
91+
fi
92+
93+
# Remap "vmware" to the correct Vagrant provider name "vmware_desktop"
94+
if [[ "$provider" == "vmware" ]]; then
95+
provider="vmware_desktop"
96+
fi
97+
98+
# Construct the final box-name version string
99+
version_major="${major_version}${kitten_suffix}${is_v2_suffix}"
100+
101+
# AlmaLinux distro release string
102+
[[ $version_major == *kitten* ]] && release_string="AlmaLinux OS Kitten $major_version $architecture" \
103+
|| release_string="AlmaLinux OS $major_version $architecture"
104+
105+
{
106+
echo "version_major=${version_major}"
107+
echo "vagrant_provider=${provider}"
108+
echo "architecture=${architecture}"
109+
echo "release_version=${release_version}"
110+
echo "release_string=${release_string}"
111+
echo "date_stamp=${date_stamp}"
112+
echo "image_file=${image_file}"
113+
} >> "$GITHUB_ENV"
114+
115+
- name: Install hcp (and vagrant if missing)
116+
shell: bash
117+
run: |
118+
# vagrant is already present on the build runner; install the hcp
119+
# CLI OS-aware (apt on Ubuntu legs, dnf on the EL9 vmware leg).
120+
if command -v apt-get >/dev/null 2>&1; then
121+
ubuntu_codename="$(lsb_release -cs)"
122+
sudo rm -f /usr/share/keyrings/hashicorp-archive-keyring.gpg
123+
wget -O - https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
124+
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com ${ubuntu_codename} main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
125+
sudo apt-get -y update
126+
sudo apt-get -y install hcp
127+
command -v vagrant >/dev/null 2>&1 || sudo apt-get -y install vagrant
128+
elif command -v dnf >/dev/null 2>&1; then
129+
sudo dnf install -y dnf-plugins-core
130+
sudo dnf config-manager --add-repo https://rpm.releases.hashicorp.com/RHEL/hashicorp.repo
131+
if ! sudo dnf install -y hcp; then
132+
# hcp may not be in the RPM repo on all EL9 mirrors; fall back to
133+
# the released binary.
134+
echo "[Info] hcp not available via dnf, downloading the released binary"
135+
sudo dnf install -y jq unzip
136+
ver=$(curl -fsSL https://api.releases.hashicorp.com/v1/releases/hcp/latest | jq -r .version)
137+
curl -fsSL -o /tmp/hcp.zip "https://releases.hashicorp.com/hcp/${ver}/hcp_${ver}_linux_amd64.zip"
138+
sudo unzip -o /tmp/hcp.zip hcp -d /usr/local/bin/
139+
fi
140+
command -v vagrant >/dev/null 2>&1 || sudo dnf install -y vagrant
141+
else
142+
echo "[Error] no supported package manager (apt-get / dnf) on this runner"
143+
exit 1
144+
fi
145+
146+
hcp version || true
147+
vagrant --version
148+
149+
- name: Publish the box
150+
shell: bash
151+
env:
152+
IMAGE_FILE: ${{ inputs.image_file }}
153+
HCP_CLIENT_ID: ${{ inputs.HCP_CLIENT_ID }}
154+
HCP_CLIENT_SECRET: ${{ inputs.HCP_CLIENT_SECRET }}
155+
HCP_ORG: ${{ inputs.HCP_ORG }}
156+
run: |
157+
# Publish the locally-built box to the HCP Vagrant Registry.
158+
# The lowercase fields below are populated by the "Prepare environment"
159+
# step via $GITHUB_ENV and read here as ${{ env.* }} (same style as
160+
# vagrant-publish.yml); checksum/arch are local to this script.
161+
BOX_DIR=$(dirname "${IMAGE_FILE}")
162+
cd "${BOX_DIR}"
163+
164+
[ "${{ env.architecture }}" = "x86_64" ] && arch=amd64
165+
[ "${{ env.architecture }}" = "aarch64" ] && arch=arm64
166+
167+
checksum="$(sha256sum "${{ env.image_file }}" | awk '{ print $1 }')"
168+
echo "[Debug] provider=${{ env.vagrant_provider }} box_name=${{ env.version_major }} checksum=${checksum} image_file=${{ env.image_file }}"
169+
170+
# Fail fast on missing credentials: with empty client-id/secret,
171+
# `hcp auth login` silently falls back to an interactive browser login
172+
# that hangs and times out in CI.
173+
if [ -z "${HCP_CLIENT_ID}" ] || [ -z "${HCP_CLIENT_SECRET}" ]; then
174+
echo "[Error] HCP_CLIENT_ID / HCP_CLIENT_SECRET are empty."
175+
echo " Configure the HCP service-principal secrets on the repo (or org):"
176+
echo " gh secret set HCP_CLIENT_ID --repo <owner>/<repo>"
177+
echo " gh secret set HCP_CLIENT_SECRET --repo <owner>/<repo>"
178+
exit 1
179+
fi
180+
181+
hcp auth login --client-id="${HCP_CLIENT_ID}" --client-secret="${HCP_CLIENT_SECRET}"
182+
VAGRANT_CLOUD_TOKEN="$(hcp auth print-access-token)"
183+
export VAGRANT_CLOUD_TOKEN
184+
185+
publish_args=(
186+
-C sha256
187+
-c "${checksum}"
188+
--release
189+
-a "${arch}"
190+
--direct-upload
191+
--debug
192+
-f
193+
"${HCP_ORG}/${{ env.version_major }}"
194+
"${{ env.release_version }}.${{ env.date_stamp }}"
195+
"${{ env.vagrant_provider }}"
196+
"${{ env.image_file }}"
197+
)
198+
199+
# Sibling providers (libvirt / virtualbox / vmware) publish to the SAME
200+
# box version in parallel, so they can collide while the shared version
201+
# is being created or modified. There is no CLI to detect an in-flight
202+
# publish, so retry with backoff: the only real contention is the
203+
# version-create race, which clears once a sibling finishes. `vagrant
204+
# cloud publish -f` is idempotent, so re-running after a partial publish
205+
# is safe.
206+
MAX_ATTEMPTS=6
207+
attempt=1
208+
while :; do
209+
echo "[Info] vagrant cloud publish attempt ${attempt}/${MAX_ATTEMPTS}"
210+
set +e
211+
publish_out=$(vagrant cloud publish "${publish_args[@]}" 2>&1)
212+
publish_rc=$?
213+
set -e
214+
echo "${publish_out}"
215+
216+
if [ "${publish_rc}" -eq 0 ]; then
217+
echo "[Info] Publish succeeded on attempt ${attempt}"
218+
break
219+
fi
220+
221+
if [ "${attempt}" -ge "${MAX_ATTEMPTS}" ]; then
222+
echo "[Error] Publish failed after ${MAX_ATTEMPTS} attempts"
223+
exit "${publish_rc}"
224+
fi
225+
226+
if echo "${publish_out}" | grep -qiE 'already (exists|been taken)|being (created|modified|updated)|conflict|\b(409|422)\b|please (retry|try again)|simultaneous|locked'; then
227+
echo "[Warn] Concurrent-publish collision on the shared box version; backing off and retrying"
228+
else
229+
echo "[Warn] Publish failed (exit ${publish_rc}); backing off and retrying"
230+
fi
231+
232+
sleep $(( attempt * 15 ))
233+
attempt=$(( attempt + 1 ))
234+
done
235+
236+
- name: The job summary
237+
uses: actions/github-script@v8
238+
env:
239+
HCP_ORG: ${{ inputs.HCP_ORG }}
240+
with:
241+
result-encoding: string
242+
script: |
243+
core.summary
244+
.addRaw('**${{ env.release_string }}** Vagrant box cloud publishing\n')
245+
.addRaw('Vagrant box for **${{ env.vagrant_provider }}** version: `${{ env.release_version }}.${{ env.date_stamp }}`\n')
246+
.addRaw('The box name: ')
247+
.addLink('${{ env.HCP_ORG }}/${{ env.version_major }}', 'https://portal.cloud.hashicorp.com/vagrant/discover/${{ env.HCP_ORG }}/${{ env.version_major }}/versions/${{ env.release_version }}.${{ env.date_stamp }}')
248+
.addRaw('Published to the Cloud: ✅')
249+
.write()
250+
251+
- name: Send notification to Mattermost
252+
uses: mattermost/action-mattermost-notify@master
253+
if: inputs.notify_mattermost == 'true' && inputs.MATTERMOST_WEBHOOK_URL != ''
254+
with:
255+
MATTERMOST_WEBHOOK_URL: ${{ inputs.MATTERMOST_WEBHOOK_URL }}
256+
MATTERMOST_CHANNEL: ${{ inputs.MATTERMOST_CHANNEL }}
257+
MATTERMOST_USERNAME: ${{ github.triggering_actor }}
258+
TEXT: |
259+
:almalinux: **${{ env.release_string }}** Vagrant box cloud publishing, by the GitHub [Action](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})
260+
261+
Vagrant box for **${{ env.vagrant_provider }}** version: `${{ env.release_version }}.${{ env.date_stamp }}`
262+
263+
The box name: [${{ inputs.HCP_ORG }}/${{ env.version_major }}](https://portal.cloud.hashicorp.com/vagrant/discover/${{ inputs.HCP_ORG }}/${{ env.version_major }}/versions/${{ env.release_version }}.${{ env.date_stamp }})
264+
265+
Published to the Cloud: ✅

0 commit comments

Comments
 (0)