- name: Validate bootstrap inputs ansible.builtin.assert: that: - k3s_platform_git_url | length > 0 - k3s_platform_git_branch | length > 0 - k3s_platform_flux_root_path | length > 0 - k3s_platform_git_auth_mode | default('public') | lower in ['public', 'ssh', 'https-basic', 'https-bearer', ''] fail_msg: "k3s platform bootstrap requires git url, branch, root path, and a supported git auth mode." - name: Resolve flux git auth inputs ansible.builtin.set_fact: k3s_platform_git_auth_mode_effective: "{{ (k3s_platform_git_auth_mode | default('public')) | lower | trim }}" no_log: true - name: Resolve SSH git private key when needed ansible.builtin.set_fact: k3s_platform_git_private_key_resolved: >- {{ k3s_platform_git_private_key if (k3s_platform_git_auth_mode_effective == 'ssh' and k3s_platform_git_private_key | default('') | length > 0) else ( lookup('ansible.builtin.file', k3s_platform_git_private_key_path) | trim if (k3s_platform_git_auth_mode_effective == 'ssh') else '' ) }} no_log: true - name: Assert SSH auth material is available ansible.builtin.assert: that: - k3s_platform_git_private_key_resolved | length > 0 fail_msg: "SSH auth requires a git deploy key content or readable key path." when: - k3s_platform_git_auth_mode_effective == 'ssh' - name: Assert HTTPS basic auth material is available ansible.builtin.assert: that: - k3s_platform_git_http_username | default('') | length > 0 - k3s_platform_git_http_password | default('') | length > 0 fail_msg: "HTTPS basic auth requires username and password/token." when: - k3s_platform_git_auth_mode_effective == 'https-basic' - name: Assert HTTPS bearer auth material is available ansible.builtin.assert: that: - k3s_platform_git_bearer_token | default('') | length > 0 fail_msg: "HTTPS bearer auth requires a bearer token." when: - k3s_platform_git_auth_mode_effective == 'https-bearer' - name: Normalize public git auth mode ansible.builtin.set_fact: k3s_platform_git_auth_mode_effective: public when: - k3s_platform_git_auth_mode_effective in ['', 'public', 'none'] - name: Install bootstrap dependencies ansible.builtin.package: name: - curl - git - openssh-client - ca-certificates - python3-pip - tar state: present - name: Ensure k3s config directory exists ansible.builtin.file: path: "{{ k3s_platform_k3s_config_path | dirname }}" state: directory mode: "0755" - name: Ensure install directory exists ansible.builtin.file: path: "{{ k3s_platform_install_dir }}" state: directory mode: "0755" - name: Render k3s config ansible.builtin.template: src: k3s-config.yaml.j2 dest: "{{ k3s_platform_k3s_config_path }}" mode: "0644" - name: Check whether k3s is already installed ansible.builtin.shell: | set -euo pipefail if { [ -x /usr/local/bin/k3s ] || [ -x /usr/bin/k3s ]; } && [ -f /etc/systemd/system/k3s.service ]; then echo "installed" exit 0 fi echo "missing" args: executable: /bin/bash register: k3s_platform_k3s_install_state changed_when: false - name: Download k3s installer script ansible.builtin.shell: | set -euo pipefail if [ "{{ k3s_platform_k3s_install_state.stdout | default('missing') }}" = "installed" ]; then exit 0 fi echo "[k3s-bootstrap] downloading installer from {{ k3s_platform_k3s_install_script_url }}" rm -f "{{ k3s_platform_k3s_install_script_path }}" curl -fL \ --retry 5 \ --retry-all-errors \ --connect-timeout 15 \ --max-time 1800 \ -o "{{ k3s_platform_k3s_install_script_path }}" \ "{{ k3s_platform_k3s_install_script_url }}" chmod 0755 "{{ k3s_platform_k3s_install_script_path }}" args: executable: /bin/bash when: - k3s_platform_k3s_install_state.stdout | default('missing') != 'installed' - name: Run k3s installer script ansible.builtin.shell: | set -euo pipefail if [ "{{ k3s_platform_k3s_install_state.stdout | default('missing') }}" = "installed" ]; then exit 0 fi echo "[k3s-bootstrap] starting installer at $(date -Is)" bash -x "{{ k3s_platform_k3s_install_script_path }}" \ 2>&1 | tee "{{ k3s_platform_k3s_install_log_path }}" echo "[k3s-bootstrap] finished installer at $(date -Is)" args: executable: /bin/bash async: "{{ k3s_platform_k3s_install_timeout_seconds }}" poll: 15 when: - k3s_platform_k3s_install_state.stdout | default('missing') != 'installed' - name: Wait for k3s binary and unit file to appear ansible.builtin.shell: | set -euo pipefail for attempt in $(seq 1 30); do k3s_state="missing" unit_state="missing" if command -v k3s >/dev/null 2>&1; then k3s_state="present" fi if [ -f /etc/systemd/system/k3s.service ]; then unit_state="present" fi echo "[k3s-bootstrap] wait ${attempt}/30: k3s=${k3s_state}, unit=${unit_state}" if [ "${k3s_state}" = "present" ] && [ "${unit_state}" = "present" ]; then echo "[k3s-bootstrap] k3s binary and unit file detected" exit 0 fi sleep 2 done echo "[k3s-bootstrap] timed out waiting for k3s binary and unit file" >&2 exit 1 args: executable: /bin/bash - name: Reload systemd after k3s install ansible.builtin.systemd: daemon_reload: true - name: Ensure k3s service is enabled and started ansible.builtin.systemd: name: k3s state: started enabled: true - name: Install helm ansible.builtin.shell: | set -euo pipefail if command -v helm >/dev/null 2>&1; then exit 0 fi curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash args: executable: /bin/bash - name: Install flux cli ansible.builtin.shell: | set -euo pipefail if command -v flux >/dev/null 2>&1; then exit 0 fi curl -s https://fluxcd.io/install.sh | bash args: executable: /bin/bash - name: Ensure flux namespace exists ansible.builtin.shell: | set -euo pipefail export KUBECONFIG="{{ k3s_platform_kubeconfig_path }}" kubectl get namespace "{{ k3s_platform_flux_namespace }}" >/dev/null 2>&1 || kubectl create namespace "{{ k3s_platform_flux_namespace }}" args: executable: /bin/bash - name: Install flux controllers ansible.builtin.shell: | set -euo pipefail export KUBECONFIG="{{ k3s_platform_kubeconfig_path }}" flux install --namespace "{{ k3s_platform_flux_namespace }}" args: executable: /bin/bash - name: Wait for flux controllers to become ready ansible.builtin.shell: | set -euo pipefail export KUBECONFIG="{{ k3s_platform_kubeconfig_path }}" kubectl -n "{{ k3s_platform_flux_namespace }}" rollout status deployment/source-controller --timeout=300s kubectl -n "{{ k3s_platform_flux_namespace }}" rollout status deployment/kustomize-controller --timeout=300s kubectl -n "{{ k3s_platform_flux_namespace }}" rollout status deployment/helm-controller --timeout=300s kubectl -n "{{ k3s_platform_flux_namespace }}" rollout status deployment/notification-controller --timeout=300s args: executable: /bin/bash - name: Wait for Kubernetes API to become ready for bootstrap resources ansible.builtin.shell: | set -euo pipefail export KUBECONFIG="{{ k3s_platform_kubeconfig_path }}" kubectl get --raw=/readyz >/dev/null args: executable: /bin/bash register: k3s_platform_apiserver_ready changed_when: false retries: 24 delay: 5 until: k3s_platform_apiserver_ready.rc == 0 - name: Ensure Vault bootstrap namespace exists ansible.builtin.shell: | set -euo pipefail export KUBECONFIG="{{ k3s_platform_kubeconfig_path }}" kubectl get namespace extsvc >/dev/null 2>&1 || kubectl create namespace extsvc args: executable: /bin/bash when: - k3s_platform_manage_extsvc | bool - name: Ensure platform namespace exists for bootstrap secrets ansible.builtin.shell: | set -euo pipefail export KUBECONFIG="{{ k3s_platform_kubeconfig_path }}" kubectl get namespace platform >/dev/null 2>&1 || kubectl create namespace platform args: executable: /bin/bash - name: Ensure core namespaces exist for bootstrap secrets ansible.builtin.shell: | set -euo pipefail export KUBECONFIG="{{ k3s_platform_kubeconfig_path }}" for namespace in database core-pre core-prod; do kubectl get namespace "${namespace}" >/dev/null 2>&1 || kubectl create namespace "${namespace}" done args: executable: /bin/bash - name: Create runtime bootstrap configmap for platform inputs ansible.builtin.shell: | set -euo pipefail export KUBECONFIG="{{ k3s_platform_kubeconfig_path }}" kubectl -n platform create configmap platform-bootstrap-env \ --from-literal=GITOPS_REPO="{{ bootstrap_gitops_repo | default('') }}" \ --from-literal=VAULT_URL="{{ bootstrap_vault_url | default('') }}" \ --from-literal=CLOUDFLARE_ZONE_ID="{{ cloudflare_zone_id | default('') }}" \ --dry-run=client -o yaml | kubectl apply -f - args: executable: /bin/bash when: - (bootstrap_gitops_repo | default('')) | length > 0 - (bootstrap_vault_url | default('')) | length > 0 - (cloudflare_zone_id | default('')) | length > 0 - name: Create runtime bootstrap secret for platform inputs ansible.builtin.shell: | set -euo pipefail export KUBECONFIG="{{ k3s_platform_kubeconfig_path }}" kubectl -n platform create secret generic platform-bootstrap-secrets \ --from-literal=VAULT_TOKEN="{{ bootstrap_vault_token | default('') }}" \ --from-literal=VAULT_ROOT_TOKEN="{{ vault_root_token | default('') }}" \ --from-literal=CLOUDFLARE_DNS_API_TOKEN="{{ lookup('env', 'CLOUDFLARE_DNS_API_TOKEN') | default('', true) }}" \ --from-literal=CLOUDFLARE_API_TOKEN="{{ cloudflare_api_token | default('') }}" \ --dry-run=client -o yaml | kubectl apply -f - args: executable: /bin/bash no_log: true when: - (bootstrap_vault_token | default('')) | length > 0 - (vault_root_token | default('')) | length > 0 - (lookup('env', 'CLOUDFLARE_DNS_API_TOKEN') | default('', true)) | length > 0 - (cloudflare_api_token | default('')) | length > 0 - name: Create runtime bootstrap secret for Vault seeding ansible.builtin.shell: | set -euo pipefail export KUBECONFIG="{{ k3s_platform_kubeconfig_path }}" kubectl -n extsvc create secret generic vault-bootstrap \ --from-literal=rootToken="{{ vault_root_token | default('') }}" \ --from-literal=cloudflareApiToken="{{ cloudflare_api_token | default('') }}" \ --dry-run=client -o yaml | kubectl apply -f - args: executable: /bin/bash no_log: true when: - k3s_platform_manage_extsvc | bool - (vault_root_token | default('')) | length > 0 - (cloudflare_api_token | default('')) | length > 0 - name: Create runtime bootstrap secret for ExternalDNS seeding ansible.builtin.shell: | set -euo pipefail export KUBECONFIG="{{ k3s_platform_kubeconfig_path }}" kubectl -n platform create secret generic cloudflare-api-token \ --from-literal=api-token="{{ cloudflare_api_token | default('') }}" \ --dry-run=client -o yaml | kubectl apply -f - args: executable: /bin/bash no_log: true when: - (cloudflare_api_token | default('')) | length > 0 - name: Render direct Helm values for caddy ansible.builtin.copy: content: "{{ (k3s_platform_values.components.caddy['values'] | default({})) | to_nice_yaml(indent=2) }}" dest: /tmp/platform-caddy-values.yaml mode: "0600" - name: Render direct Helm values for apisix ansible.builtin.copy: content: "{{ (k3s_platform_values.components.apisix['values'] | default({})) | to_nice_yaml(indent=2) }}" dest: /tmp/platform-apisix-values.yaml mode: "0600" - name: Render direct Helm values for external-dns ansible.builtin.copy: content: "{{ (k3s_platform_values.components.externalDns['values'] | default({})) | to_nice_yaml(indent=2) }}" dest: /tmp/platform-external-dns-values.yaml mode: "0600" - name: Render native APISIX ingress manifest ansible.builtin.template: src: apisix-ingress.yaml.j2 dest: /tmp/platform-apisix-ingress.yaml mode: "0600" when: - k3s_platform_values.apisixIngress.enabled | default(false) - name: Render native shared TLS sync manifest ansible.builtin.template: src: shared-tls-secret-sync.yaml.j2 dest: /tmp/platform-shared-tls-secret-sync.yaml mode: "0600" when: - k3s_platform_values.components.sharedTlsSecretSync.enabled | default(false) - name: Render native PostgreSQL TLS ingress manifest ansible.builtin.template: src: postgresql-tls-ingress.yaml.j2 dest: /tmp/platform-postgresql-tls-ingress.yaml mode: "0600" when: - k3s_platform_values.postgresqlTlsIngress.enabled | default(false) - name: Ensure platform Helm repositories are configured ansible.builtin.shell: | set -euo pipefail if helm repo list | awk '{print $1}' | grep -qx '{{ item.name }}'; then helm repo add --force-update '{{ item.name }}' '{{ item.url }}' >/dev/null else helm repo add '{{ item.name }}' '{{ item.url }}' >/dev/null fi args: executable: /bin/bash loop: "{{ k3s_platform_helm_repos }}" - name: Refresh platform Helm repositories ansible.builtin.shell: | set -euo pipefail helm repo update >/dev/null args: executable: /bin/bash - name: Remove legacy Flux-managed platform release objects ansible.builtin.shell: | set -euo pipefail export KUBECONFIG="{{ k3s_platform_kubeconfig_path }}" kubectl -n platform delete helmrelease external-secrets k3s-platform caddy apisix external-dns reloader --ignore-not-found=true kubectl -n platform delete ocirepository k3s-platform-chart --ignore-not-found=true kubectl -n platform delete configmap k3s-platform-values --ignore-not-found=true args: executable: /bin/bash - name: Install external-secrets directly with Helm ansible.builtin.shell: | set -euo pipefail export KUBECONFIG="{{ k3s_platform_kubeconfig_path }}" helm upgrade --install external-secrets external-secrets/external-secrets \ --namespace platform \ --create-namespace \ --version "{{ k3s_platform_external_secrets_chart_version }}" \ --set installCRDs=true \ --wait \ --timeout 10m args: executable: /bin/bash - name: Install reloader directly with Helm ansible.builtin.shell: | set -euo pipefail export KUBECONFIG="{{ k3s_platform_kubeconfig_path }}" helm upgrade --install reloader stakater/reloader \ --namespace platform \ --create-namespace \ --version "{{ k3s_platform_reloader_chart_version }}" \ --wait \ --timeout 10m args: executable: /bin/bash - name: Install caddy directly with Helm ansible.builtin.shell: | set -euo pipefail export KUBECONFIG="{{ k3s_platform_kubeconfig_path }}" helm upgrade --install "{{ k3s_platform_values.components.caddy.releaseName }}" caddy-ingress/caddy-ingress-controller \ --namespace platform \ --create-namespace \ --version "{{ k3s_platform_caddy_chart_version }}" \ -f /tmp/platform-caddy-values.yaml \ --wait \ --timeout 10m args: executable: /bin/bash when: - k3s_platform_values.components.caddy.enabled | default(false) - name: Install apisix directly with Helm ansible.builtin.shell: | set -euo pipefail export KUBECONFIG="{{ k3s_platform_kubeconfig_path }}" helm upgrade --install "{{ k3s_platform_values.components.apisix.releaseName }}" apisix/apisix \ --namespace platform \ --create-namespace \ --version "{{ k3s_platform_apisix_chart_version }}" \ -f /tmp/platform-apisix-values.yaml \ --wait \ --timeout 10m args: executable: /bin/bash when: - k3s_platform_values.components.apisix.enabled | default(false) - name: Install external-dns directly with Helm ansible.builtin.shell: | set -euo pipefail export KUBECONFIG="{{ k3s_platform_kubeconfig_path }}" helm upgrade --install "{{ k3s_platform_values.components.externalDns.releaseName }}" external-dns/external-dns \ --namespace platform \ --create-namespace \ --version "{{ k3s_platform_external_dns_chart_version }}" \ -f /tmp/platform-external-dns-values.yaml \ --wait \ --timeout 10m args: executable: /bin/bash when: - k3s_platform_values.components.externalDns.enabled | default(false) - name: Apply native APISIX ingress manifest ansible.builtin.shell: | set -euo pipefail export KUBECONFIG="{{ k3s_platform_kubeconfig_path }}" kubectl apply -f /tmp/platform-apisix-ingress.yaml args: executable: /bin/bash when: - k3s_platform_values.apisixIngress.enabled | default(false) - name: Apply native shared TLS sync manifest ansible.builtin.shell: | set -euo pipefail export KUBECONFIG="{{ k3s_platform_kubeconfig_path }}" kubectl apply -f /tmp/platform-shared-tls-secret-sync.yaml args: executable: /bin/bash when: - k3s_platform_values.components.sharedTlsSecretSync.enabled | default(false) - name: Apply native PostgreSQL TLS ingress manifest ansible.builtin.shell: | set -euo pipefail export KUBECONFIG="{{ k3s_platform_kubeconfig_path }}" kubectl apply -f /tmp/platform-postgresql-tls-ingress.yaml args: executable: /bin/bash when: - k3s_platform_values.postgresqlTlsIngress.enabled | default(false) - name: Wait for direct platform Helm deployments to become available ansible.builtin.shell: | set -euo pipefail export KUBECONFIG="{{ k3s_platform_kubeconfig_path }}" kubectl -n platform wait deployment \ -l {{ item.selector }} \ --for=condition=Available \ --timeout=300s args: executable: /bin/bash loop: - { name: "external-secrets", selector: "app.kubernetes.io/instance=external-secrets" } - { name: "reloader", selector: "release=reloader" } - { name: "caddy", selector: "app.kubernetes.io/instance=caddy" } - { name: "apisix", selector: "app.kubernetes.io/instance=apisix" } - { name: "external-dns", selector: "app.kubernetes.io/instance=external-dns" } - name: Render Vault bootstrap values file ansible.builtin.template: src: vault-bootstrap-values.yaml.j2 dest: /tmp/vault-bootstrap-values.yaml mode: "0600" when: - k3s_platform_manage_extsvc | bool - name: Bootstrap Vault server with Helm ansible.builtin.shell: | set -euo pipefail export KUBECONFIG="{{ k3s_platform_kubeconfig_path }}" helm repo add hashicorp https://helm.releases.hashicorp.com >/dev/null 2>&1 || true helm repo update hashicorp >/dev/null helm upgrade --install "{{ k3s_platform_vault_release_name }}" hashicorp/vault \ --namespace extsvc \ --create-namespace \ --version "{{ k3s_platform_vault_chart_version }}" \ -f /tmp/vault-bootstrap-values.yaml args: executable: /bin/bash when: - k3s_platform_manage_extsvc | bool - name: Wait for Vault server rollout ansible.builtin.shell: | set -euo pipefail export KUBECONFIG="{{ k3s_platform_kubeconfig_path }}" kubectl -n extsvc wait --for=condition=Ready pod \ -l app.kubernetes.io/name={{ k3s_platform_vault_release_name }} \ --timeout=300s args: executable: /bin/bash when: - k3s_platform_manage_extsvc | bool - k3s_platform_vault_init_phase == 'pre_flux' - name: Discover Vault pod name ansible.builtin.shell: | set -euo pipefail export KUBECONFIG="{{ k3s_platform_kubeconfig_path }}" kubectl -n extsvc get pod \ -l app.kubernetes.io/name={{ k3s_platform_vault_release_name }} \ -o jsonpath='{.items[0].metadata.name}' register: k3s_platform_vault_pod_name_result changed_when: false args: executable: /bin/bash when: - k3s_platform_manage_extsvc | bool - k3s_platform_vault_init_phase == 'pre_flux' - name: Capture Vault status ansible.builtin.command: argv: - kubectl - -n - extsvc - exec - "{{ k3s_platform_vault_pod_name_result.stdout }}" - -- - /bin/vault - status - -format=json register: k3s_platform_vault_status_result changed_when: false failed_when: false when: - k3s_platform_manage_extsvc | bool - k3s_platform_vault_init_phase == 'pre_flux' - name: Parse Vault status JSON ansible.builtin.set_fact: k3s_platform_vault_status: >- {{ ( k3s_platform_vault_status_result.stdout if (k3s_platform_vault_status_result.stdout | default('') | trim | length) > 0 else '{}' ) | from_json }} when: - k3s_platform_manage_extsvc | bool - k3s_platform_vault_init_phase == 'pre_flux' - name: Assert Vault bootstrap mode is valid ansible.builtin.assert: that: - k3s_platform_vault_bootstrap_mode in ['init', 'migrate'] fail_msg: "k3s_platform_vault_bootstrap_mode must be init or migrate." when: - k3s_platform_manage_extsvc | bool - k3s_platform_vault_init_phase == 'pre_flux' - name: Assert migrate mode has inputs when Vault is already initialized ansible.builtin.assert: that: - (vault_root_token | default('')) | length > 0 - (vault_init_json | default('')) | length > 0 fail_msg: "Vault migrate mode requires vault_root_token and vault_init_json." when: - k3s_platform_manage_extsvc | bool - k3s_platform_vault_init_phase == 'pre_flux' - k3s_platform_vault_bootstrap_mode == 'migrate' - k3s_platform_vault_status.initialized | default(false) - name: Assert migrate mode targets an initialized Vault ansible.builtin.assert: that: - k3s_platform_vault_status.initialized | default(false) fail_msg: "Vault migrate mode requires an already initialized Vault instance." when: - k3s_platform_manage_extsvc | bool - k3s_platform_vault_init_phase == 'pre_flux' - k3s_platform_vault_bootstrap_mode == 'migrate' - name: Initialize Vault when requested and not yet initialized ansible.builtin.command: argv: - kubectl - -n - extsvc - exec - "{{ k3s_platform_vault_pod_name_result.stdout }}" - -- - /bin/vault - operator - init - -format=json - -key-shares=1 - -key-threshold=1 register: k3s_platform_vault_init_result when: - k3s_platform_manage_extsvc | bool - k3s_platform_vault_init_phase == 'pre_flux' - k3s_platform_vault_bootstrap_mode == 'init' - not (k3s_platform_vault_status.initialized | default(false)) - name: Store Vault init JSON on disk ansible.builtin.copy: content: "{{ k3s_platform_vault_init_result.stdout }}" dest: "{{ k3s_platform_vault_init_json_path }}" mode: "0600" when: - k3s_platform_manage_extsvc | bool - k3s_platform_vault_init_phase == 'pre_flux' - k3s_platform_vault_init_result is defined - name: Parse Vault init JSON ansible.builtin.set_fact: k3s_platform_vault_init_data: "{{ k3s_platform_vault_init_result.stdout | from_json }}" when: - k3s_platform_manage_extsvc | bool - k3s_platform_vault_init_phase == 'pre_flux' - k3s_platform_vault_init_result is defined - name: Unseal Vault after init ansible.builtin.command: argv: - kubectl - -n - extsvc - exec - "{{ k3s_platform_vault_pod_name_result.stdout }}" - -- - /bin/vault - operator - unseal - "{{ k3s_platform_vault_init_data.unseal_keys_b64[0] }}" when: - k3s_platform_manage_extsvc | bool - k3s_platform_vault_init_phase == 'pre_flux' - k3s_platform_vault_init_result is defined - name: Sync Vault bootstrap secret with generated root token ansible.builtin.shell: | set -euo pipefail export KUBECONFIG="{{ k3s_platform_kubeconfig_path }}" kubectl -n extsvc create secret generic vault-bootstrap \ --from-literal=rootToken="{{ k3s_platform_vault_init_data.root_token }}" \ --from-literal=cloudflareApiToken="{{ cloudflare_api_token | default('') }}" \ --dry-run=client -o yaml | kubectl apply -f - args: executable: /bin/bash no_log: true when: - k3s_platform_manage_extsvc | bool - k3s_platform_vault_init_phase == 'pre_flux' - k3s_platform_vault_init_result is defined - name: Emit Vault bootstrap outputs ansible.builtin.debug: msg: mode: "{{ k3s_platform_vault_bootstrap_mode }}" vault_initialized: "{{ k3s_platform_vault_status.initialized | default(false) }}" vault_sealed: "{{ k3s_platform_vault_status.sealed | default(true) }}" admin_username: "{{ k3s_platform_vault_admin_username }}" init_json_path: "{{ k3s_platform_vault_init_json_path if k3s_platform_vault_init_result is defined else omit }}" root_token: "{{ k3s_platform_vault_init_data.root_token if (k3s_platform_vault_init_result is defined and k3s_platform_vault_allow_sensitive_output) else omit }}" init_json: "{{ k3s_platform_vault_init_result.stdout if (k3s_platform_vault_init_result is defined and k3s_platform_vault_allow_sensitive_output) else omit }}" when: - k3s_platform_manage_extsvc | bool - k3s_platform_vault_init_phase == 'pre_flux' - k3s_platform_vault_bootstrap_mode == 'init' - k3s_platform_vault_init_result is defined - name: Render flux bootstrap env file ansible.builtin.template: src: flux-bootstrap.env.j2 dest: /tmp/flux-bootstrap.env mode: "0600" - name: Write flux git private key file ansible.builtin.copy: content: "{{ k3s_platform_git_private_key_resolved }}\n" dest: /tmp/flux-git-deploy.key mode: "0600" no_log: true when: - k3s_platform_git_auth_mode_effective == 'ssh' - name: Write flux git public key file when provided ansible.builtin.copy: content: "{{ k3s_platform_git_public_key }}\n" dest: /tmp/flux-git-deploy.pub mode: "0644" when: - k3s_platform_git_public_key | length > 0 - k3s_platform_git_auth_mode_effective == 'ssh' - name: Remove flux git auth secret for public repositories ansible.builtin.command: argv: - kubectl - -n - "{{ k3s_platform_flux_namespace }}" - delete - secret - "{{ k3s_platform_flux_source_name }}" - --ignore-not-found environment: KUBECONFIG: "{{ k3s_platform_kubeconfig_path }}" when: - k3s_platform_git_auth_mode_effective in ['public', 'none', ''] - name: Create or update flux git auth secret with SSH ansible.builtin.shell: | set -euo pipefail . /tmp/flux-bootstrap.env flux create secret git "${GIT_SOURCE_NAME}" \ --url="${GIT_URL}" \ --private-key-file=/tmp/flux-git-deploy.key \ --namespace="${FLUX_NAMESPACE}" \ --export | kubectl apply -f - args: executable: /bin/bash environment: KUBECONFIG: "{{ k3s_platform_kubeconfig_path }}" no_log: true when: - k3s_platform_git_auth_mode_effective == 'ssh' - name: Create or update flux git auth secret with HTTPS basic auth ansible.builtin.shell: | set -euo pipefail . /tmp/flux-bootstrap.env cat <- {{ ( k3s_platform_vault_status_result.stdout if (k3s_platform_vault_status_result.stdout | default('') | trim | length) > 0 else '{}' ) | from_json }} when: - k3s_platform_manage_extsvc | bool - k3s_platform_vault_init_phase == 'post_flux' - name: Assert Vault bootstrap mode is valid after Flux bootstrap ansible.builtin.assert: that: - k3s_platform_vault_bootstrap_mode in ['init', 'migrate'] fail_msg: "k3s_platform_vault_bootstrap_mode must be init or migrate." when: - k3s_platform_manage_extsvc | bool - k3s_platform_vault_init_phase == 'post_flux' - name: Assert migrate mode has inputs when Vault is already initialized after Flux bootstrap ansible.builtin.assert: that: - (vault_root_token | default('')) | length > 0 - (vault_init_json | default('')) | length > 0 fail_msg: "Vault migrate mode requires vault_root_token and vault_init_json." when: - k3s_platform_manage_extsvc | bool - k3s_platform_vault_init_phase == 'post_flux' - k3s_platform_vault_bootstrap_mode == 'migrate' - k3s_platform_vault_status.initialized | default(false) - name: Assert migrate mode targets an initialized Vault after Flux bootstrap ansible.builtin.assert: that: - k3s_platform_vault_status.initialized | default(false) fail_msg: "Vault migrate mode requires an already initialized Vault instance." when: - k3s_platform_manage_extsvc | bool - k3s_platform_vault_init_phase == 'post_flux' - k3s_platform_vault_bootstrap_mode == 'migrate' - name: Initialize Vault when requested and not yet initialized after Flux bootstrap ansible.builtin.command: argv: - kubectl - -n - extsvc - exec - "{{ k3s_platform_vault_pod_name_result.stdout }}" - -- - /bin/vault - operator - init - -format=json - -key-shares=1 - -key-threshold=1 register: k3s_platform_vault_init_result when: - k3s_platform_manage_extsvc | bool - k3s_platform_vault_init_phase == 'post_flux' - k3s_platform_vault_bootstrap_mode == 'init' - not (k3s_platform_vault_status.initialized | default(false)) - name: Store Vault init JSON on disk after Flux bootstrap ansible.builtin.copy: content: "{{ k3s_platform_vault_init_result.stdout }}" dest: "{{ k3s_platform_vault_init_json_path }}" mode: "0600" when: - k3s_platform_manage_extsvc | bool - k3s_platform_vault_init_phase == 'post_flux' - k3s_platform_vault_init_result is defined - name: Parse Vault init JSON after Flux bootstrap ansible.builtin.set_fact: k3s_platform_vault_init_data: "{{ k3s_platform_vault_init_result.stdout | from_json }}" when: - k3s_platform_manage_extsvc | bool - k3s_platform_vault_init_phase == 'post_flux' - k3s_platform_vault_init_result is defined - name: Unseal Vault after init and Flux bootstrap ansible.builtin.command: argv: - kubectl - -n - extsvc - exec - "{{ k3s_platform_vault_pod_name_result.stdout }}" - -- - /bin/vault - operator - unseal - "{{ k3s_platform_vault_init_data.unseal_keys_b64[0] }}" when: - k3s_platform_manage_extsvc | bool - k3s_platform_vault_init_phase == 'post_flux' - k3s_platform_vault_init_result is defined - name: Sync Vault bootstrap secret with generated root token after Flux bootstrap ansible.builtin.shell: | set -euo pipefail export KUBECONFIG="{{ k3s_platform_kubeconfig_path }}" kubectl -n extsvc create secret generic vault-bootstrap \ --from-literal=rootToken="{{ k3s_platform_vault_init_data.root_token }}" \ --from-literal=cloudflareApiToken="{{ cloudflare_api_token | default('') }}" \ --dry-run=client -o yaml | kubectl apply -f - args: executable: /bin/bash no_log: true when: - k3s_platform_manage_extsvc | bool - k3s_platform_vault_init_phase == 'post_flux' - k3s_platform_vault_init_result is defined - name: Emit Vault bootstrap outputs after Flux bootstrap ansible.builtin.debug: msg: mode: "{{ k3s_platform_vault_bootstrap_mode }}" vault_initialized: "{{ k3s_platform_vault_status.initialized | default(false) }}" vault_sealed: "{{ k3s_platform_vault_status.sealed | default(true) }}" admin_username: "{{ k3s_platform_vault_admin_username }}" init_json_path: "{{ k3s_platform_vault_init_json_path if k3s_platform_vault_init_result is defined else omit }}" root_token: "{{ k3s_platform_vault_init_data.root_token if (k3s_platform_vault_init_result is defined and k3s_platform_vault_allow_sensitive_output) else omit }}" init_json: "{{ k3s_platform_vault_init_result.stdout if (k3s_platform_vault_init_result is defined and k3s_platform_vault_allow_sensitive_output) else omit }}" when: - k3s_platform_manage_extsvc | bool - k3s_platform_vault_init_phase == 'post_flux' - k3s_platform_vault_bootstrap_mode == 'init' - k3s_platform_vault_init_result is defined