|
| 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