Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
zstyblik committed Aug 3, 2024
1 parent 78801a8 commit fd2105d
Show file tree
Hide file tree
Showing 31 changed files with 1,856 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .github/FUNDING.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
github: [zstyblik]
19 changes: 19 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
version: 2
updates:
- package-ecosystem: "pip"
directory: "/"
schedule:
interval: "weekly"
groups:
minor-python-dependencies:
# pip: Only group minor and patch updates
update-types: [minor, patch]
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
groups:
minor-actions-dependencies:
# GitHub Actions: Only group minor and patch updates
update-types: [minor, patch]
40 changes: 40 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
---
name: CI for ansible-role-apache

on: [push] # yamllint disable-line rule:truthy

defaults:
run:
working-directory: 'zstyblik.apache'

jobs:
lint:
runs-on: ubuntu-latest
steps:
- name: Check out the codebase
uses: actions/checkout@v4
with:
path: 'zstyblik.apache'
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: 3.11
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements-ci.txt
- name: Run yamllint
run: |
./ci/run-yamllint.sh
- name: Run ansible-lint
run: |
./ci/run-ansible-lint.sh
- name: Reorder Python imports
run: |
./ci/run-reorder-python-imports.sh
- name: Lint with flake8
run: |
./ci/run-flake8.sh
- name: Lint with black
run: |
./ci/run-black.sh check || ( ./ci/run-black.sh diff; exit 1 )
17 changes: 17 additions & 0 deletions .yamllint
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
extends: default
rules:
braces:
max-spaces-inside: 1
level: error
brackets:
max-spaces-inside: 1
level: error
comments:
min-spaces-from-content: 1
line-length:
max: 120
level: warning
octal-values:
forbid-implicit-octal: true
forbid-explicit-octal: true
100 changes: 100 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# Ansible role apache

**READ ME FIRST:** Probably for the last 50 people or so which are still using
Ansible I wholeheartedly recommend to use [geerlingguy.apache] role. Why?
Because it's tested, maintained and covers a lot of, if not the most of, use
cases.

---

This is yet another ansible role for management of Apache's [httpd]. Inspiration
for this role was and is [puppetlabs-apache] module. Another one is
aforementioned Geerlingguy's ansible role [geerlingguy.apache].

Since nobody is going to use this anyway(Ansible isn't used anymore and neither
is httpd) I'm afraid that's it. Only supported OS at this moment is Debian.
I might add support for some other OS, if and when I'm bored.

#### Features

* richer httpd and virtual host configuration(perhaps it's too much)
* generate `ports.conf` from virtual hosts
* allow to enable/disable config files
* allow to enable/disable modules

## Requirements

None.

## Role variables

See `defaults/main.yml`. There is also `specs/apache_vhost_argument_specs.yml`
which should give you some idea about configuration options and possibilities
regarding virtual hosts.

## Dependencies

There are no extra dependencies as far as Ansible goes.

## Example Playbook

```
- hosts: all
vars:
apache_vhosts:
- servername: "local1.dev"
port: 80
docroot: "/var/www/html"
rewrites:
- rewrite_rule: ["^/.*$ https://localhost1 [R=302,L]"]
- servername: "localhost1"
serveradmin: "root@example.com"
port: 443
docroot: "/var/www/html"
directories:
- path: "/var/www/html"
allowoverride: ["None"]
directoryindex: "index.php"
require: "all granted"
addhandlers:
- handler: "application/x-httpd-php"
extensions:
- "\.php"
- provider: "location"
path: "/README"
require: "all denied"
ssl:
- ssl_cert: "/etc/ssl/certs/ssl-cert-snakeoil.pem"
ssl_key: "/etc/ssl/private/ssl-cert-snakeoil.key"
ssl_cacerts_dir: "/etc/ssl/certs"
- servername: "local2.dev"
state: absent
listen_ip: "127.0.0.1"
port: 8080
docroot: "/var/www/html"
error_log_format24:
- format: "[%{uc}t] [%-m:%-l] [R:%L] [C:%{C}L] %7F: %E: %M"
- flag: request
format: "[%{uc}t] [R:%L] Request %k on C:%{c}L pid:%P tid:%T"
apache_mods:
- name: rewrite
state: present
apache_confs:
- name: serve-cgi-bin
state: absent
roles:
- role: zstyblik.apache
```

## License

MIT

[geerlingguy.apache]: https://github.com/geerlingguy/ansible-role-apache/tree/master
[httpd]: https://httpd.apache.org
[puppetlabs-apache]: https://forge.puppet.com/modules/puppetlabs/apache/readme
143 changes: 143 additions & 0 deletions action_plugins/apache_ports_generator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
#!/usr/bin/env python3
"""Action plugin for ansible role apache - apache port config generator.
MIT License
Copyright (c) 2024 Zdenek Styblik
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
from ansible.errors import AnsibleError
from ansible.plugins.action import ActionBase
from ansible.utils.display import Display

display = Display()


class ActionModule(ActionBase):
"""Generator of data for ports.conf configuration."""

TRANSFERS_FILES = False
_requires_connection = False

def _format_binding(self, listen_ip, port, proto):
"""Return pre-formatted string for Listen directive."""
lip_fmt = "{:s}:".format(listen_ip) if listen_ip else ""
proto_fmt = " {:s}".format(proto) if proto else ""
return "{:s}{:d}{:s}".format(lip_fmt, port, proto_fmt)

def run(self, tmp=None, task_vars=None):
"""Transform virtual host definitions into data for ports.conf."""
result = super().run(tmp, task_vars)
del tmp

vhosts = self._task.args.get("vhosts", [])
if not vhosts:
vhosts = [{"listen_ip": "", "port": 80, "ssl": False}]

# Ref. https://httpd.apache.org/docs/2.4/bind.html
# Structure:
# port -> ip addr -> ssl yes/no
#
# Example:
# 80 -> 1.2.3.4 -> False
# 80 -> '*' -> ...
# => EXCEPTION due to collision
#
# 80 -> '*' -> False
# 80 -> '*' -> True
# => EXCEPTION due to collision
#
# then transform it for jinja2
bindings = {}
for vhost in vhosts:
try:
port = int(vhost["port"])
except KeyError as exception:
msg = "vhost '{}' is missing port attribute".format(
vhost.get("servername", "unknown")
)
raise AnsibleError(msg) from exception
except ValueError as exception:
msg = "failed to convert port '{}' of vhost '{}' to int".format(
vhost.get("servername", "unknown"),
vhost.get("port", None),
)
raise AnsibleError(msg) from exception

if port not in bindings:
bindings[port] = {}

ssl = vhost.get("ssl", False)
# According to documentation, https is default proto for port 443.
# Therefore there is no need to specify it.
if ssl is True and port != 443:
ssl = "https"
else:
ssl = ""

listen_ip = vhost.get("listen_ip", "")
if bindings[port]:
# We need to check for possible numerous and various conflicts.
if (
listen_ip in bindings[port]
and bindings[port][listen_ip] != ssl
):
# Reasoning: 'IP:Port' is the same and protocol is
# different -> error
msg = (
"HTTP/HTTPS collision for IP '{}' "
"and port '{}' in vhost '{}'".format(
listen_ip,
port,
vhost.get("servername", "unknown"),
)
)
raise AnsibleError(msg)

if (
listen_ip == "" and listen_ip not in bindings[port].keys()
) or (listen_ip != "" and "" in bindings[port].keys()):
# Reasoning: if listening on *:80, then we cannot listen
# on 1.2.3.4:80 as well and vice versa.
msg = (
"bind collision any Vs. IP for IP '{}' "
"and port '{}' in vhost '{}'".format(
listen_ip,
port,
vhost.get("servername", "unknown"),
)
)
raise AnsibleError(msg)

bindings[port][listen_ip] = ssl

result["data"] = {
"{}:{}:{}".format(listen_ip, port, binding[listen_ip]): {
"listen_ip": listen_ip,
"port": port,
"proto": binding[listen_ip],
"formatted": self._format_binding(
listen_ip, port, binding[listen_ip]
),
}
for port, binding in bindings.items()
for listen_ip in binding
}
return result
5 changes: 5 additions & 0 deletions ci/run-ansible-lint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/usr/bin/env bash
set -e
set -u

ansible-lint .
21 changes: 21 additions & 0 deletions ci/run-black.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#!/usr/bin/env bash
set -e
set -u


MODE=${1:?Mode must be given.}

if [ "${MODE}" = "check" ]; then
black_arg=" --check"
elif [ "${MODE}" = "diff" ]; then
black_arg=" --diff"
elif [ "${MODE}" = "format" ]; then
black_arg=""
else
printf "Mode '%s' is not supported.\n" "${MODE}" 1>&2
exit 1
fi

# shellcheck disable=SC2086
find . ! -path '*/\.*' -name '*.py' -print0 | \
xargs -0 -- python3 -m black ${black_arg} -l 80
13 changes: 13 additions & 0 deletions ci/run-flake8.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/usr/bin/env bash
set -e
set -u

python3 -m flake8 \
. \
--ignore=W503 \
--application-import-names="app,settings" \
--import-order-style=pycharm \
--max-line-length=80 \
--show-source \
--count \
--statistics
6 changes: 6 additions & 0 deletions ci/run-reorder-python-imports.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/usr/bin/env bash
set -e
set -u

find . ! -path '*/\.*' -name '*.py' -print0 | \
xargs -0 -- reorder-python-imports
5 changes: 5 additions & 0 deletions ci/run-yamllint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/usr/bin/env bash
set -e
set -u

yamllint .
Loading

0 comments on commit fd2105d

Please sign in to comment.