diff --git a/.github/workflows/run-playbook.yml b/.github/workflows/run-playbook.yml index 9dddfde..444015e 100644 --- a/.github/workflows/run-playbook.yml +++ b/.github/workflows/run-playbook.yml @@ -1,8 +1,9 @@ -name: HMS-Docker Install Tests -run-name: Test OS Installs +name: Deployment Tests +run-name: Test Deployment on: push: - workflow_dispatch: + schedule: + - cron: '23 9 * * 0' permissions: contents: read @@ -13,16 +14,19 @@ jobs: steps: - name: Check out repo code uses: actions/checkout@v4 + - name: Install ansible run: | sudo apt update sudo apt install ansible make python3-pip + - name: Ensure base playbook requirements run: | mkdir -p ./vars/custom cp vars/default/*.yml ./vars/custom mv ./vars/custom/main.yml ./vars/custom/main_custom.yml sudo make install-reqs + - name: Run playbook run: >- sudo ansible-playbook @@ -32,5 +36,14 @@ jobs: --diff --extra-vars " is_github_runner=yes - tautulli_jbops_enabled=yes + container_expose_ports=yes + hms_docker_plex_ssl_enabled=yes + separate_4k_instances_enable=yes + tautulli_include_jbops=yes + traefik_security_hardening=yes " + + - name: Check containers + run: | + sleep 10 + sudo make verify-containers diff --git a/.github/workflows/scripts/check_containers.py b/.github/workflows/scripts/check_containers.py new file mode 100644 index 0000000..ed3ed0c --- /dev/null +++ b/.github/workflows/scripts/check_containers.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python3 + +import sys +import docker +import os +import logging +import requests +from dotenv import load_dotenv + +# Intended as the containers may host a self-signed certificate +import urllib3 +urllib3.disable_warnings() + +LOG_PATH = f"{os.path.dirname(os.path.abspath(__file__))}/backups.log" +logger = logging.basicConfig( + level=logging.INFO, + format="%(asctime)s [%(name)s] [%(levelname)s] %(message)s", + handlers=[ + logging.FileHandler(LOG_PATH, mode='a'), + logging.StreamHandler(), + ]) + +logging.info(f'Initializing Docker client') +docker_client = docker.from_env() + +def main(): + container_list = docker_client.containers.list() + + project = 'hms-docker' + + requests_made = 0 + success_codes = 0 + failure_codes = 0 + + for cont in container_list: + try: + name = cont.name + logging.debug(f'Checking {name}') + if cont.labels['com.docker.compose.project'] == project: + workdir = cont.labels['com.docker.compose.project.working_dir'] + load_dotenv(f'{workdir}/.env') + domain = os.getenv('HMSD_DOMAIN') + for port_exposures in cont.attrs['NetworkSettings']['Ports']: + port_map = cont.attrs['NetworkSettings']['Ports'][port_exposures] + if port_map is None or 'udp' in port_exposures: + continue + logging.debug(f'{name} is in project {project} and has tcp port exposure: {port_exposures}') + for mapping in port_map: + host_ip = mapping['HostIp'] + host_port = mapping['HostPort'] + if host_ip == '::': + continue + if host_ip == '0.0.0.0': + host_ip = '127.0.0.1' + + try: + suffix = '' + ssl = False + if name == 'plex': + suffix = '/web' + ssl = True + if host_port != '32400': + logging.debug(f'Skipping {name} on {host_port} because it doesnt host a webpage') + continue + if name == 'transmission' and host_port == '8888': + logging.debug(f'Skipping {name} on {host_port} because its a proxy') + continue + if name == 'authentik-server': + name = 'authentik' + ssl = True + url = f'http{"s" if ssl else ""}://{host_ip}:{host_port}{suffix}' + host_header = f'{name}.home.{domain}' + logging.debug(f'getting {url} with Host header {host_header}') + # file deepcode ignore SSLVerificationBypass: Containers may host a self-signed certificate + response = requests.get(url, verify=False, headers={ + 'Host': host_header + }) + requests_made += 1 + status_code = response.status_code + if status_code == 200: + success_codes += 1 + logging.info(f'{name}:{host_port} OK') + elif name == 'transmission-proxy' and status_code == '502': + success_codes += 1 + else: + failure_codes += 1 + logging.warning(f'{name}:{host_port} FAILED (Code: {status_code})') + except requests.exceptions.ConnectionError as e: + logging.error(f'Failed on port {host_port} with header {host_header}: {e}') + + except KeyError as e: + logging.debug(f'{name} is not in the project {project}') + continue + fail_rate = failure_codes/requests_made + threshold = 0.34 + if fail_rate > threshold: + logging.error(f'Failure rate exceeded {threshold}, it was {fail_rate}') + sys.exit(1) + else: + logging.info(f'Failure rate: {fail_rate}') + sys.exit(0) + +if __name__ == '__main__': + main() diff --git a/Makefile b/Makefile index 3d1a91f..f8203d9 100644 --- a/Makefile +++ b/Makefile @@ -49,9 +49,13 @@ apply: install-reqs install-reqs: @ansible-galaxy install -r galaxy-requirements.yml -p ./galaxy-roles +verify-containers: + @sudo python3 .github/workflows/scripts/check_containers.py + help: - @echo make basic :: for a basic config - @echo make advanced :: for an advanced config + @echo make basic :: setup a basic config + @echo make advanced :: setup an advanced config @echo make check :: check for any changes without doing anything \(diff\) @echo make apply :: apply any changes identified in the diff @echo make install-reqs :: installs ansible galaxy role requirements + @echo make verify-containers :: checks containers exposed ports \(used in Actions\) diff --git a/README.md b/README.md index 9b8a170..40067fa 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # ansible-hms-docker -[![Ubuntu 22.04 LTS Install Test](https://github.com/ahembree/ansible-hms-docker/actions/workflows/run-playbook.yml/badge.svg)](https://github.com/ahembree/ansible-hms-docker/actions/workflows/run-playbook.yml) +[![Deployment Tests](https://github.com/ahembree/ansible-hms-docker/actions/workflows/run-playbook.yml/badge.svg)](https://github.com/ahembree/ansible-hms-docker/actions/workflows/run-playbook.yml) Ansible Playbook to setup an automated Home Media Server stack running on Docker across a variety of platforms with support for GPUs, SSL, SSO, DDNS, and more.