diff --git a/.ansible-lint b/.ansible-lint new file mode 100644 index 0000000..f7d5ff6 --- /dev/null +++ b/.ansible-lint @@ -0,0 +1,20 @@ +--- +profile: basic +warn_list: + - name[prefix] + - yaml[line-length] +skip_list: + - yaml[truthy] +exclude_paths: + - molecule/ + - roles/*/molecule/ + - inventories/ +mock_modules: + - community.docker.docker_compose_v2 + - community.docker.docker_network + - community.general.minio + - ansible.posix.sysctl + - ansible.builtin.apt + - ansible.builtin.apt_key + - ansible.builtin.apt_repository + - ansible.builtin.systemd diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 0f7eb0b..6f55f20 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -2,7 +2,23 @@ "permissions": { "allow": [ "Bash(ls -la /home/iroddis/dev/sovereign/.*)", - "Bash(ls -la /home/iroddis/dev/sovereign/roles/*/templates/)" + "Bash(ls -la /home/iroddis/dev/sovereign/roles/*/templates/)", + "Bash(pip list:*)", + "Bash(for role:*)", + "Bash(do echo:*)", + "Read(//home/iroddis/dev/sovereign/roles/$role/**)", + "Bash(done)", + "Bash(pip3 list:*)", + "Bash(pip3 show:*)", + "Bash(find /home/iroddis/dev/sovereign -type f \\\\\\(-name *.py -o -name requirements*.txt -o -name tox.ini -o -name setup.py -o -name setup.cfg -o -name pyproject.toml \\\\\\))", + "Bash(grep -v \"^\\\\.$\")", + "Bash(grep -v \"^\\\\..$\")", + "WebSearch", + "Bash(find /home/iroddis/dev/sovereign -type f ! -path */.git/* -o -type d ! -path */.git/*)", + "Bash(grep -l \"molecule_test_mode\" /home/iroddis/dev/sovereign/roles/*/tasks/main.yml)", + "Bash(grep -l \"molecule_test_mode\" /home/iroddis/dev/sovereign/roles/*/handlers/main.yml)", + "Bash(ls /home/iroddis/dev/sovereign/roles/*/molecule/default/)", + "Bash(grep -v \"^$\")" ] } } diff --git a/.yamllint b/.yamllint new file mode 100644 index 0000000..2fda4ee --- /dev/null +++ b/.yamllint @@ -0,0 +1,17 @@ +--- +extends: default +rules: + line-length: + max: 160 + level: warning + truthy: + allowed-values: ['true', 'false', 'yes', 'no'] + level: warning + comments: + min-spaces-from-content: 1 + braces: + min-spaces-inside: 0 + max-spaces-inside: 1 +ignore: | + roles/*/molecule/ + .git/ diff --git a/molecule/shared/vars.yml b/molecule/shared/vars.yml new file mode 100644 index 0000000..f604393 --- /dev/null +++ b/molecule/shared/vars.yml @@ -0,0 +1,141 @@ +--- +# Shared test variables for all Molecule scenarios. +# Loaded via vars_files in each role's converge.yml. + +molecule_test_mode: true +sovereign_base_dir: /tmp/sovereign_test + +# Branding +base_domain: test.example.com +tenant_name: "Test Corp" +tenant_primary_color: "#2563eb" +tenant_accent_color: "#1e40af" +tenant_logo_local_path: "" + +# Graylog logging (referenced in every docker-compose template) +graylog_host: "127.0.0.1" +graylog_gelf_port: 12201 +sovereign_network_name: sovereign + +# SMTP +smtp_host: stalwart +smtp_port: 587 +smtp_from: "noreply@test.example.com" +smtp_user: "noreply@test.example.com" +smtp_password: "test_smtp_password" +smtp_tls: starttls + +# Traefik +traefik_acme_email: "admin@test.example.com" +traefik_domain: "traefik.test.example.com" +traefik_dashboard_password: "test_traefik_dash" +traefik_version: "v3.1" +traefik_data_dir: /tmp/sovereign_test/traefik + +# Authentik +authentik_domain: "auth.test.example.com" +authentik_version: "2024.10.5" +authentik_secret_key: "test-secret-key-exactly-50-chars-padded-here12345" +authentik_db_password: "test_authentik_db" +authentik_admin_email: "admin@test.example.com" +authentik_admin_password: "test_authentik_admin" +authentik_data_dir: /tmp/sovereign_test/authentik + +# Graylog +graylog_domain: "logs.test.example.com" +graylog_version: "6.0" +graylog_password_secret: "test_graylog_secret_min_16_chars" +graylog_root_password_sha2: "test_sha256_placeholder" +graylog_data_dir: /tmp/sovereign_test/graylog + +# Stalwart +stalwart_domain: "mail.test.example.com" +stalwart_admin_password: "test_stalwart_admin" +stalwart_version: "latest" +stalwart_data_dir: /tmp/sovereign_test/stalwart + +# Roundcube +roundcube_domain: "webmail.test.example.com" +roundcube_version: "latest" +roundcube_db_password: "test_roundcube_db" +roundcube_des_key: "test_24_char_des_key____!" +roundcube_data_dir: /tmp/sovereign_test/roundcube + +# Wazuh +wazuh_domain: "wazuh.test.example.com" +wazuh_version: "4.9.0" +wazuh_admin_password: "test_wazuh_admin" +wazuh_api_password: "test_wazuh_api" +wazuh_indexer_memory: "512m" +wazuh_data_dir: /tmp/sovereign_test/wazuh + +# Headscale +headscale_domain: "headscale.test.example.com" +headscale_version: "0.23.0" +wireguard_domain: "vpn.test.example.com" +wireguard_port: 51820 +headscale_noise_private_key: "" +headscale_data_dir: /tmp/sovereign_test/headscale + +# Matrix / Element +matrix_domain: "matrix.test.example.com" +element_domain: "chat.test.example.com" +matrix_version: "v1.118.0" +matrix_registration_secret: "test_registration_secret" +matrix_db_password: "test_matrix_db" +element_theme: "light" +matrix_data_dir: /tmp/sovereign_test/matrix + +# Jitsi +jitsi_domain: "meet.test.example.com" +jitsi_version: "stable-9753" +jitsi_jicofo_auth_password: "test_jicofo" +jitsi_jvb_auth_password: "test_jvb" +jitsi_jibri_recorder_password: "test_jibri_recorder" +jitsi_jibri_xmpp_password: "test_jibri_xmpp" +jitsi_turn_secret: "test_turn" +jitsi_data_dir: /tmp/sovereign_test/jitsi + +# MinIO +minio_domain: "s3.test.example.com" +minio_console_domain: "minio.test.example.com" +minio_version: "latest" +minio_root_user: "minioadmin" +minio_root_password: "test_minio" +minio_nextcloud_bucket: "nextcloud" +minio_nextcloud_access_key: "nextcloud" +minio_nextcloud_secret_key: "test_nextcloud_s3" +minio_data_dir: /tmp/sovereign_test/minio + +# Nextcloud +nextcloud_domain: "cloud.test.example.com" +nextcloud_version: "29" +nextcloud_admin_user: "admin" +nextcloud_admin_password: "test_nextcloud" +nextcloud_db_password: "test_nextcloud_db" +nextcloud_db_root_password: "test_nextcloud_db_root" +nextcloud_data_dir: /tmp/sovereign_test/nextcloud + +# Vaultwarden +vaultwarden_domain: "vault.test.example.com" +vaultwarden_version: "latest" +vaultwarden_admin_token: "test_vaultwarden_admin_token" +vaultwarden_db_password: "test_vaultwarden_db" +vaultwarden_data_dir: /tmp/sovereign_test/vaultwarden + +# Forgejo +forgejo_domain: "git.test.example.com" +forgejo_version: "latest" +forgejo_db_password: "test_forgejo_db" +forgejo_secret_key: "test_forgejo_secret" +forgejo_internal_token: "test_forgejo_internal_token" +forgejo_lfs_jwt_secret: "test_forgejo_lfs_jwt" +forgejo_admin_user: "admin" +forgejo_admin_password: "test_forgejo_admin" +forgejo_admin_email: "admin@test.example.com" +forgejo_ssh_port: 2222 +forgejo_data_dir: /tmp/sovereign_test/forgejo + +# Website +website_nginx_version: "alpine" +website_data_dir: /tmp/sovereign_test/website diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..4bc5fc7 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +ansible>=10.0.0 +molecule>=24.9.0 +ansible-lint>=24.9.0 +yamllint>=1.35.0 diff --git a/roles/authentik/handlers/main.yml b/roles/authentik/handlers/main.yml index 223deef..cd532af 100644 --- a/roles/authentik/handlers/main.yml +++ b/roles/authentik/handlers/main.yml @@ -4,3 +4,4 @@ project_src: "{{ authentik_data_dir }}" state: present recreate: always + when: not (molecule_test_mode | default(false)) diff --git a/roles/authentik/molecule/default/converge.yml b/roles/authentik/molecule/default/converge.yml new file mode 100644 index 0000000..1406389 --- /dev/null +++ b/roles/authentik/molecule/default/converge.yml @@ -0,0 +1,8 @@ +--- +- name: Converge + hosts: localhost + gather_facts: false + vars_files: + - ../../../molecule/shared/vars.yml + roles: + - role: authentik diff --git a/roles/authentik/molecule/default/molecule.yml b/roles/authentik/molecule/default/molecule.yml new file mode 100644 index 0000000..859036a --- /dev/null +++ b/roles/authentik/molecule/default/molecule.yml @@ -0,0 +1,23 @@ +--- +dependency: + name: galaxy + options: + requirements-file: requirements.yml +driver: + name: delegated + options: + managed: false + ansible_connection_options: + ansible_connection: local +platforms: + - name: localhost + groups: + - sovereign +provisioner: + name: ansible + inventory: + host_vars: + localhost: + ansible_connection: local +verifier: + name: ansible diff --git a/roles/authentik/molecule/default/verify.yml b/roles/authentik/molecule/default/verify.yml new file mode 100644 index 0000000..986c9a5 --- /dev/null +++ b/roles/authentik/molecule/default/verify.yml @@ -0,0 +1,160 @@ +--- +- name: Verify authentik role + hosts: localhost + gather_facts: false + vars: + authentik_data_dir: /tmp/sovereign_test/authentik + + tasks: + - name: Check authentik data directory exists + ansible.builtin.stat: + path: /tmp/sovereign_test/authentik + register: data_dir_stat + + - name: Assert authentik data directory is present + ansible.builtin.assert: + that: data_dir_stat.stat.isdir + fail_msg: "Data directory /tmp/sovereign_test/authentik was not created" + + - name: Check authentik media directory exists + ansible.builtin.stat: + path: /tmp/sovereign_test/authentik/media + register: media_dir_stat + + - name: Assert authentik media directory is present + ansible.builtin.assert: + that: media_dir_stat.stat.isdir + fail_msg: "Directory /tmp/sovereign_test/authentik/media was not created" + + - name: Check authentik media/branding directory exists + ansible.builtin.stat: + path: /tmp/sovereign_test/authentik/media/branding + register: branding_dir_stat + + - name: Assert authentik media/branding directory is present + ansible.builtin.assert: + that: branding_dir_stat.stat.isdir + fail_msg: "Directory /tmp/sovereign_test/authentik/media/branding was not created" + + - name: Check authentik custom-templates directory exists + ansible.builtin.stat: + path: /tmp/sovereign_test/authentik/custom-templates + register: custom_templates_dir_stat + + - name: Assert authentik custom-templates directory is present + ansible.builtin.assert: + that: custom_templates_dir_stat.stat.isdir + fail_msg: "Directory /tmp/sovereign_test/authentik/custom-templates was not created" + + - name: Check authentik blueprints directory exists + ansible.builtin.stat: + path: /tmp/sovereign_test/authentik/blueprints + register: blueprints_dir_stat + + - name: Assert authentik blueprints directory is present + ansible.builtin.assert: + that: blueprints_dir_stat.stat.isdir + fail_msg: "Directory /tmp/sovereign_test/authentik/blueprints was not created" + + - name: Check authentik certs directory exists + ansible.builtin.stat: + path: /tmp/sovereign_test/authentik/certs + register: certs_dir_stat + + - name: Assert authentik certs directory is present + ansible.builtin.assert: + that: certs_dir_stat.stat.isdir + fail_msg: "Directory /tmp/sovereign_test/authentik/certs was not created" + + - name: Check authentik postgres directory exists + ansible.builtin.stat: + path: /tmp/sovereign_test/authentik/postgres + register: postgres_dir_stat + + - name: Assert authentik postgres directory is present + ansible.builtin.assert: + that: postgres_dir_stat.stat.isdir + fail_msg: "Directory /tmp/sovereign_test/authentik/postgres was not created" + + - name: Check sovereign-branding.yaml blueprint exists + ansible.builtin.stat: + path: /tmp/sovereign_test/authentik/blueprints/sovereign-branding.yaml + register: blueprint_stat + + - name: Assert sovereign-branding.yaml is present + ansible.builtin.assert: + that: blueprint_stat.stat.exists + fail_msg: "Blueprint /tmp/sovereign_test/authentik/blueprints/sovereign-branding.yaml was not created" + + - name: Read sovereign-branding.yaml + ansible.builtin.slurp: + src: /tmp/sovereign_test/authentik/blueprints/sovereign-branding.yaml + register: blueprint_raw + + - name: Set blueprint content fact + ansible.builtin.set_fact: + blueprint: "{{ blueprint_raw.content | b64decode }}" + + - name: Assert branding_title in blueprint + ansible.builtin.assert: + that: "'branding_title: \"Test Corp\"' in blueprint" + fail_msg: "Expected branding_title 'Test Corp' not found in sovereign-branding.yaml" + + - name: Assert primary color in blueprint + ansible.builtin.assert: + that: "'--ak-accent: #2563eb' in blueprint" + fail_msg: "Expected '--ak-accent: #2563eb' not found in sovereign-branding.yaml" + + - name: Assert logo path is NOT in blueprint (tenant_logo_local_path is empty) + ansible.builtin.assert: + that: "'/media/branding/logo.png' not in blueprint" + fail_msg: "Logo path /media/branding/logo.png should not appear in blueprint when tenant_logo_local_path is empty" + + - name: Check docker-compose.yml exists + ansible.builtin.stat: + path: /tmp/sovereign_test/authentik/docker-compose.yml + register: compose_stat + + - name: Assert docker-compose.yml was rendered + ansible.builtin.assert: + that: compose_stat.stat.exists + fail_msg: "docker-compose.yml was not rendered for authentik" + + - name: Read docker-compose.yml + ansible.builtin.slurp: + src: /tmp/sovereign_test/authentik/docker-compose.yml + register: compose_raw + + - name: Set compose content fact + ansible.builtin.set_fact: + compose: "{{ compose_raw.content | b64decode }}" + + - name: Assert authentik server image reference in compose + ansible.builtin.assert: + that: "'ghcr.io/goauthentik/server:2024.10.5' in compose" + fail_msg: "Expected image 'ghcr.io/goauthentik/server:2024.10.5' not found in docker-compose.yml" + + - name: Assert authentik host rule in compose + ansible.builtin.assert: + that: "'Host(`auth.test.example.com`)' in compose" + fail_msg: "Expected Host rule for auth.test.example.com not found in docker-compose.yml" + + - name: Assert GELF logging address in compose + ansible.builtin.assert: + that: "'udp://127.0.0.1:12201' in compose" + fail_msg: "Expected GELF address udp://127.0.0.1:12201 not found in docker-compose.yml" + + - name: Assert sovereign network is external in compose + ansible.builtin.assert: + that: "'external: true' in compose" + fail_msg: "Expected 'external: true' not found in docker-compose.yml" + + - name: Assert authentik db password in compose + ansible.builtin.assert: + that: "'test_authentik_db' in compose" + fail_msg: "Expected authentik_db_password 'test_authentik_db' not found in docker-compose.yml" + + - name: Assert authentik secret key in compose + ansible.builtin.assert: + that: "'test-secret-key-exactly-50-chars-padded-here12345' in compose" + fail_msg: "Expected authentik_secret_key not found in docker-compose.yml" diff --git a/roles/authentik/tasks/main.yml b/roles/authentik/tasks/main.yml index fe0f641..217d55e 100644 --- a/roles/authentik/tasks/main.yml +++ b/roles/authentik/tasks/main.yml @@ -37,6 +37,7 @@ community.docker.docker_compose_v2: project_src: "{{ authentik_data_dir }}" state: present + when: not (molecule_test_mode | default(false)) - name: Wait for Authentik to be ready ansible.builtin.uri: @@ -47,3 +48,4 @@ until: result.status == 200 retries: 30 delay: 10 + when: not (molecule_test_mode | default(false)) diff --git a/roles/common/handlers/main.yml b/roles/common/handlers/main.yml index 0d5ec5f..e6b935b 100644 --- a/roles/common/handlers/main.yml +++ b/roles/common/handlers/main.yml @@ -4,3 +4,4 @@ project_src: "{{ traefik_data_dir }}" state: present recreate: always + when: not (molecule_test_mode | default(false)) diff --git a/roles/common/molecule/default/converge.yml b/roles/common/molecule/default/converge.yml new file mode 100644 index 0000000..fff090c --- /dev/null +++ b/roles/common/molecule/default/converge.yml @@ -0,0 +1,8 @@ +--- +- name: Converge + hosts: localhost + gather_facts: false + vars_files: + - ../../../molecule/shared/vars.yml + roles: + - role: common diff --git a/roles/common/molecule/default/molecule.yml b/roles/common/molecule/default/molecule.yml new file mode 100644 index 0000000..859036a --- /dev/null +++ b/roles/common/molecule/default/molecule.yml @@ -0,0 +1,23 @@ +--- +dependency: + name: galaxy + options: + requirements-file: requirements.yml +driver: + name: delegated + options: + managed: false + ansible_connection_options: + ansible_connection: local +platforms: + - name: localhost + groups: + - sovereign +provisioner: + name: ansible + inventory: + host_vars: + localhost: + ansible_connection: local +verifier: + name: ansible diff --git a/roles/common/molecule/default/verify.yml b/roles/common/molecule/default/verify.yml new file mode 100644 index 0000000..56c2afd --- /dev/null +++ b/roles/common/molecule/default/verify.yml @@ -0,0 +1,91 @@ +--- +- name: Verify common role + hosts: localhost + gather_facts: false + vars: + traefik_data_dir: /tmp/sovereign_test/traefik + + tasks: + - name: Check traefik data directory exists + ansible.builtin.stat: + path: /tmp/sovereign_test/traefik + register: data_dir_stat + + - name: Assert traefik data directory is present + ansible.builtin.assert: + that: data_dir_stat.stat.isdir + fail_msg: "Data directory /tmp/sovereign_test/traefik was not created" + + - name: Check traefik config directory exists + ansible.builtin.stat: + path: /tmp/sovereign_test/traefik/config + register: config_dir_stat + + - name: Assert traefik config directory is present + ansible.builtin.assert: + that: config_dir_stat.stat.isdir + fail_msg: "Config directory /tmp/sovereign_test/traefik/config was not created" + + - name: Check acme.json exists + ansible.builtin.stat: + path: /tmp/sovereign_test/traefik/acme.json + register: acme_stat + + - name: Assert acme.json is present + ansible.builtin.assert: + that: acme_stat.stat.exists + fail_msg: "acme.json was not created" + + - name: Assert acme.json has mode 0600 + ansible.builtin.assert: + that: acme_stat.stat.mode == '0600' + fail_msg: "acme.json does not have mode 0600 (got {{ acme_stat.stat.mode }})" + + - name: Check docker-compose.yml exists + ansible.builtin.stat: + path: /tmp/sovereign_test/traefik/docker-compose.yml + register: compose_stat + + - name: Assert docker-compose.yml was rendered + ansible.builtin.assert: + that: compose_stat.stat.exists + fail_msg: "docker-compose.yml was not rendered for common/traefik" + + - name: Read docker-compose.yml + ansible.builtin.slurp: + src: /tmp/sovereign_test/traefik/docker-compose.yml + register: compose_raw + + - name: Set compose content fact + ansible.builtin.set_fact: + compose: "{{ compose_raw.content | b64decode }}" + + - name: Assert traefik image reference in compose + ansible.builtin.assert: + that: "'traefik:v3.1' in compose" + fail_msg: "Expected image 'traefik:v3.1' not found in docker-compose.yml" + + - name: Assert traefik dashboard host rule in compose + ansible.builtin.assert: + that: "'Host(`traefik.test.example.com`)' in compose" + fail_msg: "Expected Host rule for traefik.test.example.com not found in docker-compose.yml" + + - name: Assert GELF logging address in compose + ansible.builtin.assert: + that: "'udp://127.0.0.1:12201' in compose" + fail_msg: "Expected GELF address udp://127.0.0.1:12201 not found in docker-compose.yml" + + - name: Assert sovereign network is external in compose + ansible.builtin.assert: + that: "'external: true' in compose" + fail_msg: "Expected 'external: true' not found in docker-compose.yml" + + - name: Assert letsencrypt certificate resolver in compose + ansible.builtin.assert: + that: "'letsencrypt' in compose" + fail_msg: "Expected 'letsencrypt' certificate resolver not found in docker-compose.yml" + + - name: Assert ACME email in compose + ansible.builtin.assert: + that: "'admin@test.example.com' in compose" + fail_msg: "Expected ACME email admin@test.example.com not found in docker-compose.yml" diff --git a/roles/common/tasks/main.yml b/roles/common/tasks/main.yml index c4b0ccb..c561371 100644 --- a/roles/common/tasks/main.yml +++ b/roles/common/tasks/main.yml @@ -11,16 +11,19 @@ - python3-docker state: present update_cache: true + when: not (molecule_test_mode | default(false)) - name: Add Docker GPG key ansible.builtin.apt_key: url: https://download.docker.com/linux/ubuntu/gpg state: present + when: not (molecule_test_mode | default(false)) - name: Add Docker repository ansible.builtin.apt_repository: repo: "deb [arch=amd64] https://download.docker.com/linux/ubuntu {{ ansible_distribution_release }} stable" state: present + when: not (molecule_test_mode | default(false)) - name: Install Docker ansible.builtin.apt: @@ -31,17 +34,20 @@ - docker-compose-plugin state: present update_cache: true + when: not (molecule_test_mode | default(false)) - name: Enable and start Docker ansible.builtin.systemd: name: docker enabled: true state: started + when: not (molecule_test_mode | default(false)) - name: Create sovereign Docker network community.docker.docker_network: name: "{{ sovereign_network_name }}" state: present + when: not (molecule_test_mode | default(false)) - name: Create Traefik data directory ansible.builtin.file: @@ -69,3 +75,4 @@ community.docker.docker_compose_v2: project_src: "{{ traefik_data_dir }}" state: present + when: not (molecule_test_mode | default(false)) diff --git a/roles/forgejo/handlers/main.yml b/roles/forgejo/handlers/main.yml index a46ab7b..4d6272b 100644 --- a/roles/forgejo/handlers/main.yml +++ b/roles/forgejo/handlers/main.yml @@ -4,3 +4,4 @@ project_src: "{{ forgejo_data_dir }}" state: present recreate: always + when: not (molecule_test_mode | default(false)) diff --git a/roles/forgejo/molecule/default/converge.yml b/roles/forgejo/molecule/default/converge.yml new file mode 100644 index 0000000..adfb1ea --- /dev/null +++ b/roles/forgejo/molecule/default/converge.yml @@ -0,0 +1,8 @@ +--- +- name: Converge + hosts: localhost + gather_facts: false + vars_files: + - ../../../molecule/shared/vars.yml + roles: + - role: forgejo diff --git a/roles/forgejo/molecule/default/molecule.yml b/roles/forgejo/molecule/default/molecule.yml new file mode 100644 index 0000000..859036a --- /dev/null +++ b/roles/forgejo/molecule/default/molecule.yml @@ -0,0 +1,23 @@ +--- +dependency: + name: galaxy + options: + requirements-file: requirements.yml +driver: + name: delegated + options: + managed: false + ansible_connection_options: + ansible_connection: local +platforms: + - name: localhost + groups: + - sovereign +provisioner: + name: ansible + inventory: + host_vars: + localhost: + ansible_connection: local +verifier: + name: ansible diff --git a/roles/forgejo/molecule/default/verify.yml b/roles/forgejo/molecule/default/verify.yml new file mode 100644 index 0000000..6cfe163 --- /dev/null +++ b/roles/forgejo/molecule/default/verify.yml @@ -0,0 +1,106 @@ +--- +- name: Verify forgejo role + hosts: localhost + gather_facts: false + vars: + forgejo_data_dir: /tmp/sovereign_test/forgejo + forgejo_domain: git.test.example.com + forgejo_version: latest + forgejo_db_password: test_forgejo_db + tenant_name: Test Corp + forgejo_ssh_port: 2222 + + tasks: + - name: Check forgejo data directory exists + ansible.builtin.stat: + path: "/tmp/sovereign_test/forgejo" + register: data_dir_stat + + - name: Assert forgejo data directory is present + ansible.builtin.assert: + that: data_dir_stat.stat.isdir + fail_msg: "Data directory /tmp/sovereign_test/forgejo was not created" + + - name: Check data subdirectory exists + ansible.builtin.stat: + path: "/tmp/sovereign_test/forgejo/data" + register: data_subdir_stat + + - name: Assert data subdirectory is present + ansible.builtin.assert: + that: data_subdir_stat.stat.isdir + fail_msg: "Data subdirectory /tmp/sovereign_test/forgejo/data was not created" + + - name: Check gitea public img subdirectory exists + ansible.builtin.stat: + path: "/tmp/sovereign_test/forgejo/data/gitea/public/img" + register: img_dir_stat + + - name: Assert gitea public img subdirectory is present + ansible.builtin.assert: + that: img_dir_stat.stat.isdir + fail_msg: "Directory /tmp/sovereign_test/forgejo/data/gitea/public/img was not created" + + - name: Check config subdirectory exists + ansible.builtin.stat: + path: "/tmp/sovereign_test/forgejo/config" + register: config_dir_stat + + - name: Assert config subdirectory is present + ansible.builtin.assert: + that: config_dir_stat.stat.isdir + fail_msg: "Config directory /tmp/sovereign_test/forgejo/config was not created" + + - name: Check docker-compose.yml exists + ansible.builtin.stat: + path: "/tmp/sovereign_test/forgejo/docker-compose.yml" + register: compose_stat + + - name: Assert docker-compose.yml was rendered + ansible.builtin.assert: + that: compose_stat.stat.exists + fail_msg: "docker-compose.yml was not rendered for forgejo" + + - name: Read docker-compose.yml + ansible.builtin.slurp: + src: "/tmp/sovereign_test/forgejo/docker-compose.yml" + register: compose_raw + + - name: Set compose content fact + ansible.builtin.set_fact: + compose: "{{ compose_raw.content | b64decode }}" + + - name: Assert forgejo image is present + ansible.builtin.assert: + that: "'codeberg.org/forgejo/forgejo' in compose" + fail_msg: "codeberg.org/forgejo/forgejo image not found in docker-compose.yml" + + - name: Assert forgejo domain traefik rule is present + ansible.builtin.assert: + that: "'Host(`git.test.example.com`)' in compose" + fail_msg: "Traefik rule for git.test.example.com not found in docker-compose.yml" + + - name: Assert app name env var is present + ansible.builtin.assert: + that: "'FORGEJO____APP_NAME: \"Test Corp\"' in compose" + fail_msg: "FORGEJO____APP_NAME: \"Test Corp\" not found in docker-compose.yml" + + - name: Assert forgejo db password is present + ansible.builtin.assert: + that: "'test_forgejo_db' in compose" + fail_msg: "forgejo_db_password not found in docker-compose.yml" + + - name: Assert GELF logging address is present + ansible.builtin.assert: + that: "'udp://127.0.0.1:12201' in compose" + fail_msg: "GELF logging address udp://127.0.0.1:12201 not found in docker-compose.yml" + + - name: Assert sovereign network is external + ansible.builtin.assert: + that: "'external: true' in compose" + fail_msg: "external: true not found in docker-compose.yml networks section" + + - name: Assert SSH port is present + ansible.builtin.assert: + that: "'2222' in compose" + fail_msg: "forgejo_ssh_port 2222 not found in docker-compose.yml" diff --git a/roles/forgejo/tasks/main.yml b/roles/forgejo/tasks/main.yml index d7ee855..af4e4fa 100644 --- a/roles/forgejo/tasks/main.yml +++ b/roles/forgejo/tasks/main.yml @@ -28,3 +28,4 @@ community.docker.docker_compose_v2: project_src: "{{ forgejo_data_dir }}" state: present + when: not (molecule_test_mode | default(false)) diff --git a/roles/graylog/handlers/main.yml b/roles/graylog/handlers/main.yml index c85ac0b..ae13f1d 100644 --- a/roles/graylog/handlers/main.yml +++ b/roles/graylog/handlers/main.yml @@ -4,3 +4,4 @@ project_src: "{{ graylog_data_dir }}" state: present recreate: always + when: not (molecule_test_mode | default(false)) diff --git a/roles/graylog/molecule/default/converge.yml b/roles/graylog/molecule/default/converge.yml new file mode 100644 index 0000000..8b69c81 --- /dev/null +++ b/roles/graylog/molecule/default/converge.yml @@ -0,0 +1,8 @@ +--- +- name: Converge + hosts: localhost + gather_facts: false + vars_files: + - ../../../molecule/shared/vars.yml + roles: + - role: graylog diff --git a/roles/graylog/molecule/default/molecule.yml b/roles/graylog/molecule/default/molecule.yml new file mode 100644 index 0000000..859036a --- /dev/null +++ b/roles/graylog/molecule/default/molecule.yml @@ -0,0 +1,23 @@ +--- +dependency: + name: galaxy + options: + requirements-file: requirements.yml +driver: + name: delegated + options: + managed: false + ansible_connection_options: + ansible_connection: local +platforms: + - name: localhost + groups: + - sovereign +provisioner: + name: ansible + inventory: + host_vars: + localhost: + ansible_connection: local +verifier: + name: ansible diff --git a/roles/graylog/molecule/default/verify.yml b/roles/graylog/molecule/default/verify.yml new file mode 100644 index 0000000..a07cb92 --- /dev/null +++ b/roles/graylog/molecule/default/verify.yml @@ -0,0 +1,96 @@ +--- +- name: Verify graylog role + hosts: localhost + gather_facts: false + vars: + graylog_data_dir: /tmp/sovereign_test/graylog + + tasks: + - name: Check graylog data directory exists + ansible.builtin.stat: + path: /tmp/sovereign_test/graylog + register: data_dir_stat + + - name: Assert graylog data directory is present + ansible.builtin.assert: + that: data_dir_stat.stat.isdir + fail_msg: "Data directory /tmp/sovereign_test/graylog was not created" + + - name: Check graylog data subdirectory exists + ansible.builtin.stat: + path: /tmp/sovereign_test/graylog/data + register: data_subdir_stat + + - name: Assert graylog data subdirectory is present + ansible.builtin.assert: + that: data_subdir_stat.stat.isdir + fail_msg: "Directory /tmp/sovereign_test/graylog/data was not created" + + - name: Check graylog config directory exists + ansible.builtin.stat: + path: /tmp/sovereign_test/graylog/config + register: config_dir_stat + + - name: Assert graylog config directory is present + ansible.builtin.assert: + that: config_dir_stat.stat.isdir + fail_msg: "Directory /tmp/sovereign_test/graylog/config was not created" + + - name: Check graylog opensearch directory exists + ansible.builtin.stat: + path: /tmp/sovereign_test/graylog/opensearch + register: opensearch_dir_stat + + - name: Assert graylog opensearch directory is present + ansible.builtin.assert: + that: opensearch_dir_stat.stat.isdir + fail_msg: "Directory /tmp/sovereign_test/graylog/opensearch was not created" + + - name: Check docker-compose.yml exists + ansible.builtin.stat: + path: /tmp/sovereign_test/graylog/docker-compose.yml + register: compose_stat + + - name: Assert docker-compose.yml was rendered + ansible.builtin.assert: + that: compose_stat.stat.exists + fail_msg: "docker-compose.yml was not rendered for graylog" + + - name: Read docker-compose.yml + ansible.builtin.slurp: + src: /tmp/sovereign_test/graylog/docker-compose.yml + register: compose_raw + + - name: Set compose content fact + ansible.builtin.set_fact: + compose: "{{ compose_raw.content | b64decode }}" + + - name: Assert graylog image reference in compose + ansible.builtin.assert: + that: "'graylog/graylog:6.0' in compose" + fail_msg: "Expected image 'graylog/graylog:6.0' not found in docker-compose.yml" + + - name: Assert graylog host rule in compose + ansible.builtin.assert: + that: "'Host(`logs.test.example.com`)' in compose" + fail_msg: "Expected Host rule for logs.test.example.com not found in docker-compose.yml" + + - name: Assert GELF logging address in compose + ansible.builtin.assert: + that: "'udp://127.0.0.1:12201' in compose" + fail_msg: "Expected GELF address udp://127.0.0.1:12201 not found in docker-compose.yml" + + - name: Assert sovereign network is external in compose + ansible.builtin.assert: + that: "'external: true' in compose" + fail_msg: "Expected 'external: true' not found in docker-compose.yml" + + - name: Assert graylog password secret in compose + ansible.builtin.assert: + that: "'test_graylog_secret_min_16_chars' in compose" + fail_msg: "Expected graylog_password_secret 'test_graylog_secret_min_16_chars' not found in docker-compose.yml" + + - name: Assert graylog root password sha2 in compose + ansible.builtin.assert: + that: "'test_sha256_placeholder' in compose" + fail_msg: "Expected graylog_root_password_sha2 'test_sha256_placeholder' not found in docker-compose.yml" diff --git a/roles/graylog/tasks/main.yml b/roles/graylog/tasks/main.yml index a21fc57..d4c115d 100644 --- a/roles/graylog/tasks/main.yml +++ b/roles/graylog/tasks/main.yml @@ -16,6 +16,7 @@ owner: "1000" group: "1000" mode: '0775' + when: not (molecule_test_mode | default(false)) - name: Set vm.max_map_count for OpenSearch ansible.posix.sysctl: @@ -23,6 +24,7 @@ value: '262144' state: present sysctl_set: true + when: not (molecule_test_mode | default(false)) - name: Deploy Graylog docker-compose ansible.builtin.template: @@ -35,6 +37,7 @@ community.docker.docker_compose_v2: project_src: "{{ graylog_data_dir }}" state: present + when: not (molecule_test_mode | default(false)) - name: Wait for Graylog to be ready ansible.builtin.uri: @@ -48,3 +51,4 @@ until: result.status == 200 retries: 30 delay: 10 + when: not (molecule_test_mode | default(false)) diff --git a/roles/headscale/handlers/main.yml b/roles/headscale/handlers/main.yml index d43b2c6..a4cd640 100644 --- a/roles/headscale/handlers/main.yml +++ b/roles/headscale/handlers/main.yml @@ -4,3 +4,4 @@ project_src: "{{ headscale_data_dir }}" state: present recreate: always + when: not (molecule_test_mode | default(false)) diff --git a/roles/headscale/molecule/default/converge.yml b/roles/headscale/molecule/default/converge.yml new file mode 100644 index 0000000..89fccd2 --- /dev/null +++ b/roles/headscale/molecule/default/converge.yml @@ -0,0 +1,8 @@ +--- +- name: Converge + hosts: localhost + gather_facts: false + vars_files: + - ../../../molecule/shared/vars.yml + roles: + - role: headscale diff --git a/roles/headscale/molecule/default/molecule.yml b/roles/headscale/molecule/default/molecule.yml new file mode 100644 index 0000000..859036a --- /dev/null +++ b/roles/headscale/molecule/default/molecule.yml @@ -0,0 +1,23 @@ +--- +dependency: + name: galaxy + options: + requirements-file: requirements.yml +driver: + name: delegated + options: + managed: false + ansible_connection_options: + ansible_connection: local +platforms: + - name: localhost + groups: + - sovereign +provisioner: + name: ansible + inventory: + host_vars: + localhost: + ansible_connection: local +verifier: + name: ansible diff --git a/roles/headscale/molecule/default/verify.yml b/roles/headscale/molecule/default/verify.yml new file mode 100644 index 0000000..117c17c --- /dev/null +++ b/roles/headscale/molecule/default/verify.yml @@ -0,0 +1,113 @@ +--- +- name: Verify headscale role + hosts: localhost + gather_facts: false + vars: + headscale_data_dir: /tmp/sovereign_test/headscale + headscale_domain: headscale.test.example.com + base_domain: test.example.com + authentik_domain: auth.test.example.com + + tasks: + - name: Check headscale data directory exists + ansible.builtin.stat: + path: "/tmp/sovereign_test/headscale" + register: data_dir_stat + + - name: Assert headscale data directory is present + ansible.builtin.assert: + that: data_dir_stat.stat.isdir + fail_msg: "Data directory /tmp/sovereign_test/headscale was not created" + + - name: Check config subdirectory exists + ansible.builtin.stat: + path: "/tmp/sovereign_test/headscale/config" + register: config_dir_stat + + - name: Assert config subdirectory is present + ansible.builtin.assert: + that: config_dir_stat.stat.isdir + fail_msg: "Config directory /tmp/sovereign_test/headscale/config was not created" + + - name: Check data subdirectory exists + ansible.builtin.stat: + path: "/tmp/sovereign_test/headscale/data" + register: data_subdir_stat + + - name: Assert data subdirectory is present + ansible.builtin.assert: + that: data_subdir_stat.stat.isdir + fail_msg: "Data subdirectory /tmp/sovereign_test/headscale/data was not created" + + - name: Check config.yaml exists + ansible.builtin.stat: + path: "/tmp/sovereign_test/headscale/config/config.yaml" + register: config_yaml_stat + + - name: Assert config.yaml was rendered + ansible.builtin.assert: + that: config_yaml_stat.stat.exists + fail_msg: "config/config.yaml was not rendered for headscale" + + - name: Read config.yaml + ansible.builtin.slurp: + src: "/tmp/sovereign_test/headscale/config/config.yaml" + register: config_yaml_raw + + - name: Set config content fact + ansible.builtin.set_fact: + headscale_config: "{{ config_yaml_raw.content | b64decode }}" + + - name: Assert config contains headscale server URL + ansible.builtin.assert: + that: "'https://headscale.test.example.com' in headscale_config" + fail_msg: "config.yaml does not contain https://headscale.test.example.com" + + - name: Assert config contains base_domain + ansible.builtin.assert: + that: "'base_domain: \"test.example.com\"' in headscale_config" + fail_msg: "config.yaml does not contain base_domain: \"test.example.com\"" + + - name: Assert config contains authentik domain for OIDC + ansible.builtin.assert: + that: "'auth.test.example.com' in headscale_config" + fail_msg: "config.yaml does not contain auth.test.example.com for OIDC" + + - name: Check docker-compose.yml exists + ansible.builtin.stat: + path: "/tmp/sovereign_test/headscale/docker-compose.yml" + register: compose_stat + + - name: Assert docker-compose.yml was rendered + ansible.builtin.assert: + that: compose_stat.stat.exists + fail_msg: "docker-compose.yml was not rendered for headscale" + + - name: Read docker-compose.yml + ansible.builtin.slurp: + src: "/tmp/sovereign_test/headscale/docker-compose.yml" + register: compose_raw + + - name: Set compose content fact + ansible.builtin.set_fact: + compose: "{{ compose_raw.content | b64decode }}" + + - name: Assert headscale image is present + ansible.builtin.assert: + that: "'headscale/headscale' in compose" + fail_msg: "headscale/headscale image not found in docker-compose.yml" + + - name: Assert headscale domain traefik rule is present + ansible.builtin.assert: + that: "'Host(`headscale.test.example.com`)' in compose" + fail_msg: "Traefik rule for headscale.test.example.com not found in docker-compose.yml" + + - name: Assert GELF logging address is present + ansible.builtin.assert: + that: "'udp://127.0.0.1:12201' in compose" + fail_msg: "GELF logging address udp://127.0.0.1:12201 not found in docker-compose.yml" + + - name: Assert sovereign network is external + ansible.builtin.assert: + that: "'external: true' in compose" + fail_msg: "external: true not found in docker-compose.yml networks section" diff --git a/roles/headscale/tasks/main.yml b/roles/headscale/tasks/main.yml index 42ba331..1ef3369 100644 --- a/roles/headscale/tasks/main.yml +++ b/roles/headscale/tasks/main.yml @@ -27,3 +27,4 @@ community.docker.docker_compose_v2: project_src: "{{ headscale_data_dir }}" state: present + when: not (molecule_test_mode | default(false)) diff --git a/roles/jitsi/handlers/main.yml b/roles/jitsi/handlers/main.yml index 7a03733..221366c 100644 --- a/roles/jitsi/handlers/main.yml +++ b/roles/jitsi/handlers/main.yml @@ -4,3 +4,4 @@ project_src: "{{ jitsi_data_dir }}" state: present recreate: always + when: not (molecule_test_mode | default(false)) diff --git a/roles/jitsi/molecule/default/converge.yml b/roles/jitsi/molecule/default/converge.yml new file mode 100644 index 0000000..3edd7e8 --- /dev/null +++ b/roles/jitsi/molecule/default/converge.yml @@ -0,0 +1,8 @@ +--- +- name: Converge + hosts: localhost + gather_facts: false + vars_files: + - ../../../molecule/shared/vars.yml + roles: + - role: jitsi diff --git a/roles/jitsi/molecule/default/molecule.yml b/roles/jitsi/molecule/default/molecule.yml new file mode 100644 index 0000000..859036a --- /dev/null +++ b/roles/jitsi/molecule/default/molecule.yml @@ -0,0 +1,23 @@ +--- +dependency: + name: galaxy + options: + requirements-file: requirements.yml +driver: + name: delegated + options: + managed: false + ansible_connection_options: + ansible_connection: local +platforms: + - name: localhost + groups: + - sovereign +provisioner: + name: ansible + inventory: + host_vars: + localhost: + ansible_connection: local +verifier: + name: ansible diff --git a/roles/jitsi/molecule/default/verify.yml b/roles/jitsi/molecule/default/verify.yml new file mode 100644 index 0000000..f8dcd89 --- /dev/null +++ b/roles/jitsi/molecule/default/verify.yml @@ -0,0 +1,148 @@ +--- +- name: Verify jitsi role + hosts: localhost + gather_facts: false + vars: + jitsi_data_dir: /tmp/sovereign_test/jitsi + jitsi_domain: meet.test.example.com + tenant_name: Test Corp + base_domain: test.example.com + + tasks: + - name: Check jitsi data directory exists + ansible.builtin.stat: + path: "/tmp/sovereign_test/jitsi" + register: data_dir_stat + + - name: Assert jitsi data directory is present + ansible.builtin.assert: + that: data_dir_stat.stat.isdir + fail_msg: "Data directory /tmp/sovereign_test/jitsi was not created" + + - name: Check web subdirectory exists + ansible.builtin.stat: + path: "/tmp/sovereign_test/jitsi/web" + register: web_dir_stat + + - name: Assert web subdirectory is present + ansible.builtin.assert: + that: web_dir_stat.stat.isdir + fail_msg: "Web directory /tmp/sovereign_test/jitsi/web was not created" + + - name: Check prosody subdirectory exists + ansible.builtin.stat: + path: "/tmp/sovereign_test/jitsi/prosody" + register: prosody_dir_stat + + - name: Assert prosody subdirectory is present + ansible.builtin.assert: + that: prosody_dir_stat.stat.isdir + fail_msg: "Prosody directory /tmp/sovereign_test/jitsi/prosody was not created" + + - name: Check jicofo subdirectory exists + ansible.builtin.stat: + path: "/tmp/sovereign_test/jitsi/jicofo" + register: jicofo_dir_stat + + - name: Assert jicofo subdirectory is present + ansible.builtin.assert: + that: jicofo_dir_stat.stat.isdir + fail_msg: "Jicofo directory /tmp/sovereign_test/jitsi/jicofo was not created" + + - name: Check jvb subdirectory exists + ansible.builtin.stat: + path: "/tmp/sovereign_test/jitsi/jvb" + register: jvb_dir_stat + + - name: Assert jvb subdirectory is present + ansible.builtin.assert: + that: jvb_dir_stat.stat.isdir + fail_msg: "JVB directory /tmp/sovereign_test/jitsi/jvb was not created" + + - name: Check custom-interface-config.js exists + ansible.builtin.stat: + path: "/tmp/sovereign_test/jitsi/web/custom-interface-config.js" + register: interface_config_stat + + - name: Assert custom-interface-config.js was rendered + ansible.builtin.assert: + that: interface_config_stat.stat.exists + fail_msg: "web/custom-interface-config.js was not rendered for jitsi" + + - name: Read custom-interface-config.js + ansible.builtin.slurp: + src: "/tmp/sovereign_test/jitsi/web/custom-interface-config.js" + register: interface_config_raw + + - name: Set interface config content fact + ansible.builtin.set_fact: + interface_config: "{{ interface_config_raw.content | b64decode }}" + + - name: Assert interface config contains tenant name + ansible.builtin.assert: + that: "'Test Corp' in interface_config" + fail_msg: "custom-interface-config.js does not contain tenant name 'Test Corp'" + + - name: Assert interface config contains base domain + ansible.builtin.assert: + that: "'test.example.com' in interface_config" + fail_msg: "custom-interface-config.js does not contain base_domain test.example.com" + + - name: Check docker-compose.yml exists + ansible.builtin.stat: + path: "/tmp/sovereign_test/jitsi/docker-compose.yml" + register: compose_stat + + - name: Assert docker-compose.yml was rendered + ansible.builtin.assert: + that: compose_stat.stat.exists + fail_msg: "docker-compose.yml was not rendered for jitsi" + + - name: Read docker-compose.yml + ansible.builtin.slurp: + src: "/tmp/sovereign_test/jitsi/docker-compose.yml" + register: compose_raw + + - name: Set compose content fact + ansible.builtin.set_fact: + compose: "{{ compose_raw.content | b64decode }}" + + - name: Assert jitsi web image is present + ansible.builtin.assert: + that: "'jitsi/web' in compose" + fail_msg: "jitsi/web image not found in docker-compose.yml" + + - name: Assert jitsi prosody image is present + ansible.builtin.assert: + that: "'jitsi/prosody' in compose" + fail_msg: "jitsi/prosody image not found in docker-compose.yml" + + - name: Assert jitsi jicofo image is present + ansible.builtin.assert: + that: "'jitsi/jicofo' in compose" + fail_msg: "jitsi/jicofo image not found in docker-compose.yml" + + - name: Assert jitsi jvb image is present + ansible.builtin.assert: + that: "'jitsi/jvb' in compose" + fail_msg: "jitsi/jvb image not found in docker-compose.yml" + + - name: Assert jitsi domain traefik rule is present + ansible.builtin.assert: + that: "'Host(`meet.test.example.com`)' in compose" + fail_msg: "Traefik rule for meet.test.example.com not found in docker-compose.yml" + + - name: Assert JITSI_WATERMARKLINK env var is present + ansible.builtin.assert: + that: "'JITSI_WATERMARKLINK' in compose" + fail_msg: "JITSI_WATERMARKLINK not found in docker-compose.yml" + + - name: Assert GELF logging address is present + ansible.builtin.assert: + that: "'udp://127.0.0.1:12201' in compose" + fail_msg: "GELF logging address udp://127.0.0.1:12201 not found in docker-compose.yml" + + - name: Assert sovereign network is external + ansible.builtin.assert: + that: "'external: true' in compose" + fail_msg: "external: true not found in docker-compose.yml networks section" diff --git a/roles/jitsi/tasks/main.yml b/roles/jitsi/tasks/main.yml index d5705f2..a194a10 100644 --- a/roles/jitsi/tasks/main.yml +++ b/roles/jitsi/tasks/main.yml @@ -37,3 +37,4 @@ community.docker.docker_compose_v2: project_src: "{{ jitsi_data_dir }}" state: present + when: not (molecule_test_mode | default(false)) diff --git a/roles/matrix/handlers/main.yml b/roles/matrix/handlers/main.yml index 2ab0613..6d00cc5 100644 --- a/roles/matrix/handlers/main.yml +++ b/roles/matrix/handlers/main.yml @@ -4,3 +4,4 @@ project_src: "{{ matrix_data_dir }}" state: present recreate: always + when: not (molecule_test_mode | default(false)) diff --git a/roles/matrix/molecule/default/converge.yml b/roles/matrix/molecule/default/converge.yml new file mode 100644 index 0000000..9888565 --- /dev/null +++ b/roles/matrix/molecule/default/converge.yml @@ -0,0 +1,8 @@ +--- +- name: Converge + hosts: localhost + gather_facts: false + vars_files: + - ../../../molecule/shared/vars.yml + roles: + - role: matrix diff --git a/roles/matrix/molecule/default/molecule.yml b/roles/matrix/molecule/default/molecule.yml new file mode 100644 index 0000000..859036a --- /dev/null +++ b/roles/matrix/molecule/default/molecule.yml @@ -0,0 +1,23 @@ +--- +dependency: + name: galaxy + options: + requirements-file: requirements.yml +driver: + name: delegated + options: + managed: false + ansible_connection_options: + ansible_connection: local +platforms: + - name: localhost + groups: + - sovereign +provisioner: + name: ansible + inventory: + host_vars: + localhost: + ansible_connection: local +verifier: + name: ansible diff --git a/roles/matrix/molecule/default/verify.yml b/roles/matrix/molecule/default/verify.yml new file mode 100644 index 0000000..fcc42a5 --- /dev/null +++ b/roles/matrix/molecule/default/verify.yml @@ -0,0 +1,141 @@ +--- +- name: Verify matrix role + hosts: localhost + gather_facts: false + vars: + matrix_data_dir: /tmp/sovereign_test/matrix + matrix_domain: matrix.test.example.com + element_domain: chat.test.example.com + matrix_version: v1.118.0 + matrix_db_password: test_matrix_db + jitsi_domain: meet.test.example.com + tenant_name: Test Corp + element_theme: light + + tasks: + - name: Check matrix data directory exists + ansible.builtin.stat: + path: "/tmp/sovereign_test/matrix" + register: data_dir_stat + + - name: Assert matrix data directory is present + ansible.builtin.assert: + that: data_dir_stat.stat.isdir + fail_msg: "Data directory /tmp/sovereign_test/matrix was not created" + + - name: Check synapse subdirectory exists + ansible.builtin.stat: + path: "/tmp/sovereign_test/matrix/synapse" + register: synapse_dir_stat + + - name: Assert synapse subdirectory is present + ansible.builtin.assert: + that: synapse_dir_stat.stat.isdir + fail_msg: "Synapse directory /tmp/sovereign_test/matrix/synapse was not created" + + - name: Check element subdirectory exists + ansible.builtin.stat: + path: "/tmp/sovereign_test/matrix/element" + register: element_dir_stat + + - name: Assert element subdirectory is present + ansible.builtin.assert: + that: element_dir_stat.stat.isdir + fail_msg: "Element directory /tmp/sovereign_test/matrix/element was not created" + + - name: Check element config.json exists + ansible.builtin.stat: + path: "/tmp/sovereign_test/matrix/element/config.json" + register: element_config_stat + + - name: Assert element config.json was rendered + ansible.builtin.assert: + that: element_config_stat.stat.exists + fail_msg: "element/config.json was not rendered for matrix" + + - name: Read element config.json + ansible.builtin.slurp: + src: "/tmp/sovereign_test/matrix/element/config.json" + register: element_config_raw + + - name: Parse element config.json as JSON + ansible.builtin.set_fact: + element_config_parsed: "{{ element_config_raw.content | b64decode | from_json }}" + + - name: Assert element config.json is valid JSON + ansible.builtin.assert: + that: element_config_parsed is mapping + fail_msg: "element/config.json could not be parsed as valid JSON" + + - name: Set element config content fact + ansible.builtin.set_fact: + element_config: "{{ element_config_raw.content | b64decode }}" + + - name: Assert element config contains tenant brand name + ansible.builtin.assert: + that: '"brand": "Test Corp"' in element_config + fail_msg: "element/config.json does not contain brand: Test Corp" + + - name: Assert element config contains matrix homeserver URL + ansible.builtin.assert: + that: '"https://matrix.test.example.com"' in element_config + fail_msg: "element/config.json does not contain https://matrix.test.example.com" + + - name: Assert element config contains jitsi domain + ansible.builtin.assert: + that: '"meet.test.example.com"' in element_config + fail_msg: "element/config.json does not contain meet.test.example.com" + + - name: Assert element config contains default theme + ansible.builtin.assert: + that: '"default_theme": "light"' in element_config + fail_msg: "element/config.json does not contain default_theme: light" + + - name: Check docker-compose.yml exists + ansible.builtin.stat: + path: "/tmp/sovereign_test/matrix/docker-compose.yml" + register: compose_stat + + - name: Assert docker-compose.yml was rendered + ansible.builtin.assert: + that: compose_stat.stat.exists + fail_msg: "docker-compose.yml was not rendered for matrix" + + - name: Read docker-compose.yml + ansible.builtin.slurp: + src: "/tmp/sovereign_test/matrix/docker-compose.yml" + register: compose_raw + + - name: Set compose content fact + ansible.builtin.set_fact: + compose: "{{ compose_raw.content | b64decode }}" + + - name: Assert synapse image with version is present + ansible.builtin.assert: + that: "'ghcr.io/element-hq/synapse:v1.118.0' in compose" + fail_msg: "Expected synapse image ghcr.io/element-hq/synapse:v1.118.0 not found in docker-compose.yml" + + - name: Assert matrix domain traefik rule is present + ansible.builtin.assert: + that: "'Host(`matrix.test.example.com`)' in compose" + fail_msg: "Traefik rule for matrix.test.example.com not found in docker-compose.yml" + + - name: Assert element domain traefik rule is present + ansible.builtin.assert: + that: "'Host(`chat.test.example.com`)' in compose" + fail_msg: "Traefik rule for chat.test.example.com not found in docker-compose.yml" + + - name: Assert matrix db password is present + ansible.builtin.assert: + that: "'test_matrix_db' in compose" + fail_msg: "matrix_db_password not found in docker-compose.yml" + + - name: Assert GELF logging address is present + ansible.builtin.assert: + that: "'udp://127.0.0.1:12201' in compose" + fail_msg: "GELF logging address udp://127.0.0.1:12201 not found in docker-compose.yml" + + - name: Assert sovereign network is external + ansible.builtin.assert: + that: "'external: true' in compose" + fail_msg: "external: true not found in docker-compose.yml networks section" diff --git a/roles/matrix/tasks/main.yml b/roles/matrix/tasks/main.yml index d6baaf9..4fbe9e0 100644 --- a/roles/matrix/tasks/main.yml +++ b/roles/matrix/tasks/main.yml @@ -19,6 +19,7 @@ ghcr.io/element-hq/synapse:{{ matrix_version }} generate creates: "{{ matrix_data_dir }}/synapse/homeserver.yaml" + when: not (molecule_test_mode | default(false)) - name: Deploy Element config ansible.builtin.template: @@ -37,3 +38,4 @@ community.docker.docker_compose_v2: project_src: "{{ matrix_data_dir }}" state: present + when: not (molecule_test_mode | default(false)) diff --git a/roles/minio/handlers/main.yml b/roles/minio/handlers/main.yml index 561d5bc..87aac50 100644 --- a/roles/minio/handlers/main.yml +++ b/roles/minio/handlers/main.yml @@ -4,3 +4,4 @@ project_src: "{{ minio_data_dir }}" state: present recreate: always + when: not (molecule_test_mode | default(false)) diff --git a/roles/minio/molecule/default/converge.yml b/roles/minio/molecule/default/converge.yml new file mode 100644 index 0000000..fd8af9a --- /dev/null +++ b/roles/minio/molecule/default/converge.yml @@ -0,0 +1,8 @@ +--- +- name: Converge + hosts: localhost + gather_facts: false + vars_files: + - ../../../molecule/shared/vars.yml + roles: + - role: minio diff --git a/roles/minio/molecule/default/molecule.yml b/roles/minio/molecule/default/molecule.yml new file mode 100644 index 0000000..859036a --- /dev/null +++ b/roles/minio/molecule/default/molecule.yml @@ -0,0 +1,23 @@ +--- +dependency: + name: galaxy + options: + requirements-file: requirements.yml +driver: + name: delegated + options: + managed: false + ansible_connection_options: + ansible_connection: local +platforms: + - name: localhost + groups: + - sovereign +provisioner: + name: ansible + inventory: + host_vars: + localhost: + ansible_connection: local +verifier: + name: ansible diff --git a/roles/minio/molecule/default/verify.yml b/roles/minio/molecule/default/verify.yml new file mode 100644 index 0000000..506dc85 --- /dev/null +++ b/roles/minio/molecule/default/verify.yml @@ -0,0 +1,81 @@ +--- +- name: Verify minio role + hosts: localhost + gather_facts: false + vars: + minio_data_dir: /tmp/sovereign_test/minio + + tasks: + - name: Check minio data directory exists + ansible.builtin.stat: + path: /tmp/sovereign_test/minio + register: data_dir_stat + + - name: Assert minio data directory is present + ansible.builtin.assert: + that: data_dir_stat.stat.isdir + fail_msg: "Data directory /tmp/sovereign_test/minio was not created" + + - name: Check minio data subdirectory exists + ansible.builtin.stat: + path: /tmp/sovereign_test/minio/data + register: data_subdir_stat + + - name: Assert minio data subdirectory is present + ansible.builtin.assert: + that: data_subdir_stat.stat.isdir + fail_msg: "Directory /tmp/sovereign_test/minio/data was not created" + + - name: Check docker-compose.yml exists + ansible.builtin.stat: + path: /tmp/sovereign_test/minio/docker-compose.yml + register: compose_stat + + - name: Assert docker-compose.yml was rendered + ansible.builtin.assert: + that: compose_stat.stat.exists + fail_msg: "docker-compose.yml was not rendered for minio" + + - name: Read docker-compose.yml + ansible.builtin.slurp: + src: /tmp/sovereign_test/minio/docker-compose.yml + register: compose_raw + + - name: Set compose content fact + ansible.builtin.set_fact: + compose: "{{ compose_raw.content | b64decode }}" + + - name: Assert minio image reference in compose + ansible.builtin.assert: + that: "'quay.io/minio/minio' in compose" + fail_msg: "Expected image 'quay.io/minio/minio' not found in docker-compose.yml" + + - name: Assert minio S3 API host rule in compose + ansible.builtin.assert: + that: "'Host(`s3.test.example.com`)' in compose" + fail_msg: "Expected Host rule for s3.test.example.com not found in docker-compose.yml" + + - name: Assert minio console host rule in compose + ansible.builtin.assert: + that: "'Host(`minio.test.example.com`)' in compose" + fail_msg: "Expected Host rule for minio.test.example.com not found in docker-compose.yml" + + - name: Assert GELF logging address in compose + ansible.builtin.assert: + that: "'udp://127.0.0.1:12201' in compose" + fail_msg: "Expected GELF address udp://127.0.0.1:12201 not found in docker-compose.yml" + + - name: Assert sovereign network is external in compose + ansible.builtin.assert: + that: "'external: true' in compose" + fail_msg: "Expected 'external: true' not found in docker-compose.yml" + + - name: Assert minio root user in compose + ansible.builtin.assert: + that: "'minioadmin' in compose" + fail_msg: "Expected minio_root_user 'minioadmin' not found in docker-compose.yml" + + - name: Assert minio root password in compose + ansible.builtin.assert: + that: "'test_minio' in compose" + fail_msg: "Expected minio_root_password 'test_minio' not found in docker-compose.yml" diff --git a/roles/minio/tasks/main.yml b/roles/minio/tasks/main.yml index 5557cbb..e78efa8 100644 --- a/roles/minio/tasks/main.yml +++ b/roles/minio/tasks/main.yml @@ -19,6 +19,7 @@ community.docker.docker_compose_v2: project_src: "{{ minio_data_dir }}" state: present + when: not (molecule_test_mode | default(false)) - name: Wait for MinIO to be ready ansible.builtin.uri: @@ -29,6 +30,7 @@ until: result.status == 200 retries: 15 delay: 5 + when: not (molecule_test_mode | default(false)) - name: Create Nextcloud bucket in MinIO community.general.minio: @@ -38,3 +40,4 @@ name: "{{ minio_nextcloud_bucket }}" state: present ignore_errors: true + when: not (molecule_test_mode | default(false)) diff --git a/roles/nextcloud/handlers/main.yml b/roles/nextcloud/handlers/main.yml index b56acff..12b5f7f 100644 --- a/roles/nextcloud/handlers/main.yml +++ b/roles/nextcloud/handlers/main.yml @@ -4,3 +4,4 @@ project_src: "{{ nextcloud_data_dir }}" state: present recreate: always + when: not (molecule_test_mode | default(false)) diff --git a/roles/nextcloud/molecule/default/converge.yml b/roles/nextcloud/molecule/default/converge.yml new file mode 100644 index 0000000..3adf949 --- /dev/null +++ b/roles/nextcloud/molecule/default/converge.yml @@ -0,0 +1,8 @@ +--- +- name: Converge + hosts: localhost + gather_facts: false + vars_files: + - ../../../molecule/shared/vars.yml + roles: + - role: nextcloud diff --git a/roles/nextcloud/molecule/default/molecule.yml b/roles/nextcloud/molecule/default/molecule.yml new file mode 100644 index 0000000..859036a --- /dev/null +++ b/roles/nextcloud/molecule/default/molecule.yml @@ -0,0 +1,23 @@ +--- +dependency: + name: galaxy + options: + requirements-file: requirements.yml +driver: + name: delegated + options: + managed: false + ansible_connection_options: + ansible_connection: local +platforms: + - name: localhost + groups: + - sovereign +provisioner: + name: ansible + inventory: + host_vars: + localhost: + ansible_connection: local +verifier: + name: ansible diff --git a/roles/nextcloud/molecule/default/verify.yml b/roles/nextcloud/molecule/default/verify.yml new file mode 100644 index 0000000..535c3d9 --- /dev/null +++ b/roles/nextcloud/molecule/default/verify.yml @@ -0,0 +1,76 @@ +--- +- name: Verify nextcloud role + hosts: localhost + gather_facts: false + vars: + nextcloud_data_dir: /tmp/sovereign_test/nextcloud + + tasks: + - name: Check nextcloud data directory exists + ansible.builtin.stat: + path: /tmp/sovereign_test/nextcloud + register: data_dir_stat + + - name: Assert nextcloud data directory is present + ansible.builtin.assert: + that: data_dir_stat.stat.isdir + fail_msg: "Data directory /tmp/sovereign_test/nextcloud was not created" + + - name: Check nextcloud data subdirectory exists + ansible.builtin.stat: + path: /tmp/sovereign_test/nextcloud/data + register: data_subdir_stat + + - name: Assert nextcloud data subdirectory is present + ansible.builtin.assert: + that: data_subdir_stat.stat.isdir + fail_msg: "Directory /tmp/sovereign_test/nextcloud/data was not created" + + - name: Check docker-compose.yml exists + ansible.builtin.stat: + path: /tmp/sovereign_test/nextcloud/docker-compose.yml + register: compose_stat + + - name: Assert docker-compose.yml was rendered + ansible.builtin.assert: + that: compose_stat.stat.exists + fail_msg: "docker-compose.yml was not rendered for nextcloud" + + - name: Read docker-compose.yml + ansible.builtin.slurp: + src: /tmp/sovereign_test/nextcloud/docker-compose.yml + register: compose_raw + + - name: Set compose content fact + ansible.builtin.set_fact: + compose: "{{ compose_raw.content | b64decode }}" + + - name: Assert nextcloud image reference in compose + ansible.builtin.assert: + that: "'nextcloud:29' in compose" + fail_msg: "Expected image 'nextcloud:29' not found in docker-compose.yml" + + - name: Assert nextcloud host rule in compose + ansible.builtin.assert: + that: "'Host(`cloud.test.example.com`)' in compose" + fail_msg: "Expected Host rule for cloud.test.example.com not found in docker-compose.yml" + + - name: Assert GELF logging address in compose + ansible.builtin.assert: + that: "'udp://127.0.0.1:12201' in compose" + fail_msg: "Expected GELF address udp://127.0.0.1:12201 not found in docker-compose.yml" + + - name: Assert sovereign network is external in compose + ansible.builtin.assert: + that: "'external: true' in compose" + fail_msg: "Expected 'external: true' not found in docker-compose.yml" + + - name: Assert nextcloud db password in compose + ansible.builtin.assert: + that: "'test_nextcloud_db' in compose" + fail_msg: "Expected nextcloud_db_password 'test_nextcloud_db' not found in docker-compose.yml" + + - name: Assert nextcloud S3 secret key in compose + ansible.builtin.assert: + that: "'test_nextcloud_s3' in compose" + fail_msg: "Expected minio_nextcloud_secret_key 'test_nextcloud_s3' not found in docker-compose.yml" diff --git a/roles/nextcloud/tasks/main.yml b/roles/nextcloud/tasks/main.yml index 35143e6..098e8e4 100644 --- a/roles/nextcloud/tasks/main.yml +++ b/roles/nextcloud/tasks/main.yml @@ -19,6 +19,7 @@ community.docker.docker_compose_v2: project_src: "{{ nextcloud_data_dir }}" state: present + when: not (molecule_test_mode | default(false)) - name: Wait for Nextcloud to be ready ansible.builtin.command: @@ -28,6 +29,7 @@ retries: 30 delay: 10 changed_when: false + when: not (molecule_test_mode | default(false)) - name: Copy tenant logo into Nextcloud container volume ansible.builtin.copy: @@ -43,9 +45,10 @@ - { key: name, value: "{{ tenant_name }}" } - { key: color, value: "{{ tenant_primary_color | default('#2563eb') }}" } changed_when: false + when: not (molecule_test_mode | default(false)) - name: Configure Nextcloud theming — logo ansible.builtin.command: cmd: docker exec -u www-data nextcloud php occ theming:config logo /var/www/html/sovereign-logo.png - when: tenant_logo_local_path | default('') != '' + when: tenant_logo_local_path | default('') != '' and not (molecule_test_mode | default(false)) changed_when: false diff --git a/roles/roundcube/handlers/main.yml b/roles/roundcube/handlers/main.yml index dc342a8..a014727 100644 --- a/roles/roundcube/handlers/main.yml +++ b/roles/roundcube/handlers/main.yml @@ -4,3 +4,4 @@ project_src: "{{ roundcube_data_dir }}" state: present recreate: always + when: not (molecule_test_mode | default(false)) diff --git a/roles/roundcube/molecule/default/converge.yml b/roles/roundcube/molecule/default/converge.yml new file mode 100644 index 0000000..36eeb36 --- /dev/null +++ b/roles/roundcube/molecule/default/converge.yml @@ -0,0 +1,8 @@ +--- +- name: Converge + hosts: localhost + gather_facts: false + vars_files: + - ../../../molecule/shared/vars.yml + roles: + - role: roundcube diff --git a/roles/roundcube/molecule/default/molecule.yml b/roles/roundcube/molecule/default/molecule.yml new file mode 100644 index 0000000..859036a --- /dev/null +++ b/roles/roundcube/molecule/default/molecule.yml @@ -0,0 +1,23 @@ +--- +dependency: + name: galaxy + options: + requirements-file: requirements.yml +driver: + name: delegated + options: + managed: false + ansible_connection_options: + ansible_connection: local +platforms: + - name: localhost + groups: + - sovereign +provisioner: + name: ansible + inventory: + host_vars: + localhost: + ansible_connection: local +verifier: + name: ansible diff --git a/roles/roundcube/molecule/default/verify.yml b/roles/roundcube/molecule/default/verify.yml new file mode 100644 index 0000000..9b88ab1 --- /dev/null +++ b/roles/roundcube/molecule/default/verify.yml @@ -0,0 +1,95 @@ +--- +- name: Verify roundcube role + hosts: localhost + gather_facts: false + vars: + roundcube_data_dir: /tmp/sovereign_test/roundcube + + tasks: + - name: Check roundcube data directory exists + ansible.builtin.stat: + path: /tmp/sovereign_test/roundcube + register: data_dir_stat + + - name: Assert roundcube data directory is present + ansible.builtin.assert: + that: data_dir_stat.stat.isdir + fail_msg: "Data directory /tmp/sovereign_test/roundcube was not created" + + - name: Check roundcube config directory exists + ansible.builtin.stat: + path: /tmp/sovereign_test/roundcube/config + register: config_dir_stat + + - name: Assert roundcube config directory is present + ansible.builtin.assert: + that: config_dir_stat.stat.isdir + fail_msg: "Directory /tmp/sovereign_test/roundcube/config was not created" + + - name: Check custom.inc.php exists + ansible.builtin.stat: + path: /tmp/sovereign_test/roundcube/config/custom.inc.php + register: custom_php_stat + + - name: Assert custom.inc.php is present + ansible.builtin.assert: + that: custom_php_stat.stat.exists + fail_msg: "custom.inc.php was not rendered for roundcube" + + - name: Read custom.inc.php + ansible.builtin.slurp: + src: /tmp/sovereign_test/roundcube/config/custom.inc.php + register: custom_php_raw + + - name: Set custom.inc.php content fact + ansible.builtin.set_fact: + custom_php: "{{ custom_php_raw.content | b64decode }}" + + - name: Assert tenant branding in custom.inc.php + ansible.builtin.assert: + that: "'Test Corp Webmail' in custom_php" + fail_msg: "Expected 'Test Corp Webmail' not found in custom.inc.php" + + - name: Check docker-compose.yml exists + ansible.builtin.stat: + path: /tmp/sovereign_test/roundcube/docker-compose.yml + register: compose_stat + + - name: Assert docker-compose.yml was rendered + ansible.builtin.assert: + that: compose_stat.stat.exists + fail_msg: "docker-compose.yml was not rendered for roundcube" + + - name: Read docker-compose.yml + ansible.builtin.slurp: + src: /tmp/sovereign_test/roundcube/docker-compose.yml + register: compose_raw + + - name: Set compose content fact + ansible.builtin.set_fact: + compose: "{{ compose_raw.content | b64decode }}" + + - name: Assert roundcube image reference in compose + ansible.builtin.assert: + that: "'roundcube/roundcubemail' in compose" + fail_msg: "Expected image 'roundcube/roundcubemail' not found in docker-compose.yml" + + - name: Assert roundcube host rule in compose + ansible.builtin.assert: + that: "'Host(`webmail.test.example.com`)' in compose" + fail_msg: "Expected Host rule for webmail.test.example.com not found in docker-compose.yml" + + - name: Assert GELF logging address in compose + ansible.builtin.assert: + that: "'udp://127.0.0.1:12201' in compose" + fail_msg: "Expected GELF address udp://127.0.0.1:12201 not found in docker-compose.yml" + + - name: Assert sovereign network is external in compose + ansible.builtin.assert: + that: "'external: true' in compose" + fail_msg: "Expected 'external: true' not found in docker-compose.yml" + + - name: Assert roundcube skin (elastic) in compose + ansible.builtin.assert: + that: "'elastic' in compose" + fail_msg: "Expected roundcube skin 'elastic' not found in docker-compose.yml" diff --git a/roles/roundcube/tasks/main.yml b/roles/roundcube/tasks/main.yml index f8c5086..1d6de35 100644 --- a/roles/roundcube/tasks/main.yml +++ b/roles/roundcube/tasks/main.yml @@ -26,3 +26,4 @@ community.docker.docker_compose_v2: project_src: "{{ roundcube_data_dir }}" state: present + when: not (molecule_test_mode | default(false)) diff --git a/roles/stalwart/handlers/main.yml b/roles/stalwart/handlers/main.yml index 5025722..805deeb 100644 --- a/roles/stalwart/handlers/main.yml +++ b/roles/stalwart/handlers/main.yml @@ -4,3 +4,4 @@ project_src: "{{ stalwart_data_dir }}" state: present recreate: always + when: not (molecule_test_mode | default(false)) diff --git a/roles/stalwart/molecule/default/converge.yml b/roles/stalwart/molecule/default/converge.yml new file mode 100644 index 0000000..c5140fd --- /dev/null +++ b/roles/stalwart/molecule/default/converge.yml @@ -0,0 +1,8 @@ +--- +- name: Converge + hosts: localhost + gather_facts: false + vars_files: + - ../../../molecule/shared/vars.yml + roles: + - role: stalwart diff --git a/roles/stalwart/molecule/default/molecule.yml b/roles/stalwart/molecule/default/molecule.yml new file mode 100644 index 0000000..859036a --- /dev/null +++ b/roles/stalwart/molecule/default/molecule.yml @@ -0,0 +1,23 @@ +--- +dependency: + name: galaxy + options: + requirements-file: requirements.yml +driver: + name: delegated + options: + managed: false + ansible_connection_options: + ansible_connection: local +platforms: + - name: localhost + groups: + - sovereign +provisioner: + name: ansible + inventory: + host_vars: + localhost: + ansible_connection: local +verifier: + name: ansible diff --git a/roles/stalwart/molecule/default/verify.yml b/roles/stalwart/molecule/default/verify.yml new file mode 100644 index 0000000..790fa00 --- /dev/null +++ b/roles/stalwart/molecule/default/verify.yml @@ -0,0 +1,56 @@ +--- +- name: Verify stalwart role + hosts: localhost + gather_facts: false + vars: + stalwart_data_dir: /tmp/sovereign_test/stalwart + + tasks: + - name: Check stalwart data directory exists + ansible.builtin.stat: + path: /tmp/sovereign_test/stalwart + register: data_dir_stat + + - name: Assert stalwart data directory is present + ansible.builtin.assert: + that: data_dir_stat.stat.isdir + fail_msg: "Data directory /tmp/sovereign_test/stalwart was not created" + + - name: Check docker-compose.yml exists + ansible.builtin.stat: + path: /tmp/sovereign_test/stalwart/docker-compose.yml + register: compose_stat + + - name: Assert docker-compose.yml was rendered + ansible.builtin.assert: + that: compose_stat.stat.exists + fail_msg: "docker-compose.yml was not rendered for stalwart" + + - name: Read docker-compose.yml + ansible.builtin.slurp: + src: /tmp/sovereign_test/stalwart/docker-compose.yml + register: compose_raw + + - name: Set compose content fact + ansible.builtin.set_fact: + compose: "{{ compose_raw.content | b64decode }}" + + - name: Assert stalwart image reference in compose + ansible.builtin.assert: + that: "'stalwartlabs/mail-server' in compose" + fail_msg: "Expected image 'stalwartlabs/mail-server' not found in docker-compose.yml" + + - name: Assert stalwart host rule in compose + ansible.builtin.assert: + that: "'mail.test.example.com' in compose" + fail_msg: "Expected hostname 'mail.test.example.com' not found in docker-compose.yml" + + - name: Assert GELF logging address in compose + ansible.builtin.assert: + that: "'udp://127.0.0.1:12201' in compose" + fail_msg: "Expected GELF address udp://127.0.0.1:12201 not found in docker-compose.yml" + + - name: Assert sovereign network is external in compose + ansible.builtin.assert: + that: "'external: true' in compose" + fail_msg: "Expected 'external: true' not found in docker-compose.yml" diff --git a/roles/stalwart/tasks/main.yml b/roles/stalwart/tasks/main.yml index f32d905..98f466c 100644 --- a/roles/stalwart/tasks/main.yml +++ b/roles/stalwart/tasks/main.yml @@ -19,3 +19,4 @@ community.docker.docker_compose_v2: project_src: "{{ stalwart_data_dir }}" state: present + when: not (molecule_test_mode | default(false)) diff --git a/roles/vaultwarden/handlers/main.yml b/roles/vaultwarden/handlers/main.yml index 73651d4..3a007d5 100644 --- a/roles/vaultwarden/handlers/main.yml +++ b/roles/vaultwarden/handlers/main.yml @@ -4,3 +4,4 @@ project_src: "{{ vaultwarden_data_dir }}" state: present recreate: always + when: not (molecule_test_mode | default(false)) diff --git a/roles/vaultwarden/molecule/default/converge.yml b/roles/vaultwarden/molecule/default/converge.yml new file mode 100644 index 0000000..eb0d9f1 --- /dev/null +++ b/roles/vaultwarden/molecule/default/converge.yml @@ -0,0 +1,8 @@ +--- +- name: Converge + hosts: localhost + gather_facts: false + vars_files: + - ../../../molecule/shared/vars.yml + roles: + - role: vaultwarden diff --git a/roles/vaultwarden/molecule/default/molecule.yml b/roles/vaultwarden/molecule/default/molecule.yml new file mode 100644 index 0000000..859036a --- /dev/null +++ b/roles/vaultwarden/molecule/default/molecule.yml @@ -0,0 +1,23 @@ +--- +dependency: + name: galaxy + options: + requirements-file: requirements.yml +driver: + name: delegated + options: + managed: false + ansible_connection_options: + ansible_connection: local +platforms: + - name: localhost + groups: + - sovereign +provisioner: + name: ansible + inventory: + host_vars: + localhost: + ansible_connection: local +verifier: + name: ansible diff --git a/roles/vaultwarden/molecule/default/verify.yml b/roles/vaultwarden/molecule/default/verify.yml new file mode 100644 index 0000000..e914433 --- /dev/null +++ b/roles/vaultwarden/molecule/default/verify.yml @@ -0,0 +1,64 @@ +--- +- name: Verify vaultwarden role + hosts: localhost + gather_facts: false + vars: + vaultwarden_data_dir: /tmp/sovereign_test/vaultwarden + vaultwarden_domain: vault.test.example.com + vaultwarden_admin_token: test_vaultwarden_admin_token + vaultwarden_version: latest + + tasks: + - name: Check vaultwarden data directory exists + ansible.builtin.stat: + path: "/tmp/sovereign_test/vaultwarden" + register: data_dir_stat + + - name: Assert vaultwarden data directory is present + ansible.builtin.assert: + that: data_dir_stat.stat.isdir + fail_msg: "Data directory /tmp/sovereign_test/vaultwarden was not created" + + - name: Check docker-compose.yml exists + ansible.builtin.stat: + path: "/tmp/sovereign_test/vaultwarden/docker-compose.yml" + register: compose_stat + + - name: Assert docker-compose.yml was rendered + ansible.builtin.assert: + that: compose_stat.stat.exists + fail_msg: "docker-compose.yml was not rendered for vaultwarden" + + - name: Read docker-compose.yml + ansible.builtin.slurp: + src: "/tmp/sovereign_test/vaultwarden/docker-compose.yml" + register: compose_raw + + - name: Set compose content fact + ansible.builtin.set_fact: + compose: "{{ compose_raw.content | b64decode }}" + + - name: Assert vaultwarden server image is present + ansible.builtin.assert: + that: "'vaultwarden/server' in compose" + fail_msg: "vaultwarden/server image not found in docker-compose.yml" + + - name: Assert vaultwarden domain traefik rule is present + ansible.builtin.assert: + that: "'Host(`vault.test.example.com`)' in compose" + fail_msg: "Traefik rule for vault.test.example.com not found in docker-compose.yml" + + - name: Assert admin token is present in compose + ansible.builtin.assert: + that: "'test_vaultwarden_admin_token' in compose" + fail_msg: "vaultwarden_admin_token not found in docker-compose.yml" + + - name: Assert GELF logging address is present + ansible.builtin.assert: + that: "'udp://127.0.0.1:12201' in compose" + fail_msg: "GELF logging address udp://127.0.0.1:12201 not found in docker-compose.yml" + + - name: Assert sovereign network is external + ansible.builtin.assert: + that: "'external: true' in compose" + fail_msg: "external: true not found in docker-compose.yml networks section" diff --git a/roles/vaultwarden/tasks/main.yml b/roles/vaultwarden/tasks/main.yml index 25effb6..cbaf94f 100644 --- a/roles/vaultwarden/tasks/main.yml +++ b/roles/vaultwarden/tasks/main.yml @@ -19,3 +19,4 @@ community.docker.docker_compose_v2: project_src: "{{ vaultwarden_data_dir }}" state: present + when: not (molecule_test_mode | default(false)) diff --git a/roles/wazuh/handlers/main.yml b/roles/wazuh/handlers/main.yml index dc1b156..1db7745 100644 --- a/roles/wazuh/handlers/main.yml +++ b/roles/wazuh/handlers/main.yml @@ -4,3 +4,4 @@ project_src: "{{ wazuh_data_dir }}" state: present recreate: always + when: not (molecule_test_mode | default(false)) diff --git a/roles/wazuh/molecule/default/converge.yml b/roles/wazuh/molecule/default/converge.yml new file mode 100644 index 0000000..ec4dcf4 --- /dev/null +++ b/roles/wazuh/molecule/default/converge.yml @@ -0,0 +1,8 @@ +--- +- name: Converge + hosts: localhost + gather_facts: false + vars_files: + - ../../../molecule/shared/vars.yml + roles: + - role: wazuh diff --git a/roles/wazuh/molecule/default/molecule.yml b/roles/wazuh/molecule/default/molecule.yml new file mode 100644 index 0000000..859036a --- /dev/null +++ b/roles/wazuh/molecule/default/molecule.yml @@ -0,0 +1,23 @@ +--- +dependency: + name: galaxy + options: + requirements-file: requirements.yml +driver: + name: delegated + options: + managed: false + ansible_connection_options: + ansible_connection: local +platforms: + - name: localhost + groups: + - sovereign +provisioner: + name: ansible + inventory: + host_vars: + localhost: + ansible_connection: local +verifier: + name: ansible diff --git a/roles/wazuh/molecule/default/verify.yml b/roles/wazuh/molecule/default/verify.yml new file mode 100644 index 0000000..a8fd177 --- /dev/null +++ b/roles/wazuh/molecule/default/verify.yml @@ -0,0 +1,119 @@ +--- +- name: Verify wazuh role + hosts: localhost + gather_facts: false + vars: + wazuh_data_dir: /tmp/sovereign_test/wazuh + wazuh_domain: wazuh.test.example.com + wazuh_admin_password: test_wazuh_admin + wazuh_version: "4.9.0" + tenant_name: Test Corp + + tasks: + - name: Check wazuh data directory exists + ansible.builtin.stat: + path: "/tmp/sovereign_test/wazuh" + register: data_dir_stat + + - name: Assert wazuh data directory is present + ansible.builtin.assert: + that: data_dir_stat.stat.isdir + fail_msg: "Data directory /tmp/sovereign_test/wazuh was not created" + + - name: Check config subdirectory exists + ansible.builtin.stat: + path: "/tmp/sovereign_test/wazuh/config" + register: config_dir_stat + + - name: Assert config subdirectory is present + ansible.builtin.assert: + that: config_dir_stat.stat.isdir + fail_msg: "Config directory /tmp/sovereign_test/wazuh/config was not created" + + - name: Check dashboard-config subdirectory exists + ansible.builtin.stat: + path: "/tmp/sovereign_test/wazuh/dashboard-config" + register: dashboard_config_dir_stat + + - name: Assert dashboard-config subdirectory is present + ansible.builtin.assert: + that: dashboard_config_dir_stat.stat.isdir + fail_msg: "Dashboard-config directory /tmp/sovereign_test/wazuh/dashboard-config was not created" + + - name: Check opensearch_dashboards.yml exists + ansible.builtin.stat: + path: "/tmp/sovereign_test/wazuh/dashboard-config/opensearch_dashboards.yml" + register: dashboards_config_stat + + - name: Assert opensearch_dashboards.yml was rendered + ansible.builtin.assert: + that: dashboards_config_stat.stat.exists + fail_msg: "dashboard-config/opensearch_dashboards.yml was not rendered for wazuh" + + - name: Read opensearch_dashboards.yml + ansible.builtin.slurp: + src: "/tmp/sovereign_test/wazuh/dashboard-config/opensearch_dashboards.yml" + register: dashboards_config_raw + + - name: Set dashboards config content fact + ansible.builtin.set_fact: + dashboards_config: "{{ dashboards_config_raw.content | b64decode }}" + + - name: Assert dashboards config contains tenant branding title + ansible.builtin.assert: + that: "'Test Corp Security' in dashboards_config" + fail_msg: "opensearch_dashboards.yml does not contain 'Test Corp Security'" + + - name: Assert dashboards config contains admin password + ansible.builtin.assert: + that: "'test_wazuh_admin' in dashboards_config" + fail_msg: "opensearch_dashboards.yml does not contain test_wazuh_admin password" + + - name: Check docker-compose.yml exists + ansible.builtin.stat: + path: "/tmp/sovereign_test/wazuh/docker-compose.yml" + register: compose_stat + + - name: Assert docker-compose.yml was rendered + ansible.builtin.assert: + that: compose_stat.stat.exists + fail_msg: "docker-compose.yml was not rendered for wazuh" + + - name: Read docker-compose.yml + ansible.builtin.slurp: + src: "/tmp/sovereign_test/wazuh/docker-compose.yml" + register: compose_raw + + - name: Set compose content fact + ansible.builtin.set_fact: + compose: "{{ compose_raw.content | b64decode }}" + + - name: Assert wazuh-manager image with version is present + ansible.builtin.assert: + that: "'wazuh/wazuh-manager:4.9.0' in compose" + fail_msg: "wazuh/wazuh-manager:4.9.0 image not found in docker-compose.yml" + + - name: Assert wazuh-dashboard image with version is present + ansible.builtin.assert: + that: "'wazuh/wazuh-dashboard:4.9.0' in compose" + fail_msg: "wazuh/wazuh-dashboard:4.9.0 image not found in docker-compose.yml" + + - name: Assert wazuh domain traefik rule is present + ansible.builtin.assert: + that: "'Host(`wazuh.test.example.com`)' in compose" + fail_msg: "Traefik rule for wazuh.test.example.com not found in docker-compose.yml" + + - name: Assert admin password is present in compose + ansible.builtin.assert: + that: "'test_wazuh_admin' in compose" + fail_msg: "wazuh_admin_password not found in docker-compose.yml" + + - name: Assert GELF logging address is present + ansible.builtin.assert: + that: "'udp://127.0.0.1:12201' in compose" + fail_msg: "GELF logging address udp://127.0.0.1:12201 not found in docker-compose.yml" + + - name: Assert sovereign network is external + ansible.builtin.assert: + that: "'external: true' in compose" + fail_msg: "external: true not found in docker-compose.yml networks section" diff --git a/roles/wazuh/tasks/main.yml b/roles/wazuh/tasks/main.yml index ecc02bc..cb8453c 100644 --- a/roles/wazuh/tasks/main.yml +++ b/roles/wazuh/tasks/main.yml @@ -30,6 +30,7 @@ value: '262144' state: present sysctl_set: true + when: not (molecule_test_mode | default(false)) - name: Deploy Wazuh docker-compose ansible.builtin.template: @@ -42,3 +43,4 @@ community.docker.docker_compose_v2: project_src: "{{ wazuh_data_dir }}" state: present + when: not (molecule_test_mode | default(false)) diff --git a/roles/website/handlers/main.yml b/roles/website/handlers/main.yml index 14a2749..14b0cbe 100644 --- a/roles/website/handlers/main.yml +++ b/roles/website/handlers/main.yml @@ -4,3 +4,4 @@ project_src: "{{ website_data_dir }}" state: present recreate: always + when: not (molecule_test_mode | default(false)) diff --git a/roles/website/molecule/default/converge.yml b/roles/website/molecule/default/converge.yml new file mode 100644 index 0000000..9320882 --- /dev/null +++ b/roles/website/molecule/default/converge.yml @@ -0,0 +1,8 @@ +--- +- name: Converge + hosts: localhost + gather_facts: false + vars_files: + - ../../../molecule/shared/vars.yml + roles: + - role: website diff --git a/roles/website/molecule/default/molecule.yml b/roles/website/molecule/default/molecule.yml new file mode 100644 index 0000000..859036a --- /dev/null +++ b/roles/website/molecule/default/molecule.yml @@ -0,0 +1,23 @@ +--- +dependency: + name: galaxy + options: + requirements-file: requirements.yml +driver: + name: delegated + options: + managed: false + ansible_connection_options: + ansible_connection: local +platforms: + - name: localhost + groups: + - sovereign +provisioner: + name: ansible + inventory: + host_vars: + localhost: + ansible_connection: local +verifier: + name: ansible diff --git a/roles/website/molecule/default/verify.yml b/roles/website/molecule/default/verify.yml new file mode 100644 index 0000000..45a6956 --- /dev/null +++ b/roles/website/molecule/default/verify.yml @@ -0,0 +1,68 @@ +--- +- name: Verify website role + hosts: localhost + gather_facts: false + vars: + website_data_dir: /tmp/sovereign_test/website + base_domain: test.example.com + website_nginx_version: alpine + + tasks: + - name: Check website data directory exists + ansible.builtin.stat: + path: "/tmp/sovereign_test/website" + register: data_dir_stat + + - name: Assert website data directory is present + ansible.builtin.assert: + that: data_dir_stat.stat.isdir + fail_msg: "Data directory /tmp/sovereign_test/website was not created" + + - name: Check html subdirectory exists + ansible.builtin.stat: + path: "/tmp/sovereign_test/website/html" + register: html_dir_stat + + - name: Assert html subdirectory is present + ansible.builtin.assert: + that: html_dir_stat.stat.isdir + fail_msg: "HTML directory /tmp/sovereign_test/website/html was not created" + + - name: Check docker-compose.yml exists + ansible.builtin.stat: + path: "/tmp/sovereign_test/website/docker-compose.yml" + register: compose_stat + + - name: Assert docker-compose.yml was rendered + ansible.builtin.assert: + that: compose_stat.stat.exists + fail_msg: "docker-compose.yml was not rendered for website" + + - name: Read docker-compose.yml + ansible.builtin.slurp: + src: "/tmp/sovereign_test/website/docker-compose.yml" + register: compose_raw + + - name: Set compose content fact + ansible.builtin.set_fact: + compose: "{{ compose_raw.content | b64decode }}" + + - name: Assert nginx alpine image is present + ansible.builtin.assert: + that: "'nginx:alpine' in compose" + fail_msg: "nginx:alpine image not found in docker-compose.yml" + + - name: Assert bare domain traefik rule is present + ansible.builtin.assert: + that: "'Host(`test.example.com`)' in compose" + fail_msg: "Traefik rule for bare domain test.example.com not found in docker-compose.yml" + + - name: Assert GELF logging address is present + ansible.builtin.assert: + that: "'udp://127.0.0.1:12201' in compose" + fail_msg: "GELF logging address udp://127.0.0.1:12201 not found in docker-compose.yml" + + - name: Assert sovereign network is external + ansible.builtin.assert: + that: "'external: true' in compose" + fail_msg: "external: true not found in docker-compose.yml networks section" diff --git a/roles/website/tasks/main.yml b/roles/website/tasks/main.yml index 40dacc4..b4bb867 100644 --- a/roles/website/tasks/main.yml +++ b/roles/website/tasks/main.yml @@ -19,3 +19,4 @@ community.docker.docker_compose_v2: project_src: "{{ website_data_dir }}" state: present + when: not (molecule_test_mode | default(false))