_02_cloudflare_redirect_rule.ymlยถ

---
# This task file ensures that a Cloudflare "Dynamic Redirect Rule" exists
# which redirects www.example.com โ†’ https://example.com{uri} with 301.

# Required vars to pass in:
#   www_fqdn:   "www.example.com"
#   apex_url:   "https://example.com"
#   API.cloudflare.api_token (via lookup('api', 'cloudflare.api_token'))

- name: "๐Ÿงฎ Derive zone from www_fqdn; apex comes from caller"
  set_fact:
    cf_zone_name: "{{ www_fqdn | to_zone }}"
    domain_apex: "{{ domain }}"

- name: "๐Ÿ” Cloudflare: Lookup zone id for {{ cf_zone_name }}"
  no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
  uri_retry:
    url: "https://api.cloudflare.com/client/v4/zones?name={{ cf_zone_name }}"
    method: GET
    headers:
      Authorization: "Bearer {{ lookup('api', 'cloudflare.api_token') }}"
      Content-Type: "application/json"
    return_content: true
  register: cf_zone_lookup
  failed_when: >
    (cf_zone_lookup.status != 200) or
    (cf_zone_lookup.json.result | length) == 0

- name: "๐Ÿ”ง Set fact: zone_id"
  set_fact:
    cf_zone_id: "{{ (cf_zone_lookup.json.result | first).id }}"

- name: "๐Ÿ—‘๏ธ Remove conflicting A/AAAA record for {{ www_fqdn }}"
  community.general.cloudflare_dns:
    api_token: "{{ lookup('api', 'cloudflare.api_token') }}"
    zone: "{{ cf_zone_name }}"
    type: "A"
    name: "{{ www_fqdn }}"
    state: absent
  ignore_errors: true

- name: "๐ŸŒ Cloudflare DNS: ensure {{ www_fqdn }} is proxied (CNAME)"
  community.general.cloudflare_dns:
    api_token: "{{ lookup('api', 'cloudflare.api_token') }}"
    zone: "{{ cf_zone_name }}"
    type: "CNAME"
    name: "{{ www_fqdn }}"
    value: "{{ domain_apex }}"
    proxied: true
    ttl: 1
    state: present

- name: "๐Ÿ“‹ Cloudflare Rulesets: list dynamic redirect rulesets"
  no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
  uri_retry:
    url: "https://api.cloudflare.com/client/v4/zones/{{ cf_zone_id }}/rulesets?phase=http_request_dynamic_redirect"
    method: GET
    headers:
      Authorization: "Bearer {{ lookup('api', 'cloudflare.api_token') }}"
      Content-Type: "application/json"
    return_content: true
  register: cf_rulesets

- name: "๐ŸŽฏ Pick existing custom ruleset (if any)"
  set_fact:
    cf_redirect_ruleset: >-
      {{
        (cf_rulesets.json.result | default([]))
        | selectattr('kind','equalto','zone')
        | list
        | first
      }}
    cf_redirect_ruleset_id: "{{ (cf_redirect_ruleset.id | default('')) }}"

- name: "๐Ÿ”จ Build desired redirect rule object"
  set_fact:
    desired_redirect_rule:
      ref: "redirect_www_to_apex"
      description: "Redirect www โ†’ apex (301)"
      expression: "http.host eq \"{{ www_fqdn }}\""
      action: "redirect"
      action_parameters:
        from_value:
          target_url:
            expression: "\"{{ apex_url }}\" + http.request.uri.path + (len(http.request.uri.query) > 0 ? (\"?\" + http.request.uri.query) : \"\")"
          status_code: 301
          preserve_query_string: true

- name: "๐Ÿ”„ Update existing dynamic redirect ruleset (PUT)"
  no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
  when: cf_redirect_ruleset_id | length > 0
  vars:
    existing_rules: "{{ cf_redirect_ruleset.rules | default([]) }}"
    merged_rules: >-
      {{
        (
          (existing_rules | rejectattr('ref','equalto', desired_redirect_rule.ref) | list)
          + [ desired_redirect_rule ]
        )
      }}
  uri_retry:
    url: "https://api.cloudflare.com/client/v4/zones/{{ cf_zone_id }}/rulesets/{{ cf_redirect_ruleset_id }}"
    method: PUT
    headers:
      Authorization: "Bearer {{ lookup('api', 'cloudflare.api_token') }}"
      Content-Type: "application/json"
    status_code: 200
    body_format: json
    body:
      name: "{{ cf_redirect_ruleset.name | default('www-to-apex') }}"
      kind: "zone"
      phase: "http_request_dynamic_redirect"
      rules: "{{ merged_rules }}"
  register: cf_ruleset_update
  changed_when: cf_ruleset_update.status in [200]

- name: "๐Ÿ“ Create dynamic redirect ruleset (POST)"
  no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
  when: cf_redirect_ruleset_id | length == 0
  uri_retry:
    url: "https://api.cloudflare.com/client/v4/zones/{{ cf_zone_id }}/rulesets"
    method: POST
    headers:
      Authorization: "Bearer {{ lookup('api', 'cloudflare.api_token') }}"
      Content-Type: "application/json"
    status_code: 200
    body_format: json
    body:
      name: "www-to-apex"
      kind: "zone"
      phase: "http_request_dynamic_redirect"
      rules:
        - "{{ desired_redirect_rule }}"
  register: cf_ruleset_create
  changed_when: cf_ruleset_create.status in [200]