---
# showcase/k3s-worker.yml — K3s agent (worker node) setup
#
# Discovers the control plane instance via Incus API, reads the join
# token from the control instance, and installs K3s agent joining
# the discovered control plane.
#
# Note: The Aether blueprint profile should include:
#   security.nesting: "true"

# --- Step 1: Find the control plane from the deployment manifest ---

- name: Find control plane instances from deployment
  ansible.builtin.set_fact:
    control_instances: >-
      {{ ffsdn_instances | selectattr('component_name', 'equalto', 'control') | list }}

- name: Validate control plane found
  ansible.builtin.assert:
    that:
      - control_instances | length > 0
    fail_msg: >-
      No control component found in ffsdn_instances. The blueprint must
      include a control component for worker nodes to join.

- name: Select control plane instance
  ansible.builtin.set_fact:
    control_instance:
      name: "{{ control_instances[0].name }}"
      ip: "{{ control_instances[0].ip }}"

- name: Display control plane target
  ansible.builtin.debug:
    msg: "Joining control plane: {{ control_instance.name }} at {{ control_instance.ip }}"

# --- Step 2: Wait for and read the join token ---

- name: Wait for K3s join token to be available (up to 10 minutes)
  ansible.builtin.uri:
    url: "{{ incus_api }}/1.0/instances/{{ control_instance.name }}/files?path=/var/lib/rancher/k3s/server/token"
    method: GET
    client_cert: "{{ incus_cert }}"
    client_key: "{{ incus_key }}"
    validate_certs: false
    return_content: true
    status_code: [200, 404]
  register: k3s_token_response
  until: k3s_token_response.status == 200
  retries: 60
  delay: 10

- name: Extract join token
  ansible.builtin.set_fact:
    k3s_server_url: "https://{{ control_instance.ip }}:6443"
    k3s_token: "{{ k3s_token_response.content | trim }}"

# --- Step 2b: Add ACL rules for K3s communication ---

- name: Build control ACL name
  ansible.builtin.set_fact:
    control_acl_name: "52-{{ control_instance.name }}-aether-acl"

- name: Read current control ACL
  ansible.builtin.uri:
    url: "{{ incus_api }}/1.0/network-acls/{{ control_acl_name }}"
    method: GET
    client_cert: "{{ incus_cert }}"
    client_key: "{{ incus_key }}"
    validate_certs: false
    return_content: true
    status_code: [200, 404]
  register: control_acl_response

- name: Build updated control ingress rules (allow worker to reach K3s API)
  ansible.builtin.set_fact:
    updated_control_ingress: >-
      {{ (control_acl_response.json.metadata.ingress | default([])) + [
        {
          'action': 'allow',
          'state': 'enabled',
          'source': (ffsdn_instance_ip | default('')) + '/32',
          'destination': control_instance.ip + '/32',
          'protocol': 'tcp',
          'destination_port': '6443,10250',
          'description': 'Allow ' + ffsdn_instance_name + ' to control K3s TCP ports'
        },
        {
          'action': 'allow',
          'state': 'enabled',
          'source': (ffsdn_instance_ip | default('')) + '/32',
          'destination': control_instance.ip + '/32',
          'protocol': 'udp',
          'destination_port': '8472',
          'description': 'Allow ' + ffsdn_instance_name + ' to control VXLAN'
        }
      ] }}
  when: control_acl_response.status == 200

- name: Update control ACL with worker access rule
  ansible.builtin.uri:
    url: "{{ incus_api }}/1.0/network-acls/{{ control_acl_name }}"
    method: PATCH
    client_cert: "{{ incus_cert }}"
    client_key: "{{ incus_key }}"
    validate_certs: false
    body_format: json
    body:
      ingress: "{{ updated_control_ingress }}"
    status_code: [200]
  when:
    - control_acl_response.status == 200
    - updated_control_ingress is defined

- name: Build worker ACL name
  ansible.builtin.set_fact:
    worker_acl_name: "52-{{ ffsdn_instance_name }}-aether-acl"

- name: Read current worker ACL
  ansible.builtin.uri:
    url: "{{ incus_api }}/1.0/network-acls/{{ worker_acl_name }}"
    method: GET
    client_cert: "{{ incus_cert }}"
    client_key: "{{ incus_key }}"
    validate_certs: false
    return_content: true
    status_code: [200, 404]
  register: worker_acl_response

- name: Build updated worker ingress rules (allow control to reach kubelet)
  ansible.builtin.set_fact:
    updated_worker_ingress: >-
      {{ (worker_acl_response.json.metadata.ingress | default([])) + [
        {
          'action': 'allow',
          'state': 'enabled',
          'source': control_instance.ip + '/32',
          'destination': (ffsdn_instance_ip | default('')) + '/32',
          'protocol': 'tcp',
          'destination_port': '10250',
          'description': 'Allow control to reach ' + ffsdn_instance_name + ' kubelet'
        },
        {
          'action': 'allow',
          'state': 'enabled',
          'source': control_instance.ip + '/32',
          'destination': (ffsdn_instance_ip | default('')) + '/32',
          'protocol': 'udp',
          'destination_port': '8472',
          'description': 'Allow control VXLAN to ' + ffsdn_instance_name
        }
      ] }}
  when: worker_acl_response.status == 200

- name: Update worker ACL with control access rule
  ansible.builtin.uri:
    url: "{{ incus_api }}/1.0/network-acls/{{ worker_acl_name }}"
    method: PATCH
    client_cert: "{{ incus_cert }}"
    client_key: "{{ incus_key }}"
    validate_certs: false
    body_format: json
    body:
      ingress: "{{ updated_worker_ingress }}"
    status_code: [200]
  when:
    - worker_acl_response.status == 200
    - updated_worker_ingress is defined

- name: Report ACL updates
  ansible.builtin.debug:
    msg: >-
      ACL rules added: {{ ffsdn_instance_name }} ↔ {{ control_instance.name }}
      (K3s API 6443, kubelet 10250, VXLAN 8472)

# --- Step 3: Install K3s agent ---

- name: Generate K3s agent setup script
  ansible.builtin.set_fact:
    k3s_agent_script: |
      #!/bin/bash
      exec > >(tee /tmp/k3s-agent-setup.log) 2>&1
      set -e

      echo "=== K3s agent setup starting ==="
      export DEBIAN_FRONTEND=noninteractive

      # Container workaround: create /dev/kmsg if missing
      [ -e /dev/kmsg ] || ln -s /dev/console /dev/kmsg

      # Install prerequisites
      apt-get install -y -qq curl iptables 2>&1 | tail -1
      echo "[OK] curl + iptables installed"

      # Test connectivity to control plane before installing
      echo "Testing connectivity to {{ k3s_server_url }}..."
      if curl -sk --connect-timeout 5 "{{ k3s_server_url }}" >/dev/null 2>&1; then
        echo "[OK] Control plane reachable"
      else
        echo "[WARN] Control plane not reachable via curl (may still work via K3s)"
      fi

      # Download and install K3s binary (skip service start)
      echo "Downloading K3s install script..."
      curl -sfL https://get.k3s.io -o /tmp/k3s-install.sh
      chmod +x /tmp/k3s-install.sh
      echo "[OK] Install script downloaded"

      echo "Installing K3s agent binary (skip start)..."
      K3S_URL="{{ k3s_server_url }}" \
        K3S_TOKEN="{{ k3s_token }}" \
        INSTALL_K3S_EXEC="--snapshotter=native --kubelet-arg=feature-gates=KubeletInUserNamespace=true --node-ip={{ ffsdn_instance_ip | default('127.0.0.1') }}" \
        INSTALL_K3S_SKIP_START=true \
        /tmp/k3s-install.sh
      echo "[OK] K3s agent binary installed"

      # Show resource limits
      echo "--- container limits ---"
      echo "Memory limit: $(cat /sys/fs/cgroup/memory.max 2>/dev/null || echo unknown)"
      echo "PIDs max: $(cat /sys/fs/cgroup/pids.max 2>/dev/null || echo unknown)"
      free -m 2>/dev/null || true

      # Write config file (K3s re-execs itself and loses CLI args)
      mkdir -p /etc/rancher/k3s
      cat > /etc/rancher/k3s/config.yaml <<'CFGEOF'
      server: "{{ k3s_server_url }}"
      token: "{{ k3s_token }}"
      snapshotter: native
      kubelet-arg:
        - "feature-gates=KubeletInUserNamespace=true"
      node-ip: "{{ ffsdn_instance_ip | default('127.0.0.1') }}"
      CFGEOF
      echo "[OK] K3s agent config written to /etc/rancher/k3s/config.yaml"

      # Start K3s agent directly (bypass systemd/D-Bus to avoid container crash)
      echo "Starting K3s agent directly..."
      nohup /usr/local/bin/k3s agent \
        > /var/log/k3s-agent.log 2>&1 &
      K3S_PID=$!
      echo "[OK] K3s agent started (PID $K3S_PID)"

      # Wait for agent to initialize (check process is still alive)
      echo "Waiting for K3s agent to initialize..."
      for i in $(seq 1 12); do
        sleep 5
        if ! kill -0 $K3S_PID 2>/dev/null; then
          echo "[ERROR] K3s agent process died"
          echo "--- last 30 lines of agent log ---"
          tail -30 /var/log/k3s-agent.log 2>/dev/null || true
          exit 1
        fi
        echo "  ... alive after ${i}x5s (PID $K3S_PID)"
      done
      echo "[OK] K3s agent running for 60s"

      # Verify node is registered with the cluster
      echo "--- K3s agent log (last 10 lines) ---"
      tail -10 /var/log/k3s-agent.log 2>/dev/null || true

      echo "=== K3s agent setup complete ==="

- name: Push K3s agent setup script
  ansible.builtin.uri:
    url: "{{ incus_api }}/1.0/instances/{{ ffsdn_instance_name }}/files?path=/tmp/k3s-agent-setup.sh"
    method: POST
    client_cert: "{{ incus_cert }}"
    client_key: "{{ incus_key }}"
    validate_certs: false
    body: "{{ k3s_agent_script }}"
    headers:
      Content-Type: "application/octet-stream"
      X-Incus-type: "file"
      X-Incus-mode: "0755"
    status_code: [200]

- name: Execute K3s agent setup script
  ansible.builtin.uri:
    url: "{{ incus_api }}/1.0/instances/{{ ffsdn_instance_name }}/exec"
    method: POST
    client_cert: "{{ incus_cert }}"
    client_key: "{{ incus_key }}"
    validate_certs: false
    body_format: json
    body:
      command: ["/bin/bash", "/tmp/k3s-agent-setup.sh"]
      record-output: true
      interactive: false
      wait-for-websocket: false
      environment:
        DEBIAN_FRONTEND: "noninteractive"
    status_code: [202]
    return_content: true
  register: k3s_agent_exec

- name: Wait for K3s agent setup to complete (up to 10 minutes)
  ansible.builtin.uri:
    url: "{{ incus_api }}{{ k3s_agent_exec.json.operation }}/wait?timeout=600"
    method: GET
    client_cert: "{{ incus_cert }}"
    client_key: "{{ incus_key }}"
    validate_certs: false
    return_content: true
    timeout: 620
  register: k3s_agent_wait
  failed_when: false

- name: Retrieve K3s agent setup log from container
  ansible.builtin.uri:
    url: "{{ incus_api }}/1.0/instances/{{ ffsdn_instance_name }}/files?path=/tmp/k3s-agent-setup.log"
    method: GET
    client_cert: "{{ incus_cert }}"
    client_key: "{{ incus_key }}"
    validate_certs: false
    return_content: true
    status_code: [200, 404]
  register: k3s_agent_log
  ignore_errors: true

- name: Display K3s agent setup log
  ansible.builtin.debug:
    msg: "{{ k3s_agent_log.content | default('No log captured') }}"
  when: k3s_agent_log is defined and k3s_agent_log.status == 200
  ignore_errors: true

- name: Verify K3s agent setup exit code
  ansible.builtin.assert:
    that:
      - k3s_agent_wait.json.metadata.metadata.return | int == 0
    fail_msg: "K3s agent setup exited with code {{ k3s_agent_wait.json.metadata.metadata.return }}"
    success_msg: "K3s agent joined control plane at {{ k3s_server_url }}"

- name: Clean up setup script
  ansible.builtin.uri:
    url: "{{ incus_api }}/1.0/instances/{{ ffsdn_instance_name }}/exec"
    method: POST
    client_cert: "{{ incus_cert }}"
    client_key: "{{ incus_key }}"
    validate_certs: false
    body_format: json
    body:
      command: ["/bin/rm", "-f", "/tmp/k3s-agent-setup.sh"]
      record-output: false
      interactive: false
      wait-for-websocket: false
    status_code: [202]
  ignore_errors: true
