From f9a5afccfe79b3524cb6be8bb5736196d9bb79bc Mon Sep 17 00:00:00 2001 From: Francisco-Xiq Date: Sun, 17 May 2026 18:31:34 -0300 Subject: [PATCH] feat: new module apache2_site - enable/disable Apache2 Sites on Debian Systems --- changelogs/fragments/9209-apache2-site.yml | 2 + plugins/modules/apache2_site.py | 112 ++++++++++++++++++ .../integration/targets/apache2_site/aliases | 6 + .../targets/apache2_site/meta/main.yml | 7 ++ .../targets/apache2_site/tasks/actualtest.yml | 103 ++++++++++++++++ .../targets/apache2_site/tasks/main.yml | 40 +++++++ 6 files changed, 270 insertions(+) create mode 100644 changelogs/fragments/9209-apache2-site.yml create mode 100644 plugins/modules/apache2_site.py create mode 100644 tests/integration/targets/apache2_site/aliases create mode 100644 tests/integration/targets/apache2_site/meta/main.yml create mode 100644 tests/integration/targets/apache2_site/tasks/actualtest.yml create mode 100644 tests/integration/targets/apache2_site/tasks/main.yml diff --git a/changelogs/fragments/9209-apache2-site.yml b/changelogs/fragments/9209-apache2-site.yml new file mode 100644 index 0000000000..5efcdb2fde --- /dev/null +++ b/changelogs/fragments/9209-apache2-site.yml @@ -0,0 +1,2 @@ +minor_changes: + - apache2_site - new module to enable/disable Apache2 sites using ``a2ensite`` and ``a2dissite`` (https://github.com/ansible-collections/community.general/issues/9209). diff --git a/plugins/modules/apache2_site.py b/plugins/modules/apache2_site.py new file mode 100644 index 0000000000..d141ad16ba --- /dev/null +++ b/plugins/modules/apache2_site.py @@ -0,0 +1,112 @@ +#!/usr/bin/python + +# Copyright (c) 2025, Francisco Pereira (@Francisco-Xiq) +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +from __future__ import annotations + +DOCUMENTATION = r""" +module: apache2_site +author: + - Francisco Pereira (@Francisco-Xiq) +short_description: Enables/disables a site of the Apache2 webserver +description: + - Enables or disables a specified site of the Apache2 webserver. + - Uses C(a2ensite) and C(a2dissite) under the hood. +notes: + - This module is only available on Debian/Ubuntu-based systems, + as it depends on C(a2ensite) and C(a2dissite). +extends_documentation_fragment: + - community.general._attributes +attributes: + check_mode: + support: full + diff_mode: + support: none +options: + name: + type: str + description: + - Name of the site to enable/disable as given to C(a2ensite)/C(a2dissite). + - The name should not include the C(.conf) extension. + required: true + state: + type: str + description: + - Desired state of the site. + choices: [present, absent] + required: true +""" + +EXAMPLES = r""" +- name: Enable my_cool_site + community.general.apache2_site: + state: present + name: my_cool_site + +- name: Disable old site + community.general.apache2_site: + state: absent + name: very_old_site +""" + +RETURN = r""" +name: + description: Name of the site. + returned: success + type: str + sample: my_cool_site +""" + +import os +from ansible.module_utils.basic import AnsibleModule + + +def site_is_enabled(name): + return os.path.islink(f"/etc/apache2/sites-enabled/{name}.conf") + + +def main(): + module = AnsibleModule( + argument_spec=dict( + name=dict(type="str", required=True), + state=dict(type="str", required=True, choices=["present", "absent"]), + ), + supports_check_mode=True, + ) + + name = module.params["name"] + state = module.params["state"] + want_enabled = state == "present" + is_enabled = site_is_enabled(name) + + changed = False + + if want_enabled and not is_enabled: + changed = True + if not module.check_mode: + a2ensite = module.get_bin_path("a2ensite", required=True) + rc, stdout, stderr = module.run_command([a2ensite, name]) + if rc != 0: + module.fail_json( + msg=f"Failed to enable site {name}: {stderr}", + rc=rc, stdout=stdout, stderr=stderr + ) + + elif not want_enabled and is_enabled: + changed = True + if not module.check_mode: + a2dissite = module.get_bin_path("a2dissite", required=True) + rc, stdout, stderr = module.run_command([a2dissite, name]) + if rc != 0: + module.fail_json( + msg=f"Failed to disable site {name}: {stderr}", + rc=rc, stdout=stdout, stderr=stderr + ) + + module.exit_json(changed=changed, name=name) + + +if __name__ == "__main__": + main() diff --git a/tests/integration/targets/apache2_site/aliases b/tests/integration/targets/apache2_site/aliases new file mode 100644 index 0000000000..1eac407375 --- /dev/null +++ b/tests/integration/targets/apache2_site/aliases @@ -0,0 +1,6 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +azp/posix/3 +destructive diff --git a/tests/integration/targets/apache2_site/meta/main.yml b/tests/integration/targets/apache2_site/meta/main.yml new file mode 100644 index 0000000000..f32339acf0 --- /dev/null +++ b/tests/integration/targets/apache2_site/meta/main.yml @@ -0,0 +1,7 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +dependencies: + - setup_apache2 diff --git a/tests/integration/targets/apache2_site/tasks/actualtest.yml b/tests/integration/targets/apache2_site/tasks/actualtest.yml new file mode 100644 index 0000000000..4e0adfdd21 --- /dev/null +++ b/tests/integration/targets/apache2_site/tasks/actualtest.yml @@ -0,0 +1,103 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +# Setup: create a minimal site config for testing +- name: create test site config + ansible.builtin.copy: + dest: /etc/apache2/sites-available/ansible_test_site.conf + content: | + + ServerName ansible-test.local + DocumentRoot /var/www/html + + +# ------------------------------------------------------------------ +# Test: enable a site +# ------------------------------------------------------------------ +- name: enable test site + community.general.apache2_site: + name: ansible_test_site + state: present + register: enable_first + +- name: ensure changed on first enable + ansible.builtin.assert: + that: + - enable_first is changed + +# ------------------------------------------------------------------ +# Test: idempotency on enable +# ------------------------------------------------------------------ +- name: enable test site again (idempotency check) + community.general.apache2_site: + name: ansible_test_site + state: present + register: enable_second + +- name: ensure idempotency on enable + ansible.builtin.assert: + that: + - enable_second is not changed + +# ------------------------------------------------------------------ +# Test: disable a site +# ------------------------------------------------------------------ +- name: disable test site + community.general.apache2_site: + name: ansible_test_site + state: absent + register: disable_first + +- name: ensure changed on first disable + ansible.builtin.assert: + that: + - disable_first is changed + +# ------------------------------------------------------------------ +# Test: idempotency on disable +# ------------------------------------------------------------------ +- name: disable test site again (idempotency check) + community.general.apache2_site: + name: ansible_test_site + state: absent + register: disable_second + +- name: ensure idempotency on disable + ansible.builtin.assert: + that: + - disable_second is not changed + +# ------------------------------------------------------------------ +# Test: check_mode does not make changes +# ------------------------------------------------------------------ +- name: enable test site in check_mode + community.general.apache2_site: + name: ansible_test_site + state: present + check_mode: true + register: check_mode_result + +- name: ensure check_mode reports changed + ansible.builtin.assert: + that: + - check_mode_result is changed + +- name: ensure check_mode did not actually enable the site + ansible.builtin.stat: + path: /etc/apache2/sites-enabled/ansible_test_site.conf + register: site_stat + +- name: assert symlink was not created in check_mode + ansible.builtin.assert: + that: + - not site_stat.stat.exists + +# ------------------------------------------------------------------ +# Cleanup: remove test site config +# ------------------------------------------------------------------ +- name: remove test site config + ansible.builtin.file: + path: /etc/apache2/sites-available/ansible_test_site.conf + state: absent diff --git a/tests/integration/targets/apache2_site/tasks/main.yml b/tests/integration/targets/apache2_site/tasks/main.yml new file mode 100644 index 0000000000..a51e058259 --- /dev/null +++ b/tests/integration/targets/apache2_site/tasks/main.yml @@ -0,0 +1,40 @@ +--- +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: test apache2_site + block: + - name: get list of enabled sites before tests + ansible.builtin.command: ls /etc/apache2/sites-enabled/ + register: sites_before + changed_when: false + + - name: include actual tests + include_tasks: actualtest.yml + + always: + - name: get list of enabled sites after tests + ansible.builtin.command: ls /etc/apache2/sites-enabled/ + register: sites_after + changed_when: false + + - name: sites before tests + ansible.builtin.debug: + var: sites_before.stdout_lines + + - name: sites after tests + ansible.builtin.debug: + var: sites_after.stdout_lines + + - name: ensure that all test sites are disabled again + ansible.builtin.assert: + that: sites_before.stdout == sites_after.stdout + + when: ansible_facts.os_family == 'Debian' + # a2ensite/a2dissite are Debian/Ubuntu specific tools