Some checks failed
Terraform Plan / Terraform Plan (push) Failing after 10s
Make Terraform the source of truth for node IPs, remove guest-agent/SSH discovery from the normal workflow path, simplify the bootstrap controller to a fresh-run flow, and swap the initial CNI to Flannel so cluster readiness is easier to prove before reintroducing more complex reconcile behavior.
210 lines
7.7 KiB
YAML
210 lines
7.7 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 }}
|
|
run: |
|
|
set -euo pipefail
|
|
terraform -chdir=terraform output -json | ./nixos/kubeadm/scripts/render-inventory-from-tf-output.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
|