mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2025-09-09 19:57:16 +02:00
Compare commits
3 Commits
1126765da2
...
14e868a644
Author | SHA1 | Date | |
---|---|---|---|
14e868a644 | |||
2a1a956739 | |||
bd2dde3af6 |
@@ -10,30 +10,30 @@
|
|||||||
## Helper Variables:
|
## Helper Variables:
|
||||||
_oidc_client_realm: "{{ OIDC.CLIENT.REALM if OIDC.CLIENT is defined and OIDC.CLIENT.REALM is defined else SOFTWARE_NAME | lower }}"
|
_oidc_client_realm: "{{ OIDC.CLIENT.REALM if OIDC.CLIENT is defined and OIDC.CLIENT.REALM is defined else SOFTWARE_NAME | lower }}"
|
||||||
_oidc_url: "{{
|
_oidc_url: "{{
|
||||||
(OIDC.URL
|
( OIDC.URL
|
||||||
if (oidc is defined and OIDC.URL is defined)
|
if (OIDC is defined and OIDC.URL is defined)
|
||||||
else WEB_PROTOCOL ~ '://' ~ (domains | get_domain('web-app-keycloak'))
|
else WEB_PROTOCOL ~ '://' ~ (domains | get_domain('web-app-keycloak'))
|
||||||
)
|
).rstrip('/')
|
||||||
}}"
|
}}"
|
||||||
_oidc_client_issuer_url: "{{ _oidc_url }}/realms/{{_oidc_client_realm}}/"
|
_oidc_client_issuer_url: "{{ _oidc_url ~ '/realms/' ~ _oidc_client_realm }}"
|
||||||
_oidc_client_id: "{{ OIDC.CLIENT.ID if OIDC.CLIENT is defined and OIDC.CLIENT.ID is defined else SOFTWARE_NAME | lower }}"
|
_oidc_client_id: "{{ OIDC.CLIENT.ID if OIDC.CLIENT is defined and OIDC.CLIENT.ID is defined else SOFTWARE_NAME | lower }}"
|
||||||
|
|
||||||
defaults_oidc:
|
defaults_oidc:
|
||||||
URL: "{{ _oidc_url }}"
|
URL: "{{ _oidc_url }}"
|
||||||
CLIENT:
|
CLIENT:
|
||||||
ID: "{{ _oidc_client_id }}" # Client identifier, typically matching your primary domain
|
ID: "{{ _oidc_client_id }}" # Client identifier, typically matching your primary domain
|
||||||
# secret: # Client secret for authenticating with the OIDC provider (set in the inventory file). Recommend greater then 32 characters
|
# secret: # Client secret for authenticating with the OIDC provider (set in the inventory file). Recommend greater then 32 characters
|
||||||
REALM: "{{_oidc_client_realm}}" # The realm to which the client belongs in the OIDC provider
|
REALM: "{{ _oidc_client_realm }}" # The realm to which the client belongs in the OIDC provider
|
||||||
ISSUER_URL: "{{_oidc_client_issuer_url}}" # Base URL of the OIDC provider (issuer)
|
ISSUER_URL: "{{ _oidc_client_issuer_url }}" # Base URL of the OIDC provider (issuer)
|
||||||
DISCOVERY_DOCUMENT: "{{_oidc_client_issuer_url}}/.well-known/openid-configuration" # URL for fetching the provider's configuration details
|
DISCOVERY_DOCUMENT: "{{ _oidc_client_issuer_url ~ '/.well-known/openid-configuration' }}" # URL for fetching the provider's configuration details
|
||||||
AUTHORIZE_URL: "{{_oidc_client_issuer_url}}/protocol/openid-connect/auth" # Endpoint to start the authorization process
|
AUTHORIZE_URL: "{{ _oidc_client_issuer_url ~ '/protocol/openid-connect/auth' }}" # Endpoint to start the authorization process
|
||||||
TOKEN_URL: "{{_oidc_client_issuer_url}}/protocol/openid-connect/token" # Endpoint to exchange authorization codes for tokens (note: 'token_url' may be a typo for 'token_url')
|
TOKEN_URL: "{{ _oidc_client_issuer_url ~ '/protocol/openid-connect/token' }}" # Endpoint to exchange authorization codes for tokens (note: 'token_url' may be a typo for 'token_url')
|
||||||
USER_INFO_URL: "{{_oidc_client_issuer_url}}/protocol/openid-connect/userinfo" # Endpoint to retrieve user information
|
USER_INFO_URL: "{{ _oidc_client_issuer_url ~ '/protocol/openid-connect/userinfo' }}" # Endpoint to retrieve user information
|
||||||
LOGOUT_URL: "{{_oidc_client_issuer_url}}/protocol/openid-connect/logout" # Endpoint to log out the user
|
LOGOUT_URL: "{{ _oidc_client_issuer_url ~ '/protocol/openid-connect/logout' }}" # Endpoint to log out the user
|
||||||
CHANGE_CREDENTIALS: "{{_oidc_client_issuer_url}}account/account-security/signing-in" # URL for managing or changing user credentials
|
CHANGE_CREDENTIALS: "{{ _oidc_client_issuer_url ~ '/account/account-security/signing-in' }}" # URL for managing or changing user credentials
|
||||||
CERTS: "{{_oidc_client_issuer_url}}/protocol/openid-connect/certs" # JSON Web Key Set (JWKS)
|
CERTS: "{{ _oidc_client_issuer_url ~ '/protocol/openid-connect/certs' }}" # JSON Web Key Set (JWKS)
|
||||||
RESET_CREDENTIALS: "{{_oidc_client_issuer_url}}/login-actions/reset-credentials?client_id={{ _oidc_client_id }}" # Password reset url
|
RESET_CREDENTIALS: "{{ _oidc_client_issuer_url ~ '/login-actions/reset-credentials?client_id=' ~ _oidc_client_id }}" # Password reset url
|
||||||
BUTTON_TEXT: "SSO Login ({{ PRIMARY_DOMAIN | upper }})" # Default button text
|
BUTTON_TEXT: "SSO Login ({{ PRIMARY_DOMAIN | upper }})" # Default button text
|
||||||
ATTRIBUTES:
|
ATTRIBUTES:
|
||||||
# Attribut to identify the user
|
# Attribut to identify the user
|
||||||
USERNAME: "preferred_username"
|
USERNAME: "preferred_username"
|
||||||
|
@@ -1,37 +0,0 @@
|
|||||||
# Cloudflare DNS Records
|
|
||||||
|
|
||||||
## Description
|
|
||||||
|
|
||||||
This Ansible role automates the management of DNS A-records in Cloudflare zones. It uses the [community.general.cloudflare_dns](https://docs.ansible.com/ansible/latest/collections/community/general/cloudflare_dns_module.html) module to create or update A-records for a list of domains, automatically detects the correct zone for each record, and supports configurable proxy settings.
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
Looping over a provided list of domains (`cloudflare_domains`), this role:
|
|
||||||
- Determines the zone name by extracting the last two labels of each domain.
|
|
||||||
- Ensures an A-record for each domain points to the specified IP (`cloudflare_target_ip`).
|
|
||||||
- Honors the `proxied` flag to switch between DNS-only and Cloudflare-proxied modes.
|
|
||||||
- Provides an optional debug task (`MODE_DEBUG`) to output the domain list before changes.
|
|
||||||
|
|
||||||
Ideal for environments where bulk or dynamic DNS updates are needed, this role abstracts away the complexity of Cloudflare’s zone and record API.
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
|
|
||||||
Cloudflare DNS Records delivers an idempotent, scalable solution for managing A-records across multiple Cloudflare zones. Whether you need to onboard hundreds of domains or toggle proxy settings in CI/CD pipelines, this role handles the orchestration and ensures consistency.
|
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
- **Automatic Zone Detection:** Parses each domain to derive its zone (`example.com`) without manual intervention.
|
|
||||||
- **Bulk Record Management:** Creates or updates A-records for all entries in `cloudflare_domains`.
|
|
||||||
- **Proxy Toggle:** Configure `proxied: true` or `false` per record to switch between DNS-only and proxied modes.
|
|
||||||
- **Debug Support:** Enable `MODE_DEBUG` to print the domain list for validation before execution.
|
|
||||||
- **Flexible Authentication:** Supports both API token (`api_token`) and Global API key + email.
|
|
||||||
- **Low-TTL Option:** Use `ttl: 1` for rapid DNS propagation during dynamic updates.
|
|
||||||
|
|
||||||
## Author
|
|
||||||
|
|
||||||
Kevin Veen-Birkenbach
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
Infinito.Nexus NonCommercial License
|
|
||||||
<https://s.infinito.nexus/license>
|
|
@@ -1,26 +0,0 @@
|
|||||||
---
|
|
||||||
galaxy_info:
|
|
||||||
author: "Kevin Veen-Birkenbach"
|
|
||||||
description: "Manages DNS A-records in Cloudflare zones."
|
|
||||||
license: "Infinito.Nexus NonCommercial License"
|
|
||||||
license_url: "https://s.infinito.nexus/license"
|
|
||||||
company: |
|
|
||||||
Kevin Veen-Birkenbach
|
|
||||||
Consulting & Coaching Solutions
|
|
||||||
https://www.veen.world
|
|
||||||
min_ansible_version: "2.9"
|
|
||||||
platforms:
|
|
||||||
- name: "All"
|
|
||||||
versions:
|
|
||||||
- "all"
|
|
||||||
galaxy_tags:
|
|
||||||
- "cloudflare"
|
|
||||||
- "dns"
|
|
||||||
- "records"
|
|
||||||
- "ansible"
|
|
||||||
- "network"
|
|
||||||
- "automation"
|
|
||||||
repository: "https://s.infinito.nexus/code"
|
|
||||||
issue_tracker_url: "https://s.infinito.nexus/issues"
|
|
||||||
documentation: "https://docs.infinito.nexus"
|
|
||||||
dependencies: []
|
|
@@ -1,17 +0,0 @@
|
|||||||
# run_once_srv_web_7_7_dns_records: deactivated
|
|
||||||
|
|
||||||
- name: Create or update Cloudflare A-record for {{ item }}
|
|
||||||
community.general.cloudflare_dns:
|
|
||||||
api_token: "{{ CLOUDFLARE_API_TOKEN }}"
|
|
||||||
zone: "{{ item.split('.')[-2:] | join('.') }}"
|
|
||||||
state: present
|
|
||||||
type: A
|
|
||||||
name: "{{ item }}"
|
|
||||||
content: "{{ cloudflare_target_ip }}"
|
|
||||||
ttl: 1
|
|
||||||
proxied: "{{ cloudflare_proxied | int }}"
|
|
||||||
loop: "{{ cloudflare_domains }}"
|
|
||||||
loop_control:
|
|
||||||
label: "{{ item }}"
|
|
||||||
async: "{{ ASYNC_TIME if ASYNC_ENABLED | bool else omit }}"
|
|
||||||
poll: "{{ ASYNC_POLL if ASYNC_ENABLED | bool else omit }}"
|
|
@@ -58,10 +58,12 @@
|
|||||||
|
|
||||||
- name: Include DNS role to register Gitea domain(s)
|
- name: Include DNS role to register Gitea domain(s)
|
||||||
include_role:
|
include_role:
|
||||||
name: srv-web-7-7-dns-records
|
name: sys-dns-cloudflare-records
|
||||||
vars:
|
vars:
|
||||||
CLOUDFLARE_API_TOKEN: "{{ CLOUDFLARE_API_TOKEN }}"
|
cloudflare_records:
|
||||||
cloudflare_domains: "{{ [ domains | get_domain(application_id) ] }}"
|
- zone: "{{ domains | get_domain(application_id) | to_zone }}"
|
||||||
cloudflare_target_ip: "{{ networks.internet.ip4 }}"
|
type: A
|
||||||
cloudflare_proxied: false
|
name: "{{ domains | get_domain(application_id) }}"
|
||||||
|
content: "{{ networks.internet.ip4 }}"
|
||||||
|
proxied: false # Necessary for SSH port
|
||||||
when: DNS_PROVIDER == 'cloudflare'
|
when: DNS_PROVIDER == 'cloudflare'
|
2
roles/web-opt-rdr-www/Todo.md
Normal file
2
roles/web-opt-rdr-www/Todo.md
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
# To-dos
|
||||||
|
- Test flavor 'edge'
|
5
roles/web-opt-rdr-www/config/main.yml
Normal file
5
roles/web-opt-rdr-www/config/main.yml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# The following defines the redirect flavor
|
||||||
|
# Possible options
|
||||||
|
# - 'edge' for configuration via cloudflare
|
||||||
|
# - 'origin' via native nginx
|
||||||
|
prefered_flavor: "origin"
|
@@ -1,6 +1,6 @@
|
|||||||
galaxy_info:
|
galaxy_info:
|
||||||
author: "Kevin Veen-Birkenbach"
|
author: "Kevin Veen-Birkenbach"
|
||||||
description: "An Ansible role to redirect www subdomains to non-www domains in Nginx"
|
description: "An Ansible role to redirect www subdomains to bare domains (apex). Supports Cloudflare edge redirects or local Nginx redirects."
|
||||||
license: "Infinito.Nexus NonCommercial License"
|
license: "Infinito.Nexus NonCommercial License"
|
||||||
license_url: "https://s.infinito.nexus/license"
|
license_url: "https://s.infinito.nexus/license"
|
||||||
company: |
|
company: |
|
||||||
@@ -9,15 +9,15 @@ galaxy_info:
|
|||||||
https://www.veen.world
|
https://www.veen.world
|
||||||
min_ansible_version: "2.9"
|
min_ansible_version: "2.9"
|
||||||
platforms:
|
platforms:
|
||||||
- name: Archlinux
|
- name: "Archlinux"
|
||||||
versions:
|
versions:
|
||||||
- rolling
|
- "rolling"
|
||||||
galaxy_tags:
|
galaxy_tags:
|
||||||
- nginx
|
- nginx
|
||||||
- redirect
|
- redirect
|
||||||
- www
|
- www
|
||||||
- wildcard
|
- cloudflare
|
||||||
- seo
|
- seo
|
||||||
repository: "https://s.infinito.nexus/code"
|
repository: "https://s.infinito.nexus/code"
|
||||||
issue_tracker_url: "https://s.infinito.nexus/issues"
|
issue_tracker_url: "https://s.infinito.nexus/issues"
|
||||||
documentation: "https://docs.infinito.nexus"
|
documentation: "https://docs.infinito.nexus"
|
||||||
|
134
roles/web-opt-rdr-www/tasks/cloudflare_redirect_rule.yml
Normal file
134
roles/web-opt-rdr-www/tasks/cloudflare_redirect_rule.yml
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
# 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"
|
||||||
|
# CLOUDFLARE_API_TOKEN
|
||||||
|
|
||||||
|
- name: "Derive zone and apex from www_fqdn"
|
||||||
|
set_fact:
|
||||||
|
cf_zone_name: "{{ www_fqdn | to_zone }}" # e.g. "cymais.cloud"
|
||||||
|
domain_apex: "{{ www_fqdn | regex_replace('^www\\.', '') }}" # e.g. "academy.cymais.cloud"
|
||||||
|
|
||||||
|
- name: "Cloudflare: Lookup zone id for {{ cf_zone_name }}"
|
||||||
|
ansible.builtin.uri:
|
||||||
|
url: "https://api.cloudflare.com/client/v4/zones?name={{ cf_zone_name }}"
|
||||||
|
method: GET
|
||||||
|
headers:
|
||||||
|
Authorization: "Bearer {{ 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: "{{ 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: "{{ CLOUDFLARE_API_TOKEN }}"
|
||||||
|
zone: "{{ cf_zone_name }}"
|
||||||
|
type: "CNAME"
|
||||||
|
name: "{{ www_fqdn }}"
|
||||||
|
value: "{{ domain_apex }}"
|
||||||
|
proxied: true
|
||||||
|
ttl: 1
|
||||||
|
state: present
|
||||||
|
|
||||||
|
# 1) Fetch existing redirect rulesets
|
||||||
|
- name: "Cloudflare Rulesets: list dynamic redirect rulesets"
|
||||||
|
ansible.builtin.uri:
|
||||||
|
url: "https://api.cloudflare.com/client/v4/zones/{{ cf_zone_id }}/rulesets?phase=http_request_dynamic_redirect"
|
||||||
|
method: GET
|
||||||
|
headers:
|
||||||
|
Authorization: "Bearer {{ 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('')) }}"
|
||||||
|
|
||||||
|
# Desired redirect rule object
|
||||||
|
- 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
|
||||||
|
|
||||||
|
# 2) Update existing ruleset (PUT)
|
||||||
|
- name: "Update existing dynamic redirect ruleset (PUT)"
|
||||||
|
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 ]
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
ansible.builtin.uri:
|
||||||
|
url: "https://api.cloudflare.com/client/v4/zones/{{ cf_zone_id }}/rulesets/{{ cf_redirect_ruleset_id }}"
|
||||||
|
method: PUT
|
||||||
|
headers:
|
||||||
|
Authorization: "Bearer {{ 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]
|
||||||
|
|
||||||
|
# 3) Create new ruleset (POST)
|
||||||
|
- name: "Create dynamic redirect ruleset (POST)"
|
||||||
|
when: cf_redirect_ruleset_id | length == 0
|
||||||
|
ansible.builtin.uri:
|
||||||
|
url: "https://api.cloudflare.com/client/v4/zones/{{ cf_zone_id }}/rulesets"
|
||||||
|
method: POST
|
||||||
|
headers:
|
||||||
|
Authorization: "Bearer {{ 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]
|
@@ -6,22 +6,42 @@
|
|||||||
- include_tasks: utils/run_once.yml
|
- include_tasks: utils/run_once.yml
|
||||||
when: run_once_web_opt_rdr_www is not defined
|
when: run_once_web_opt_rdr_www is not defined
|
||||||
|
|
||||||
- name: Filter www-prefixed domains from current_play_domains_all
|
|
||||||
set_fact:
|
|
||||||
www_domains: "{{ current_play_domains_all | select('match', '^www\\.') | list }}"
|
|
||||||
|
|
||||||
- name: Include web-opt-rdr-domains role for www-to-bare redirects
|
- name: Include web-opt-rdr-domains role for www-to-bare redirects
|
||||||
include_role:
|
include_role:
|
||||||
name: web-opt-rdr-domains
|
name: web-opt-rdr-domains
|
||||||
vars:
|
vars:
|
||||||
domain_mappings: "{{ www_domains | map('regex_replace', '^www\\.(.+)$', '{ source: \"www.\\1\", target: \"\\1\" }') | map('from_yaml') | list }}"
|
domain_mappings: "{{ REDIRECT_WWW_DOMAINS | map('regex_replace', '^www\\.(.+)$', '{ source: \"www.\\1\", target: \"\\1\" }') | map('from_yaml') | list }}"
|
||||||
|
when: REDIRECT_WWW_FLAVOR == 'origin'
|
||||||
|
|
||||||
- name: Include DNS role to set redirects
|
- name: Include DNS role to set redirects
|
||||||
include_role:
|
include_role:
|
||||||
name: srv-web-7-7-dns-records
|
name: sys-dns-cloudflare-records
|
||||||
vars:
|
vars:
|
||||||
CLOUDFLARE_API_TOKEN: "{{ CLOUDFLARE_API_TOKEN }}"
|
cloudflare_records: |
|
||||||
cloudflare_domains: "{{ www_domains }}"
|
{%- set bare = REDIRECT_WWW_DOMAINS | map('regex_replace', '^www\\.(.+)$', '\\1') | list -%}
|
||||||
cloudflare_target_ip: "{{ networks.internet.ip4 }}"
|
[
|
||||||
cloudflare_proxied: false
|
{%- for d in bare -%}
|
||||||
when: DNS_PROVIDER == 'cloudflare'
|
{
|
||||||
|
"type": "A",
|
||||||
|
"zone": "{{ d | to_zone }}",
|
||||||
|
"name": "{{ d }}",
|
||||||
|
"content": "{{ networks.internet.ip4 }}",
|
||||||
|
"proxied": {{ REDIRECT_WWW_FLAVOR == 'edge' }},
|
||||||
|
"ttl": 1
|
||||||
|
}{{ "," if not loop.last else "" }}
|
||||||
|
{%- endfor -%}
|
||||||
|
]
|
||||||
|
when:
|
||||||
|
- DNS_PROVIDER == 'cloudflare'
|
||||||
|
- REDIRECT_WWW_FLAVOR == 'origin'
|
||||||
|
|
||||||
|
- name: Include Cloudflare redirect rule to enforce www → apex
|
||||||
|
include_tasks: cloudflare_redirect_rule.yml
|
||||||
|
vars:
|
||||||
|
domain: "{{ item | regex_replace('^www\\.', '') }}"
|
||||||
|
www_fqdn: "{{ item }}"
|
||||||
|
apex_url: "{{ WEB_PROTOCOL }}://{{ item | regex_replace('^www\\.', '') }}"
|
||||||
|
loop: "{{ REDIRECT_WWW_DOMAINS }}"
|
||||||
|
when: REDIRECT_WWW_FLAVOR == 'edge'
|
||||||
|
|
||||||
|
|
||||||
|
@@ -1 +1,6 @@
|
|||||||
application_id: "web-opt-rdr-www"
|
# General
|
||||||
|
application_id: "web-opt-rdr-www"
|
||||||
|
|
||||||
|
# Redirect WWW
|
||||||
|
REDIRECT_WWW_FLAVOR: "{{ applications | get_app_conf(application_id, 'prefered_flavor') if DNS_PROVIDER == 'cloudflare' else 'origin' }}"
|
||||||
|
REDIRECT_WWW_DOMAINS: "{{ current_play_domains_all | select('match', '^www\\.') | list }}"
|
@@ -1,36 +0,0 @@
|
|||||||
import unittest
|
|
||||||
import yaml
|
|
||||||
import glob
|
|
||||||
import os
|
|
||||||
|
|
||||||
class TestWebRolesDomains(unittest.TestCase):
|
|
||||||
def test_canonical_domains_present_and_not_empty(self):
|
|
||||||
"""
|
|
||||||
Check all roles/web-*/config/main.yml files:
|
|
||||||
- must have domains.canonical defined
|
|
||||||
- domains.canonical must not be empty dict, empty list, or empty string
|
|
||||||
"""
|
|
||||||
role_config_paths = glob.glob("roles/web-*/config/main.yml")
|
|
||||||
self.assertTrue(role_config_paths, "No roles/web-*/config/main.yml files found.")
|
|
||||||
|
|
||||||
for path in role_config_paths:
|
|
||||||
with self.subTest(role_config=path):
|
|
||||||
with open(path, "r") as f:
|
|
||||||
data = yaml.safe_load(f)
|
|
||||||
|
|
||||||
self.assertIsInstance(data, dict, f"YAML root is not a dict in {path}")
|
|
||||||
|
|
||||||
domains = data.get('server',{}).get('domains',{})
|
|
||||||
self.assertIsNotNone(domains, f"'domains' section missing in {path}")
|
|
||||||
self.assertIsInstance(domains, dict, f"'domains' must be a dict in {path}")
|
|
||||||
|
|
||||||
canonical = domains.get("canonical")
|
|
||||||
self.assertIsNotNone(canonical, f"'server.domains.canonical' missing in {path}")
|
|
||||||
|
|
||||||
# Check for emptiness
|
|
||||||
empty_values = [{}, [], ""]
|
|
||||||
self.assertNotIn(canonical, empty_values,
|
|
||||||
f"'server.domains.canonical' in {path} must not be empty dict, list, or empty string")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
unittest.main()
|
|
Reference in New Issue
Block a user