Skip to content

Instantly share code, notes, and snippets.

@askb
Created September 24, 2025 22:35
Show Gist options
  • Select an option

  • Save askb/1be8f3235a0ef5877cbda5ec8c5bfdc5 to your computer and use it in GitHub Desktop.

Select an option

Save askb/1be8f3235a0ef5877cbda5ec8c5bfdc5 to your computer and use it in GitHub Desktop.
packer merge
---
# SPDX-License-Identifier: Apache-2.0
# Copyright (c) 2025 The Linux Foundation
name: Gerrit CI Packer Merge using Tailscale VPN and SSH Tunnel
# yamllint disable-line rule:truthy
on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:
inputs:
GERRIT_BRANCH:
description: "Branch that change is against"
required: true
type: string
GERRIT_CHANGE_ID:
description: "The ID for the change"
required: true
type: string
GERRIT_CHANGE_NUMBER:
description: "The Gerrit number"
required: true
type: string
GERRIT_CHANGE_URL:
description: "URL to the change"
required: true
type: string
GERRIT_EVENT_TYPE:
description: "Type of Gerrit event"
required: true
type: string
GERRIT_PATCHSET_NUMBER:
description: "The patch number for the change"
required: true
type: string
GERRIT_PATCHSET_REVISION:
description: "The revision sha"
required: true
type: string
GERRIT_PROJECT:
description: "Project in Gerrit"
required: true
type: string
GERRIT_REFSPEC:
description: "Gerrit refspec to fetch change"
required: true
type: string
concurrency:
group: ${{ github.workflow }}-${{ github.event.number || github.ref }}
cancel-in-progress: true
env:
PACKER_VERSION: "1.11.2"
OS_CLOUD: "vex"
jobs:
prepare:
runs-on: ubuntu-latest
outputs:
has-changes: ${{ steps.check.outputs.has-changes }}
steps:
- name: Gerrit Checkout
# yamllint disable-line rule:line-length
uses: lfit/checkout-gerrit-change-action@54d751e8bd167bc91f7d665dabe33fae87aaaa63 # v0.9
with:
gerrit-refspec: ${{ inputs.GERRIT_REFSPEC || github.ref }}
delay: "0s"
- name: Clone git submodules
shell: bash
run: git submodule update --init
- name: Check for changes
# yamllint disable-line rule:line-length
uses: dorny/paths-filter@4512585405083f25c027a35db413c2b3b9006d50 # v2.11.1
id: check
with:
base: ${{ inputs.GERRIT_BRANCH || github.event.repository.default_branch }}
ref: ${{ inputs.GERRIT_PATCHSET_REVISION || github.sha }}
filters: |
has-changes:
- 'vars/**'
- 'templates/**'
- 'provision/**'
packer-build:
needs: prepare
runs-on: ubuntu-latest
if: needs.prepare.outputs.has-changes == 'true'
steps:
- name: Checkout code
# yamllint disable-line rule:line-length
uses: lfit/checkout-gerrit-change-action@54d751e8bd167bc91f7d665dabe33fae87aaaa63 # v0.9
with:
gerrit-refspec: ${{ inputs.GERRIT_REFSPEC || github.ref }}
delay: "0s"
- name: Clone git submodules
shell: bash
run: git submodule update --init
# Step 1: Connect to Tailscale VPN
- name: Setup Tailscale VPN
uses: tailscale/github-action@v2
with:
oauth-key: ${{ secrets.TAILSCALE_OAUTH_KEY }}
tags: tag:ci
hostname: github-actions-${{ github.run_id }}
# Step 2: Verify Tailscale connection
- name: Verify Tailscale connection
run: |
echo "Tailscale status:"
sudo tailscale status
echo "Checking connectivity to Jenkins server..."
ping -c 3 ${{ secrets.JENKINS_TAILSCALE_IP }} || echo "Ping failed, but SSH might still work"
# Step 3: Setup SSH credentials
- name: Setup SSH key
env:
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
JENKINS_TAILSCALE_IP: ${{ secrets.JENKINS_TAILSCALE_IP }}
run: |
mkdir -p ~/.ssh
echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
# Add Jenkins server's Tailscale IP to known hosts
ssh-keyscan -H $JENKINS_TAILSCALE_IP >> ~/.ssh/known_hosts
# Step 4: Start SSH tunnel through Tailscale
- name: Start SSH tunnel via Tailscale
env:
JENKINS_TAILSCALE_IP: ${{ secrets.JENKINS_TAILSCALE_IP }}
JENKINS_USER: ${{ secrets.JENKINS_USER }}
run: |
# Start SSH tunnel using Tailscale IP
ssh -D 1080 -f -N -o StrictHostKeyChecking=no \
-o ConnectTimeout=30 -o ServerAliveInterval=60 \
${JENKINS_USER}@${JENKINS_TAILSCALE_IP}
# Store PID and verify tunnel
sleep 5
SSH_PID=$(pgrep -f "ssh -D 1080")
echo "SSH_TUNNEL_PID=${SSH_PID}" >> $GITHUB_ENV
if [ ! -z "$SSH_PID" ]; then
echo "✅ SSH tunnel started successfully with PID: ${SSH_PID}"
else
echo "❌ Failed to start SSH tunnel"
exit 1
fi
# Step 5: Setup Packer
- name: Setup packer
uses: hashicorp/setup-packer@main
id: setup
with:
version: ${{ env.PACKER_VERSION }}
# Step 6: Create cloud configuration files
- name: Create cloud-env file required for packer
id: create-cloud-env-file
shell: bash
run: |
echo "${{ secrets.CLOUD_ENV_B64 }}" | base64 --decode \
> "${GITHUB_WORKSPACE}/cloud-env.pkrvars.hcl"
- name: Create cloud.yaml file for openstack client
id: create-cloud-yaml-file
shell: bash
run: |
mkdir -p "$HOME/.config/openstack"
echo "${{ secrets.CLOUDS_YAML_B64 }}" | base64 --decode \
> "$HOME/.config/openstack/clouds.yaml"
- name: Setup Python
# yamllint disable-line rule:line-length
uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5.0.0
with:
python-version: "3.11"
- name: Install openstack deps
id: install-openstack-deps
shell: bash
run: |
python -m pip install --upgrade pip
pip install python-openstackclient
pip freeze
# Step 7: Configure proxy if needed
- name: Configure proxy for Packer
run: |
# Set SOCKS proxy for applications that need it
echo "HTTP_PROXY=socks5://127.0.0.1:1080" >> $GITHUB_ENV
echo "HTTPS_PROXY=socks5://127.0.0.1:1080" >> $GITHUB_ENV
# Step 8: Initialize and validate Packer templates
- name: Initialize and validate Packer files
shell: bash
run: |
set -x
varfiles=(vars/*.pkrvars.hcl)
templates=(templates/*.pkr.hcl)
mkdir -p "${GITHUB_WORKSPACE}/logs"
PACKER_LOGS_DIR="${GITHUB_WORKSPACE}/logs"
for varfile in "${varfiles[@]}"; do
if [[ "$varfile" == *"cloud-env.json"* ]] || \
[[ "$varfile" == "vars/*.json" ]] || \
[[ "$varfile" == *"cloud-env.pkrvars.hcl"* ]] || \
[[ "$varfile" == *"cloud-env-aws.pkrvars.hcl"* ]] || \
[[ "$varfile" == "vars/*.pkrvars.hcl" ]]; then
continue
fi
echo "::group::Validating $varfile"
echo "-----> Test var: $varfile"
for template in "${templates[@]}"; do
if [[ "$template" == *"variables.pkr.hcl"* ]] || \
[[ "$template" == *"variables.auto.pkr.hcl"* ]]; then
continue
fi
if [[ "${template#*.}" == "pkr.hcl" ]]; then
echo "packer init $template ..."
packer init "$template"
fi
LOG_FILE="packer-validate-${varfile##*/}-${template##*/}.log"
export PACKER_LOG="yes"
export PACKER_LOG_PATH="$PACKER_LOGS_DIR/$LOG_FILE"
if output=$(OS_CLOUD=${{ env.OS_CLOUD }} packer validate \
-var-file="${GITHUB_WORKSPACE}/cloud-env.pkrvars.hcl" \
-var-file="$varfile" "$template"); then
echo "$template: $output"
else
echo "$template: $output"
exit 1
fi
done
echo "::endgroup::"
done
# Step 9: Run Packer build (selective - only build specific templates)
- name: Run Packer build
shell: bash
env:
# Limit builds to prevent resource exhaustion
BUILD_LIMIT: 1
run: |
set -x
varfiles=(vars/*.pkrvars.hcl)
templates=(templates/*.pkr.hcl)
mkdir -p "${GITHUB_WORKSPACE}/build-logs"
PACKER_LOGS_DIR="${GITHUB_WORKSPACE}/build-logs"
build_count=0
max_builds=${BUILD_LIMIT:-1}
for varfile in "${varfiles[@]}"; do
# Skip example and cloud-env files
if [[ "$varfile" == *"cloud-env.json"* ]] || \
[[ "$varfile" == "vars/*.json" ]] || \
[[ "$varfile" == *"cloud-env.pkrvars.hcl"* ]] || \
[[ "$varfile" == *"cloud-env-aws.pkrvars.hcl"* ]] || \
[[ "$varfile" == "vars/*.pkrvars.hcl" ]]; then
continue
fi
# Only build Ubuntu 22.04 by default to limit resource usage
if [[ "$varfile" != *"ubuntu-22.04"* ]]; then
echo "Skipping $varfile (not ubuntu-22.04)"
continue
fi
if [ $build_count -ge $max_builds ]; then
echo "Reached maximum builds limit ($max_builds), stopping."
break
fi
echo "::group::Building with $varfile"
echo "-----> Building var: $varfile"
for template in "${templates[@]}"; do
if [[ "$template" == *"variables.pkr.hcl"* ]] || \
[[ "$template" == *"variables.auto.pkr.hcl"* ]] || \
[[ "$template" == *"windows"* ]]; then
continue
fi
# Only build basic templates
if [[ "$template" != *"builder.pkr.hcl" ]]; then
echo "Skipping $template (not builder.pkr.hcl)"
continue
fi
LOG_FILE="packer-build-${varfile##*/}-${template##*/}.log"
export PACKER_LOG="yes"
export PACKER_LOG_PATH="$PACKER_LOGS_DIR/$LOG_FILE"
echo "Building $template with $varfile..."
if OS_CLOUD=${{ env.OS_CLOUD }} packer build \
-var-file="${GITHUB_WORKSPACE}/cloud-env.pkrvars.hcl" \
-var-file="$varfile" "$template"; then
echo "✅ Successfully built $template with $varfile"
build_count=$((build_count + 1))
else
echo "❌ Failed to build $template with $varfile"
# Continue with other builds instead of failing completely
echo "::warning::Build failed for $template with $varfile"
fi
# Only build one template per varfile to limit resource usage
break
done
echo "::endgroup::"
done
echo "Completed $build_count builds"
# Step 10: Upload build artifacts and logs
- name: Upload Packer logs
uses: actions/upload-artifact@v4
if: always()
with:
name: packer-logs
path: |
logs/
build-logs/
retention-days: 30
# Step 11: Cleanup (always runs)
- name: Cleanup SSH tunnel and Tailscale
if: always()
run: |
# Kill SSH tunnel
if [ ! -z "$SSH_TUNNEL_PID" ]; then
kill $SSH_TUNNEL_PID 2>/dev/null || true
echo "SSH tunnel cleaned up"
fi
# Tailscale cleanup (optional - runner will be destroyed anyway)
sudo tailscale logout || true
vote:
if: always() && needs.prepare.outputs.has-changes == 'true'
needs: [prepare, packer-build-tailscale]
runs-on: ubuntu-latest
steps:
- name: Get workflow conclusion
uses: technote-space/workflow-conclusion-action@v3
- name: Set vote
uses: lfit/gerrit-review-action@9627b9a144f2a2cad70707ddfae87c87dce60729 # v0.8
with:
host: ${{ vars.GERRIT_SERVER }}
username: ${{ vars.GERRIT_SSH_USER }}
key: ${{ secrets.GERRIT_SSH_PRIVKEY }}
known_hosts: ${{ secrets.GERRIT_KNOWN_HOSTS }}
gerrit-change-number: ${{ inputs.GERRIT_CHANGE_NUMBER }}
gerrit-patchset-number: ${{ inputs.GERRIT_PATCHSET_NUMBER }}
vote-type: ${{ env.WORKFLOW_CONCLUSION }}
comment-only: true
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment