Skip to content

Commit e50855c

Browse files
committed
Initial commit
1 parent 78801a8 commit e50855c

31 files changed

+1842
-0
lines changed

.github/FUNDING.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
---
2+
github: [zstyblik]

.github/dependabot.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
version: 2
3+
updates:
4+
- package-ecosystem: "pip"
5+
directory: "/"
6+
schedule:
7+
interval: "weekly"
8+
groups:
9+
minor-python-dependencies:
10+
# pip: Only group minor and patch updates
11+
update-types: [minor, patch]
12+
- package-ecosystem: "github-actions"
13+
directory: "/"
14+
schedule:
15+
interval: "weekly"
16+
groups:
17+
minor-actions-dependencies:
18+
# GitHub Actions: Only group minor and patch updates
19+
update-types: [minor, patch]

.github/workflows/ci.yml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
---
2+
name: CI for ansible-role-apache
3+
4+
on: [push] # yamllint disable-line rule:truthy
5+
6+
defaults:
7+
run:
8+
working-directory: 'zstyblik.apache'
9+
10+
jobs:
11+
lint:
12+
runs-on: ubuntu-latest
13+
steps:
14+
- name: Check out the codebase
15+
uses: actions/checkout@v4
16+
with:
17+
path: 'zstyblik.apache'
18+
- name: Set up Python
19+
uses: actions/setup-python@v5
20+
with:
21+
python-version: 3.11
22+
- name: Install dependencies
23+
run: |
24+
python -m pip install --upgrade pip
25+
pip install -r requirements-ci.txt
26+
- name: Run yamllint
27+
run: |
28+
./ci/run-yamllint.sh
29+
- name: Run ansible-lint
30+
run: |
31+
./ci/run-ansible-lint.sh
32+
- name: Reorder Python imports
33+
run: |
34+
./ci/run-reorder-python-imports.sh
35+
- name: Lint with flake8
36+
run: |
37+
./ci/run-flake8.sh
38+
- name: Lint with black
39+
run: |
40+
./ci/run-black.sh check || ( ./ci/run-black.sh diff; exit 1 )

.yamllint

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
extends: default
3+
rules:
4+
braces:
5+
max-spaces-inside: 1
6+
level: error
7+
brackets:
8+
max-spaces-inside: 1
9+
level: error
10+
comments:
11+
min-spaces-from-content: 1
12+
line-length:
13+
max: 120
14+
level: warning
15+
octal-values:
16+
forbid-implicit-octal: true
17+
forbid-explicit-octal: true

README.md

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
# Ansible role apache
2+
3+
**READ ME FIRST:** I wholeheartedly recommend to use [geerlingguy.apache]
4+
role. Why? Because it's tested, maintained and covers a lot of, if not the most
5+
of, use cases.
6+
7+
Probably for the last 50 people or so which are still using Ansible ...
8+
9+
This is yet another ansible role for management of Apache's [httpd]. Inspiration
10+
for this role was and is [puppetlabs-apache] module. Another one is
11+
aforementioned Geerlingguy's ansible role [geerlingguy.apache].
12+
13+
Since nobody is going to use this anyway(httpd isn't used anymore, is it?) I'm
14+
afraid that's it. Only supported OS at this moment is Debian. I might add
15+
support for some other OS, if and when I'm bored.
16+
17+
## Requirements
18+
19+
None.
20+
21+
## Role variables
22+
23+
See `defaults/main.yml`. There is also `specs/apache_vhost_argument_specs.yml`
24+
which should give you some idea about configuration options and possibilities
25+
regarding virtual hosts.
26+
27+
## Dependencies
28+
29+
There are no extra dependencies as far as Ansible goes.
30+
31+
## Example Playbook
32+
33+
```
34+
- hosts: all
35+
vars:
36+
apache_vhosts:
37+
- servername: "local1.dev"
38+
port: 80
39+
docroot: "/var/www/html"
40+
rewrites:
41+
- rewrite_rule: ["^/.*$ https://localhost1 [R=302,L]"]
42+
43+
- servername: "localhost1"
44+
serveradmin: "[email protected]"
45+
port: 443
46+
docroot: "/var/www/html"
47+
directories:
48+
- path: "/var/www/html"
49+
allowoverride: ["None"]
50+
directoryindex: "index.php"
51+
require: "all granted"
52+
addhandlers:
53+
- handler: "application/x-httpd-php"
54+
extensions:
55+
- "\.php"
56+
57+
- provider: "location"
58+
path: "/README"
59+
require: "all denied"
60+
ssl:
61+
- ssl_cert: "/etc/ssl/certs/ssl-cert-snakeoil.pem"
62+
ssl_key: "/etc/ssl/private/ssl-cert-snakeoil.key"
63+
ssl_cacerts_dir: "/etc/ssl/certs"
64+
65+
- servername: "local2.dev"
66+
state: absent
67+
listen_ip: "127.0.0.1"
68+
port: 8080
69+
docroot: "/var/www/html"
70+
71+
apache_mods:
72+
- name: rewrite
73+
state: present
74+
75+
apache_confs:
76+
- name: serve-cgi-bin
77+
state: absent
78+
roles:
79+
- role: zstyblik.apache
80+
```
81+
82+
## License
83+
84+
MIT
85+
86+
[geerlingguy.apache]: https://github.com/geerlingguy/ansible-role-apache/tree/master
87+
[httpd]: https://httpd.apache.org
88+
[puppetlabs-apache]: https://forge.puppet.com/modules/puppetlabs/apache/readme
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
#!/usr/bin/env python3
2+
"""Action plugin for ansible role apache - apache port config generator.
3+
4+
MIT License
5+
6+
Copyright (c) 2024 Zdenek Styblik
7+
8+
Permission is hereby granted, free of charge, to any person obtaining a copy
9+
of this software and associated documentation files (the "Software"), to deal
10+
in the Software without restriction, including without limitation the rights
11+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+
copies of the Software, and to permit persons to whom the Software is
13+
furnished to do so, subject to the following conditions:
14+
15+
The above copyright notice and this permission notice shall be included in all
16+
copies or substantial portions of the Software.
17+
18+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24+
SOFTWARE.
25+
"""
26+
from ansible.errors import AnsibleError
27+
from ansible.plugins.action import ActionBase
28+
from ansible.utils.display import Display
29+
30+
display = Display()
31+
32+
33+
class ActionModule(ActionBase):
34+
"""Generator of data for ports.conf configuration."""
35+
36+
TRANSFERS_FILES = False
37+
_requires_connection = False
38+
39+
def _format_binding(self, listen_ip, port, proto):
40+
"""Return pre-formatted string for Listen directive."""
41+
lip_fmt = "{:s}:".format(listen_ip) if listen_ip else ""
42+
proto_fmt = " {:s}".format(proto) if proto else ""
43+
return "{:s}{:d}{:s}".format(lip_fmt, port, proto_fmt)
44+
45+
def run(self, tmp=None, task_vars=None):
46+
"""Transform virtual host definitions into data for ports.conf."""
47+
result = super().run(tmp, task_vars)
48+
del tmp
49+
50+
vhosts = self._task.args.get("vhosts", [])
51+
if not vhosts:
52+
vhosts = [{"listen_ip": "", "port": 80, "ssl": False}]
53+
54+
# Ref. https://httpd.apache.org/docs/2.4/bind.html
55+
# Structure:
56+
# port -> ip addr -> ssl yes/no
57+
#
58+
# Example:
59+
# 80 -> 1.2.3.4 -> False
60+
# 80 -> '*' -> ...
61+
# => EXCEPTION due to collision
62+
#
63+
# 80 -> '*' -> False
64+
# 80 -> '*' -> True
65+
# => EXCEPTION due to collision
66+
#
67+
# then transform it for jinja2
68+
bindings = {}
69+
for vhost in vhosts:
70+
try:
71+
port = int(vhost["port"])
72+
except KeyError as exception:
73+
msg = "vhost '{}' is missing port attribute".format(
74+
vhost.get("servername", "unknown")
75+
)
76+
raise AnsibleError(msg) from exception
77+
except ValueError as exception:
78+
msg = "failed to convert port '{}' of vhost '{}' to int".format(
79+
vhost.get("servername", "unknown"),
80+
vhost.get("port", None),
81+
)
82+
raise AnsibleError(msg) from exception
83+
84+
if port not in bindings:
85+
bindings[port] = {}
86+
87+
ssl = vhost.get("ssl", False)
88+
# According to documentation, https is default proto for port 443.
89+
# Therefore there is no need to specify it.
90+
if ssl is True and port != 443:
91+
ssl = "https"
92+
else:
93+
ssl = ""
94+
95+
listen_ip = vhost.get("listen_ip", "")
96+
if bindings[port]:
97+
# We need to check for possible numerous and various conflicts.
98+
if (
99+
listen_ip in bindings[port]
100+
and bindings[port][listen_ip] != ssl
101+
):
102+
# Reasoning: 'IP:Port' is the same and protocol is
103+
# different -> error
104+
msg = (
105+
"HTTP/HTTPS collision for IP '{}' "
106+
"and port '{}' in vhost '{}'".format(
107+
listen_ip,
108+
port,
109+
vhost.get("servername", "unknown"),
110+
)
111+
)
112+
raise AnsibleError(msg)
113+
114+
if (
115+
listen_ip == "" and listen_ip not in bindings[port].keys()
116+
) or (listen_ip != "" and "" in bindings[port].keys()):
117+
# Reasoning: if listening on *:80, then we cannot listen
118+
# on 1.2.3.4:80 as well and vice versa.
119+
msg = (
120+
"bind collision any Vs. IP for IP '{}' "
121+
"and port '{}' in vhost '{}'".format(
122+
listen_ip,
123+
port,
124+
vhost.get("servername", "unknown"),
125+
)
126+
)
127+
raise AnsibleError(msg)
128+
129+
bindings[port][listen_ip] = ssl
130+
131+
result["data"] = {
132+
"{}:{}:{}".format(listen_ip, port, binding[listen_ip]): {
133+
"listen_ip": listen_ip,
134+
"port": port,
135+
"proto": binding[listen_ip],
136+
"formatted": self._format_binding(
137+
listen_ip, port, binding[listen_ip]
138+
),
139+
}
140+
for port, binding in bindings.items()
141+
for listen_ip in binding
142+
}
143+
return result

ci/run-ansible-lint.sh

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#!/usr/bin/env bash
2+
set -e
3+
set -u
4+
5+
ansible-lint .

ci/run-black.sh

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#!/usr/bin/env bash
2+
set -e
3+
set -u
4+
5+
6+
MODE=${1:?Mode must be given.}
7+
8+
if [ "${MODE}" = "check" ]; then
9+
black_arg=" --check"
10+
elif [ "${MODE}" = "diff" ]; then
11+
black_arg=" --diff"
12+
elif [ "${MODE}" = "format" ]; then
13+
black_arg=""
14+
else
15+
printf "Mode '%s' is not supported.\n" "${MODE}" 1>&2
16+
exit 1
17+
fi
18+
19+
# shellcheck disable=SC2086
20+
find . ! -path '*/\.*' -name '*.py' -print0 | \
21+
xargs -0 -- python3 -m black ${black_arg} -l 80

ci/run-flake8.sh

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#!/usr/bin/env bash
2+
set -e
3+
set -u
4+
5+
python3 -m flake8 \
6+
. \
7+
--ignore=W503 \
8+
--application-import-names="app,settings" \
9+
--import-order-style=pycharm \
10+
--max-line-length=80 \
11+
--show-source \
12+
--count \
13+
--statistics

ci/run-reorder-python-imports.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#!/usr/bin/env bash
2+
set -e
3+
set -u
4+
5+
find . ! -path '*/\.*' -name '*.py' -print0 | \
6+
xargs -0 -- reorder-python-imports

ci/run-yamllint.sh

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#!/usr/bin/env bash
2+
set -e
3+
set -u
4+
5+
yamllint .

0 commit comments

Comments
 (0)