playbooks/roles/vhosts/k3s_platform_bootstrap/tasks/main.yml

1082 lines
36 KiB
YAML

- 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 <<EOF | kubectl apply -f -
apiVersion: v1
kind: Secret
metadata:
name: ${GIT_SOURCE_NAME}
namespace: ${FLUX_NAMESPACE}
type: Opaque
stringData:
username: {{ k3s_platform_git_http_username | to_json }}
password: {{ k3s_platform_git_http_password | to_json }}
EOF
args:
executable: /bin/bash
environment:
KUBECONFIG: "{{ k3s_platform_kubeconfig_path }}"
no_log: true
when:
- k3s_platform_git_auth_mode_effective == 'https-basic'
- name: Create or update flux git auth secret with HTTPS bearer token
ansible.builtin.shell: |
set -euo pipefail
. /tmp/flux-bootstrap.env
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Secret
metadata:
name: ${GIT_SOURCE_NAME}
namespace: ${FLUX_NAMESPACE}
type: Opaque
stringData:
bearerToken: {{ k3s_platform_git_bearer_token | to_json }}
EOF
args:
executable: /bin/bash
environment:
KUBECONFIG: "{{ k3s_platform_kubeconfig_path }}"
no_log: true
when:
- k3s_platform_git_auth_mode_effective == 'https-bearer'
- name: Apply root Flux source and kustomization
ansible.builtin.shell: |
set -euo pipefail
export KUBECONFIG="{{ k3s_platform_kubeconfig_path }}"
. /tmp/flux-bootstrap.env
if [ "{{ k3s_platform_git_auth_mode_effective }}" = "public" ] || [ "{{ k3s_platform_git_auth_mode_effective }}" = "none" ] || [ -z "{{ k3s_platform_git_auth_mode_effective }}" ]; then
SECRET_REF_BLOCK=""
else
SECRET_REF_BLOCK=" secretRef:\n name: ${GIT_SOURCE_NAME}\n"
fi
cat <<EOF | kubectl apply -f -
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
name: ${GIT_SOURCE_NAME}
namespace: ${FLUX_NAMESPACE}
spec:
interval: 1m0s
url: ${GIT_URL}
ref:
branch: ${GIT_BRANCH}
${SECRET_REF_BLOCK}
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: platform-root
namespace: ${FLUX_NAMESPACE}
spec:
interval: 5m0s
sourceRef:
kind: GitRepository
name: ${GIT_SOURCE_NAME}
path: ${FLUX_ROOT_PATH}
prune: true
wait: true
timeout: 5m0s
EOF
args:
executable: /bin/bash
- name: Reconcile root Flux kustomization
ansible.builtin.shell: |
set -euo pipefail
export KUBECONFIG="{{ k3s_platform_kubeconfig_path }}"
flux reconcile source git "{{ k3s_platform_flux_source_name }}" \
--namespace "{{ k3s_platform_flux_namespace }}" \
--timeout=5m
flux reconcile kustomization platform-root \
--namespace "{{ k3s_platform_flux_namespace }}" \
--with-source \
--timeout=10m
args:
executable: /bin/bash
async: 1200
poll: 0
changed_when: true
- name: Verify flux resources
ansible.builtin.shell: |
set -euo pipefail
export KUBECONFIG="{{ k3s_platform_kubeconfig_path }}"
kubectl -n "{{ k3s_platform_flux_namespace }}" get gitrepositories,kustomizations
args:
executable: /bin/bash
- name: Verify platform namespaces exist after Flux bootstrap
ansible.builtin.shell: |
set -euo pipefail
export KUBECONFIG="{{ k3s_platform_kubeconfig_path }}"
kubectl get namespace "{{ item }}"
args:
executable: /bin/bash
loop: "{{ k3s_platform_namespaces }}"
- name: Wait for Vault server rollout after Flux bootstrap
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 == 'post_flux'
- name: Discover Vault pod name after Flux bootstrap
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 == 'post_flux'
- name: Capture Vault status after Flux bootstrap
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 == 'post_flux'
- name: Parse Vault status JSON after Flux bootstrap
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 == '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