---
- name: Update Cloudflare DNS for observability.svc.plus
  hosts: localhost
  connection: local
  gather_facts: false
  vars:
    cloudflare_zone_name: svc.plus
    cloudflare_api_base: https://api.cloudflare.com/client/v4
    observability_domain: observability.svc.plus
    observability_dns_target: us-xhttp.svc.plus
    observability_dns_type: CNAME
    observability_dns_ttl: 1
    observability_dns_proxied: false
    dns_wait_retries: 30
    dns_wait_delay: 10
  tasks:
    - name: Validate Cloudflare token is present in environment
      ansible.builtin.assert:
        that:
          - lookup('ansible.builtin.env', 'CLOUDFLARE_API_TOKEN') | length > 0
        fail_msg: "CLOUDFLARE_API_TOKEN must be exported before running this playbook."

    - name: Resolve Cloudflare zone id
      ansible.builtin.uri:
        url: "{{ cloudflare_api_base }}/zones?name={{ cloudflare_zone_name }}"
        method: GET
        headers:
          Authorization: "Bearer {{ lookup('ansible.builtin.env', 'CLOUDFLARE_API_TOKEN') }}"
          Content-Type: application/json
        return_content: true
      register: cloudflare_zone_lookup

    - name: Validate zone lookup result
      ansible.builtin.assert:
        that:
          - cloudflare_zone_lookup.json.success
          - cloudflare_zone_lookup.json.result | length > 0
        fail_msg: "Unable to resolve Cloudflare zone id for {{ cloudflare_zone_name }}."

    - name: Set Cloudflare zone id
      ansible.builtin.set_fact:
        cloudflare_zone_id: "{{ cloudflare_zone_lookup.json.result[0].id }}"

    - name: Query existing observability DNS records
      ansible.builtin.uri:
        url: "{{ cloudflare_api_base }}/zones/{{ cloudflare_zone_id }}/dns_records?name={{ observability_domain }}"
        method: GET
        headers:
          Authorization: "Bearer {{ lookup('ansible.builtin.env', 'CLOUDFLARE_API_TOKEN') }}"
          Content-Type: application/json
        return_content: true
      register: observability_dns_records

    - name: Remove conflicting observability DNS records with different type
      ansible.builtin.uri:
        url: "{{ cloudflare_api_base }}/zones/{{ cloudflare_zone_id }}/dns_records/{{ item.id }}"
        method: DELETE
        headers:
          Authorization: "Bearer {{ lookup('ansible.builtin.env', 'CLOUDFLARE_API_TOKEN') }}"
          Content-Type: application/json
      loop: "{{ observability_dns_records.json.result | default([]) }}"
      loop_control:
        label: "{{ item.type }} {{ item.name }}"
      when: item.type != observability_dns_type

    - name: Create observability DNS record when missing
      ansible.builtin.uri:
        url: "{{ cloudflare_api_base }}/zones/{{ cloudflare_zone_id }}/dns_records"
        method: POST
        headers:
          Authorization: "Bearer {{ lookup('ansible.builtin.env', 'CLOUDFLARE_API_TOKEN') }}"
          Content-Type: application/json
        body_format: raw
        body: >-
          {{
            {
              'type': observability_dns_type,
              'name': observability_domain,
              'content': observability_dns_target,
              'ttl': (observability_dns_ttl | int),
              'proxied': (observability_dns_proxied | bool)
            } | to_json
          }}
      when: (observability_dns_records.json.result | selectattr('type', 'equalto', observability_dns_type) | list | length) == 0

    - name: Update observability DNS record when target changes
      ansible.builtin.uri:
        url: "{{ cloudflare_api_base }}/zones/{{ cloudflare_zone_id }}/dns_records/{{ (observability_dns_records.json.result | selectattr('type', 'equalto', observability_dns_type) | list | first).id }}"
        method: PUT
        headers:
          Authorization: "Bearer {{ lookup('ansible.builtin.env', 'CLOUDFLARE_API_TOKEN') }}"
          Content-Type: application/json
        body_format: raw
        body: >-
          {{
            {
              'type': observability_dns_type,
              'name': observability_domain,
              'content': observability_dns_target,
              'ttl': (observability_dns_ttl | int),
              'proxied': (observability_dns_proxied | bool)
            } | to_json
          }}
      when:
        - (observability_dns_records.json.result | selectattr('type', 'equalto', observability_dns_type) | list | length) > 0
        - >
          ((observability_dns_records.json.result | selectattr('type', 'equalto', observability_dns_type) | list | first).content != observability_dns_target)
          or
          (((observability_dns_records.json.result | selectattr('type', 'equalto', observability_dns_type) | list | first).proxied | default(false)) != observability_dns_proxied)

    - name: Wait for public DNS to expose observability CNAME
      ansible.builtin.uri:
        url: "https://cloudflare-dns.com/dns-query?name={{ observability_domain }}&type=CNAME"
        method: GET
        headers:
          Accept: application/dns-json
        return_content: true
      register: observability_dns_public
      until:
        - observability_dns_public.status == 200
        - >
          (
            observability_dns_public.json.Status
            if (observability_dns_public.json is defined)
            else ((observability_dns_public.content | from_json).Status | default(1))
          ) == 0
        - >
          (
            observability_dns_public.json.Answer
            if (observability_dns_public.json is defined)
            else ((observability_dns_public.content | from_json).Answer | default([]))
          ) | selectattr('data', 'equalto', observability_dns_target ~ '.')
            | list | length > 0
      retries: "{{ dns_wait_retries }}"
      delay: "{{ dns_wait_delay }}"

    - name: Show effective observability DNS target
      ansible.builtin.debug:
        msg: "{{ observability_domain }} -> {{ observability_dns_target }} proxied={{ observability_dns_proxied }}"

- import_playbook: infra.yml
  vars:
    infra_domain: observability.svc.plus
    infra_portal:
      home: { domain: observability.svc.plus }
    caddy_enabled: true
    nginx_enabled: false
