All checks were successful
Terraform Plan / Terraform Plan (push) Successful in 16s
Use terraform plan -refresh=false in plan/apply workflows to avoid slow Proxmox state refresh on every push. This keeps CI fast while preserving apply behavior from the generated plan.
226 lines
8.4 KiB
YAML
226 lines
8.4 KiB
YAML
name: Terraform Apply
|
|
|
|
on:
|
|
push:
|
|
branches:
|
|
- master
|
|
|
|
concurrency:
|
|
group: terraform-global
|
|
cancel-in-progress: false
|
|
|
|
jobs:
|
|
terraform:
|
|
name: "Terraform Apply"
|
|
runs-on: ubuntu-latest
|
|
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: https://gitea.com/actions/checkout@v4
|
|
|
|
- name: Create secrets.tfvars
|
|
working-directory: terraform
|
|
run: |
|
|
cat > secrets.auto.tfvars << EOF
|
|
pm_api_token_secret = "${{ secrets.PM_API_TOKEN_SECRET }}"
|
|
SSH_KEY_PUBLIC = "$(printf '%s' "${{ secrets.SSH_KEY_PUBLIC }}" | tr -d '\r\n')"
|
|
EOF
|
|
cat > backend.hcl << EOF
|
|
bucket = "${{ secrets.B2_TF_BUCKET }}"
|
|
key = "terraform.tfstate"
|
|
region = "us-east-005"
|
|
endpoints = {
|
|
s3 = "${{ secrets.B2_TF_ENDPOINT }}"
|
|
}
|
|
access_key = "$(printf '%s' "${{ secrets.B2_KEY_ID }}" | tr -d '\r\n')"
|
|
secret_key = "$(printf '%s' "${{ secrets.B2_APPLICATION_KEY }}" | tr -d '\r\n')"
|
|
skip_credentials_validation = true
|
|
skip_metadata_api_check = true
|
|
skip_region_validation = true
|
|
skip_requesting_account_id = true
|
|
use_path_style = true
|
|
EOF
|
|
|
|
- name: Set up Terraform
|
|
uses: hashicorp/setup-terraform@v2
|
|
with:
|
|
terraform_version: 1.6.6
|
|
terraform_wrapper: false
|
|
|
|
- name: Terraform Init
|
|
working-directory: terraform
|
|
run: terraform init -reconfigure -backend-config=backend.hcl
|
|
|
|
- name: Terraform Plan
|
|
working-directory: terraform
|
|
run: |
|
|
set -euo pipefail
|
|
for attempt in 1 2; do
|
|
echo "Terraform plan attempt $attempt/2"
|
|
if timeout 20m terraform plan -refresh=false -parallelism=1 -out=tfplan; then
|
|
exit 0
|
|
fi
|
|
if [ "$attempt" -eq 1 ]; then
|
|
echo "Plan attempt failed or timed out; retrying in 20s"
|
|
sleep 20
|
|
fi
|
|
done
|
|
echo "Terraform plan failed after retries"
|
|
exit 1
|
|
|
|
- name: Block accidental destroy
|
|
env:
|
|
ALLOW_TF_DESTROY: ${{ secrets.ALLOW_TF_DESTROY }}
|
|
working-directory: terraform
|
|
run: |
|
|
terraform show -json -no-color tfplan > tfplan.json
|
|
DESTROY_COUNT=$(python3 -c 'import json; raw=open("tfplan.json","rb").read().decode("utf-8","ignore"); start=raw.find("{"); data=json.JSONDecoder().raw_decode(raw[start:])[0]; print(sum(1 for rc in data.get("resource_changes", []) if "delete" in rc.get("change", {}).get("actions", [])))')
|
|
echo "Planned deletes: $DESTROY_COUNT"
|
|
if [ "$DESTROY_COUNT" -gt 0 ] && [ "${ALLOW_TF_DESTROY}" != "true" ]; then
|
|
echo "Destroy actions detected. Set ALLOW_TF_DESTROY=true to allow."
|
|
exit 1
|
|
fi
|
|
|
|
- name: Terraform Apply
|
|
working-directory: terraform
|
|
run: terraform apply -parallelism=1 -auto-approve tfplan
|
|
|
|
- name: Create SSH key
|
|
run: |
|
|
install -m 0700 -d ~/.ssh
|
|
KEY_SOURCE=""
|
|
KEY_CONTENT=""
|
|
KEY_B64="$(printf '%s' "${{ secrets.SSH_KEY_PRIVATE_BASE64 }}")"
|
|
if [ -n "$KEY_B64" ]; then
|
|
KEY_SOURCE="SSH_KEY_PRIVATE_BASE64"
|
|
KEY_CONTENT="$(printf '%s' "$KEY_B64" | base64 -d)"
|
|
else
|
|
KEY_CONTENT="$(printf '%s' "${{ secrets.SSH_KEY_PRIVATE }}")"
|
|
if [ -n "$KEY_CONTENT" ]; then
|
|
KEY_SOURCE="SSH_KEY_PRIVATE"
|
|
else
|
|
KEY_CONTENT="$(printf '%s' "${{ secrets.KUBEADM_SSH_PRIVATE_KEY }}")"
|
|
KEY_SOURCE="KUBEADM_SSH_PRIVATE_KEY"
|
|
fi
|
|
fi
|
|
|
|
if [ -z "$KEY_CONTENT" ]; then
|
|
echo "Missing SSH private key secret. Set SSH_KEY_PRIVATE_BASE64, SSH_KEY_PRIVATE, or KUBEADM_SSH_PRIVATE_KEY."
|
|
exit 1
|
|
fi
|
|
|
|
KEY_CONTENT="$(printf '%s' "$KEY_CONTENT" | tr -d '\r')"
|
|
if printf '%s' "$KEY_CONTENT" | grep -q '\\n'; then
|
|
printf '%b' "$KEY_CONTENT" > ~/.ssh/id_ed25519
|
|
else
|
|
printf '%s\n' "$KEY_CONTENT" > ~/.ssh/id_ed25519
|
|
fi
|
|
chmod 0600 ~/.ssh/id_ed25519
|
|
|
|
if ! ssh-keygen -y -f ~/.ssh/id_ed25519 >/dev/null 2>&1; then
|
|
echo "Invalid private key content from $KEY_SOURCE"
|
|
exit 1
|
|
fi
|
|
|
|
- name: Verify SSH keypair match
|
|
run: |
|
|
if ! ssh-keygen -y -f ~/.ssh/id_ed25519 >/tmp/key.pub 2>/tmp/key.err; then
|
|
echo "Invalid private key content in SSH_KEY_PRIVATE/KUBEADM_SSH_PRIVATE_KEY"
|
|
cat /tmp/key.err
|
|
exit 1
|
|
fi
|
|
|
|
printf '%s\n' "${{ secrets.SSH_KEY_PUBLIC }}" | tr -d '\r' > /tmp/secret.pub
|
|
if ! ssh-keygen -lf /tmp/secret.pub >/tmp/secret.fp 2>/tmp/secret.err; then
|
|
echo "Invalid SSH_KEY_PUBLIC format"
|
|
cat /tmp/secret.err
|
|
exit 1
|
|
fi
|
|
|
|
PRIV_FP="$(ssh-keygen -lf /tmp/key.pub | awk '{print $2}')"
|
|
PUB_FP="$(awk '{print $2}' /tmp/secret.fp)"
|
|
|
|
echo "private fingerprint: $PRIV_FP"
|
|
echo "public fingerprint: $PUB_FP"
|
|
|
|
if [ "$PRIV_FP" != "$PUB_FP" ]; then
|
|
echo "SSH_KEY_PRIVATE does not match SSH_KEY_PUBLIC. Update secrets with the same keypair."
|
|
exit 1
|
|
fi
|
|
|
|
- name: Create kubeadm inventory from Terraform outputs
|
|
env:
|
|
KUBEADM_SSH_USER: ${{ secrets.KUBEADM_SSH_USER }}
|
|
KUBEADM_SUBNET_PREFIX: ${{ secrets.KUBEADM_SUBNET_PREFIX }}
|
|
run: |
|
|
set -euo pipefail
|
|
TF_OUTPUT_JSON=""
|
|
for attempt in 1 2 3 4 5 6; do
|
|
echo "Inventory render attempt $attempt/6"
|
|
TF_OUTPUT_JSON="$(terraform -chdir=terraform output -json)"
|
|
if printf '%s' "$TF_OUTPUT_JSON" | ./nixos/kubeadm/scripts/render-inventory-from-tf-output.py > nixos/kubeadm/scripts/inventory.env; then
|
|
exit 0
|
|
fi
|
|
|
|
if [ "$attempt" -lt 6 ]; then
|
|
echo "VM IPv4s not available yet; waiting 30s before retry"
|
|
sleep 30
|
|
fi
|
|
done
|
|
|
|
echo "Falling back to SSH-based inventory discovery"
|
|
printf '%s' "$TF_OUTPUT_JSON" | ./nixos/kubeadm/scripts/discover-inventory-from-ssh.py > nixos/kubeadm/scripts/inventory.env
|
|
|
|
- name: Ensure nix and nixos-rebuild
|
|
env:
|
|
NIX_CONFIG: experimental-features = nix-command flakes
|
|
run: |
|
|
if [ ! -x /nix/var/nix/profiles/default/bin/nix ] && ! command -v nix >/dev/null 2>&1; then
|
|
if [ "$(id -u)" -eq 0 ]; then
|
|
mkdir -p /nix
|
|
chown root:root /nix
|
|
chmod 0755 /nix
|
|
|
|
if ! getent group nixbld >/dev/null 2>&1; then
|
|
groupadd --system nixbld
|
|
fi
|
|
|
|
for i in $(seq 1 10); do
|
|
if ! id "nixbld$i" >/dev/null 2>&1; then
|
|
useradd --system --create-home --home-dir /var/empty --shell /usr/sbin/nologin "nixbld$i"
|
|
fi
|
|
usermod -a -G nixbld "nixbld$i"
|
|
done
|
|
fi
|
|
sh <(curl -L https://nixos.org/nix/install) --no-daemon
|
|
fi
|
|
|
|
if [ -f "$HOME/.nix-profile/etc/profile.d/nix.sh" ]; then
|
|
. "$HOME/.nix-profile/etc/profile.d/nix.sh"
|
|
elif [ -f "/root/.nix-profile/etc/profile.d/nix.sh" ]; then
|
|
. /root/.nix-profile/etc/profile.d/nix.sh
|
|
fi
|
|
|
|
export PATH="$HOME/.nix-profile/bin:/root/.nix-profile/bin:/nix/var/nix/profiles/default/bin:$PATH"
|
|
|
|
nix --version
|
|
nix profile install nixpkgs#nixos-rebuild
|
|
|
|
- name: Rebuild and bootstrap/reconcile kubeadm cluster
|
|
env:
|
|
NIX_CONFIG: experimental-features = nix-command flakes
|
|
FAST_MODE: "1"
|
|
WORKER_PARALLELISM: "3"
|
|
REBUILD_TIMEOUT: "45m"
|
|
REBUILD_RETRIES: "2"
|
|
run: |
|
|
if [ -f "$HOME/.nix-profile/etc/profile.d/nix.sh" ]; then
|
|
. "$HOME/.nix-profile/etc/profile.d/nix.sh"
|
|
elif [ -f "/root/.nix-profile/etc/profile.d/nix.sh" ]; then
|
|
. /root/.nix-profile/etc/profile.d/nix.sh
|
|
fi
|
|
|
|
export PATH="$HOME/.nix-profile/bin:/root/.nix-profile/bin:/nix/var/nix/profiles/default/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:$PATH"
|
|
|
|
./nixos/kubeadm/scripts/rebuild-and-bootstrap.sh
|