diff --git a/.drone.yml b/.drone.yml index 08029de..78fd3c6 100644 --- a/.drone.yml +++ b/.drone.yml @@ -1,30 +1,50 @@ -# --- -# kind: pipeline -# name: deploy to swarm-test.autonomic.zone -# steps: -# - name: deployment -# image: git.coopcloud.tech/coop-cloud/stack-ssh-deploy:latest -# settings: -# host: swarm-test.autonomic.zone -# stack: wordpress -# generate_secrets: true -# purge: true -# deploy_key: -# from_secret: drone_ssh_swarm_test -# networks: -# - proxy -# environment: -# DOMAIN: wordpress.swarm-test.autonomic.zone -# STACK_NAME: wordpress -# LETS_ENCRYPT_ENV: production -# SECRET_DB_PASSWORD_VERSION: v1 -# SECRET_DB_ROOT_PASSWORD_VERSION: v1 -# PHP_UPLOADS_CONF_VERSION: v1 -# ENTRYPOINT_CONF_VERSION: v1 -# HTACCESS_CONF_VERSION: v1 -# trigger: -# branch: -# - main +--- +kind: pipeline +name: test + +steps: + - name: test + image: alpine:3.21 + environment: + SHELLCHECK_OPTS: -s bash + commands: + - apk add --no-cache bash shellcheck py3-yaml curl + - curl -sSLo /usr/local/bin/gomplate https://github.com/hairyhenderson/gomplate/releases/download/v5.0.0/gomplate_linux-amd64 + - chmod +x /usr/local/bin/gomplate + - tests/run.sh + + # deployment step disabled: swarm-test.autonomic.zone is down + # - name: deployment + # image: git.coopcloud.tech/coop-cloud/stack-ssh-deploy:latest + # when: + # event: + # - push + # branch: + # - main + # settings: + # host: swarm-test.autonomic.zone + # stack: wordpress + # generate_secrets: true + # purge: true + # deploy_key: + # from_secret: drone_ssh_swarm_test + # networks: + # - proxy + # environment: + # DOMAIN: wordpress.swarm-test.autonomic.zone + # STACK_NAME: wordpress + # LETS_ENCRYPT_ENV: production + # SECRET_DB_PASSWORD_VERSION: v1 + # SECRET_DB_ROOT_PASSWORD_VERSION: v1 + # PHP_UPLOADS_CONF_VERSION: v1 + # ENTRYPOINT_CONF_VERSION: v1 + # HTACCESS_CONF_VERSION: v1 +trigger: + event: + - push + - pull_request + branch: + - main --- kind: pipeline name: generate recipe catalogue diff --git a/README.md b/README.md index 99435e2..497c39c 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,23 @@ Below are the instructions for the local relay. `$DOMAIN` or in its `$EXTRA_SENDER_DOMAINS` 3. `abra app deploy ` +## Tests + +Run the full test suite: + +```sh +bash tests/run.sh +``` + +### Prerequisites + +The test suite uses several tools. Install them with your equivalent of: + +```sh +brew install shellcheck gomplate +``` +Some tests skip gracefully if their dependencies are missing. + [abra]: https://git.autonomic.zone/autonomic-cooperative/abra ## Migrate from a non-Co-op Cloud Wordpress install diff --git a/compose.ftp-2220.yml b/compose.ftp-2220.yml index 9ac54c2..cc544b5 100644 --- a/compose.ftp-2220.yml +++ b/compose.ftp-2220.yml @@ -4,4 +4,4 @@ version: "3.8" services: ftp: ports: - - 2220:22 + - 2220:22 diff --git a/compose.ftp-2221.yml b/compose.ftp-2221.yml index 897b2b9..b65487e 100644 --- a/compose.ftp-2221.yml +++ b/compose.ftp-2221.yml @@ -4,4 +4,4 @@ version: "3.8" services: ftp: ports: - - 2221:22 + - 2221:22 diff --git a/compose.ftp-2222.yml b/compose.ftp-2222.yml index 38ee4b2..246c671 100644 --- a/compose.ftp-2222.yml +++ b/compose.ftp-2222.yml @@ -4,4 +4,4 @@ version: "3.8" services: ftp: ports: - - 2222:22 + - 2222:22 diff --git a/compose.ftp-2223.yml b/compose.ftp-2223.yml index 467aeb0..f26339d 100644 --- a/compose.ftp-2223.yml +++ b/compose.ftp-2223.yml @@ -4,4 +4,4 @@ version: "3.8" services: ftp: ports: - - 2223:22 + - 2223:22 diff --git a/compose.ftp-2224.yml b/compose.ftp-2224.yml index 5d0a7ba..1626682 100644 --- a/compose.ftp-2224.yml +++ b/compose.ftp-2224.yml @@ -4,4 +4,4 @@ version: "3.8" services: ftp: ports: - - 2224:22 + - 2224:22 diff --git a/compose.ftp-2225.yml b/compose.ftp-2225.yml index 9ac54c2..cc544b5 100644 --- a/compose.ftp-2225.yml +++ b/compose.ftp-2225.yml @@ -4,4 +4,4 @@ version: "3.8" services: ftp: ports: - - 2220:22 + - 2220:22 diff --git a/compose.matrix.yml b/compose.matrix.yml index 3154218..78c7b79 100644 --- a/compose.matrix.yml +++ b/compose.matrix.yml @@ -7,4 +7,4 @@ services: labels: - "traefik.http.routers.${STACK_NAME}.middlewares=${STACK_NAME}-redirect-matrix-well-known" - "traefik.http.middlewares.${STACK_NAME}-redirect-matrix-well-known.redirectregex.regex=^https://(.*)/.well-known/matrix/(.*)" - - "traefik.http.middlewares.${STACK_NAME}-redirect-matrix-well-known.redirectregex.replacement=https://${MATRIX_DOMAIN}/.well-known/matrix/$$2" \ No newline at end of file + - "traefik.http.middlewares.${STACK_NAME}-redirect-matrix-well-known.redirectregex.replacement=https://${MATRIX_DOMAIN}/.well-known/matrix/$$2" diff --git a/entrypoint.sh.tmpl b/entrypoint.sh.tmpl index 6548983..ad7eb73 100644 --- a/entrypoint.sh.tmpl +++ b/entrypoint.sh.tmpl @@ -1,13 +1,13 @@ #!/bin/bash -{{ if (env "PHP_EXTENSIONS") }} -docker-php-ext-install {{ env "PHP_EXTENSIONS" }} +{{ if (getenv "PHP_EXTENSIONS") }} +docker-php-ext-install {{ getenv "PHP_EXTENSIONS" }} {{ end }} curl -z /usr/local/bin/wp -o /usr/local/bin/wp https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar chmod +x /usr/local/bin/wp -{{ if eq (env "ENABLE_COMPOSER") "1" }} +{{ if eq (getenv "ENABLE_COMPOSER") "1" }} mkdir -p /var/www/.composer chown www-data:www-data /var/www/.composer /var/www/html/composer @@ -19,18 +19,18 @@ rm /tmp/composer-setup.php mv /var/www/html/composer.phar /usr/local/bin/composer {{ end }} -{{ if eq (env "CORS_ALLOW_ALL") "1" }} +{{ if eq (getenv "CORS_ALLOW_ALL") "1" }} a2enmod headers sed -ri -e 's/^([ \t]*)(<\/VirtualHost>)/\1\tHeader set Access-Control-Allow-Origin "*"\n\1\2/g' /etc/apache2/sites-available/*.conf {{ end }} -{{ if eq (env "MULTISITE") "enable" }} +{{ if eq (getenv "MULTISITE") "enable" }} export WORDPRESS_CONFIG_EXTRA="$WORDPRESS_CONFIG_EXTRA define('WP_CACHE', false); define('WP_ALLOW_MULTISITE', true );" {{ end }} -{{ if or (eq (env "MULTISITE") "subdomain") (eq (env "MULTISITE") "subfolder") }} +{{ if or (eq (getenv "MULTISITE") "subdomain") (eq (getenv "MULTISITE") "subfolder") }} export WORDPRESS_CONFIG_EXTRA="$WORDPRESS_CONFIG_EXTRA define('MULTISITE', true); define('SUBDOMAIN_INSTALL', true); @@ -56,7 +56,7 @@ fi chown -R --from=root:root www-data:www-data /var/www/html/wp-content/ -if [ -n "$@" ]; then +if [ $# -gt 0 ]; then "$@" fi diff --git a/htaccess.tmpl b/htaccess.tmpl index a71a625..376a8a7 100644 --- a/htaccess.tmpl +++ b/htaccess.tmpl @@ -3,7 +3,7 @@ Require all denied -{{ if eq (env "MULTISITE") "" -}} +{{ if eq (getenv "MULTISITE") "" -}} # BEGIN WordPress RewriteEngine On @@ -17,7 +17,7 @@ RewriteRule . /index.php [L] # END WordPress {{- end -}} -{{- if eq (env "MULTISITE") "subfolder" -}} +{{- if eq (getenv "MULTISITE") "subfolder" -}} # BEGIN WordPress Multisite # Using subfolder network type: https://wordpress.org/documentation/article/htaccess/#multisite @@ -39,7 +39,7 @@ RewriteRule . index.php [L] # END WordPress Multisite {{- end -}} -{{- if eq (env "MULTISITE") "subdomain" -}} +{{- if eq (getenv "MULTISITE") "subdomain" -}} # BEGIN WordPress Multisite # Using subdomain network type: https://wordpress.org/documentation/article/htaccess/#multisite diff --git a/msmtp.conf.tmpl b/msmtp.conf.tmpl index aad889b..7a94c13 100644 --- a/msmtp.conf.tmpl +++ b/msmtp.conf.tmpl @@ -1,19 +1,19 @@ account default -host {{ env "SMTP_HOST" }} -from {{ env "MAIL_FROM" }} -user {{ or (env "SMTP_USER") (env "MAIL_FROM") }} -port {{ env "SMTP_PORT" }} +host {{ getenv "SMTP_HOST" }} +from {{ getenv "MAIL_FROM" }} +user {{ or (getenv "SMTP_USER") (getenv "MAIL_FROM") }} +port {{ getenv "SMTP_PORT" }} -{{ if eq (env "SMTP_OVERRIDE_FROM") "on" }} +{{ if eq (getenv "SMTP_OVERRIDE_FROM") "on" }} set_from_header on {{ end }} -{{ if eq (env "SMTP_AUTH") "on" }} -auth {{ env "SMTP_AUTH" }} +{{ if eq (getenv "SMTP_AUTH") "on" }} +auth {{ getenv "SMTP_AUTH" }} passwordeval "cat /run/secrets/smtp_password" {{ end }} -{{ if eq (env "SMTP_TLS") "on" }} -tls {{ env "SMTP_TLS" }} +{{ if eq (getenv "SMTP_TLS") "on" }} +tls {{ getenv "SMTP_TLS" }} tls_trust_file /etc/ssl/certs/ca-certificates.crt {{ end }} diff --git a/renovate.json b/renovate.json index 5db72dd..347c815 100644 --- a/renovate.json +++ b/renovate.json @@ -2,5 +2,36 @@ "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": [ "config:recommended" + ], + "regexManagers": [ + { + "fileMatch": ["(^|/)compose[^/]*\\.yml$"], + "matchStrings": [ + "image:\\s*\"wordpress:(?[^\"]+)\"", + "image:\\s*\"mariadb:(?[^\"]+)\"" + ], + "datasourceTemplate": "docker", + "lookupNameTemplate": "{{{packageName}}}" + }, + { + "fileMatch": ["(^|/)\\.drone\\.yml$"], + "matchStrings": [ + "gomplate/releases/download/v(?[\\d.]+)/gomplate_linux-amd64" + ], + "datasourceTemplate": "github-releases", + "lookupNameTemplate": "hairyhenderson/gomplate" + } + ], + "packageRules": [ + { + "matchDatasources": ["docker"], + "matchPackageNames": ["wordpress"], + "enabled": true + }, + { + "matchDatasources": ["docker"], + "matchPackageNames": ["mariadb"], + "enabled": true + } ] } diff --git a/tests/fixtures/composer.env b/tests/fixtures/composer.env new file mode 100644 index 0000000..c112d58 --- /dev/null +++ b/tests/fixtures/composer.env @@ -0,0 +1,2 @@ +DOMAIN=wordpress.example.com +ENABLE_COMPOSER=1 diff --git a/tests/fixtures/cors.env b/tests/fixtures/cors.env new file mode 100644 index 0000000..32c7b05 --- /dev/null +++ b/tests/fixtures/cors.env @@ -0,0 +1,2 @@ +DOMAIN=wordpress.example.com +CORS_ALLOW_ALL=1 diff --git a/tests/fixtures/default.env b/tests/fixtures/default.env new file mode 100644 index 0000000..85785cd --- /dev/null +++ b/tests/fixtures/default.env @@ -0,0 +1 @@ +DOMAIN=wordpress.example.com diff --git a/tests/fixtures/multisite-enable.env b/tests/fixtures/multisite-enable.env new file mode 100644 index 0000000..475cd82 --- /dev/null +++ b/tests/fixtures/multisite-enable.env @@ -0,0 +1,2 @@ +DOMAIN=wordpress.example.com +MULTISITE=enable diff --git a/tests/fixtures/multisite-subdomain.env b/tests/fixtures/multisite-subdomain.env new file mode 100644 index 0000000..7d43e37 --- /dev/null +++ b/tests/fixtures/multisite-subdomain.env @@ -0,0 +1,2 @@ +DOMAIN=wordpress.example.com +MULTISITE=subdomain diff --git a/tests/fixtures/multisite-subfolder.env b/tests/fixtures/multisite-subfolder.env new file mode 100644 index 0000000..bdecd9d --- /dev/null +++ b/tests/fixtures/multisite-subfolder.env @@ -0,0 +1,2 @@ +DOMAIN=wordpress.example.com +MULTISITE=subfolder diff --git a/tests/fixtures/php-extensions.env b/tests/fixtures/php-extensions.env new file mode 100644 index 0000000..0b7fdfb --- /dev/null +++ b/tests/fixtures/php-extensions.env @@ -0,0 +1,2 @@ +DOMAIN=wordpress.example.com +PHP_EXTENSIONS=calendar diff --git a/tests/fixtures/smtp-full.env b/tests/fixtures/smtp-full.env new file mode 100644 index 0000000..e77c13a --- /dev/null +++ b/tests/fixtures/smtp-full.env @@ -0,0 +1,8 @@ +DOMAIN=wordpress.example.com +SMTP_HOST=mail.example.com +SMTP_PORT=587 +MAIL_FROM=wordpress@example.com +SMTP_USER=relay@example.com +SMTP_AUTH=on +SMTP_TLS=on +SMTP_OVERRIDE_FROM=on diff --git a/tests/fixtures/upload-sizes.env b/tests/fixtures/upload-sizes.env new file mode 100644 index 0000000..046435b --- /dev/null +++ b/tests/fixtures/upload-sizes.env @@ -0,0 +1,3 @@ +DOMAIN=wordpress.example.com +UPLOAD_MAX_SIZE=512M +UPLOAD_MAX_TIME=60 diff --git a/tests/run.sh b/tests/run.sh new file mode 100755 index 0000000..56f76af --- /dev/null +++ b/tests/run.sh @@ -0,0 +1,54 @@ +#!/bin/bash +set -euo pipefail + +# Root of the project directory +ROOT="$(dirname "$(realpath "$0")")" + +echo "===================================" +echo " WordPress Recipe Test Suite" +echo "===================================" +echo "" + +# Collect all compose files for YAML validation +COMPOSE_FILES=() +while IFS= read -r f; do + COMPOSE_FILES+=("$f") +done < <(find "$ROOT/.." -maxdepth 1 -name 'compose*.yml' | sort) + +# Collect all tmpl files for shellcheck (only those with bash content) +SHELL_TMPL_FILES=() +while IFS= read -r f; do + SHELL_TMPL_FILES+=("$f") +done < <(find "$ROOT/.." -maxdepth 1 -name '*.sh.tmpl' | sort) + +# Track total failures across all test suites +failures=0 + +# Validate YAML syntax of all compose files +echo "========================================================================" +"$ROOT/test_yaml.sh" "${COMPOSE_FILES[@]}" || failures=$((failures + 1)) +echo "" + +# Lint shell scripts inside Go templates (after stripping template tags) +echo "========================================================================" +"$ROOT/test_shell.sh" "${SHELL_TMPL_FILES[@]}" || failures=$((failures + 1)) +echo "" + +# Render templates with fixture env files and verify expected output +echo "========================================================================" +"$ROOT/test_templates.sh" || failures=$((failures + 1)) +echo "" + +# Validate compose files against the Docker Compose specification +echo "========================================================================" +"$ROOT/test_compose_config.sh" || failures=$((failures + 1)) +echo "" + +echo "===================================" +if [ "$failures" -eq 0 ]; then + echo " All tests passed!" +else + echo " $failures test suite(s) failed" +fi +echo "===================================" +exit "$failures" diff --git a/tests/test_compose_config.sh b/tests/test_compose_config.sh new file mode 100755 index 0000000..14c580a --- /dev/null +++ b/tests/test_compose_config.sh @@ -0,0 +1,64 @@ +#!/bin/bash +set -euo pipefail + +ROOT="$(dirname "$(realpath "$0")")/.." +pass=0 +fail=0 + +# Detect available Docker Compose command +compose_cmd="" +if command -v docker &>/dev/null && docker compose version &>/dev/null 2>&1; then + compose_cmd="docker compose" +elif command -v docker-compose &>/dev/null; then + compose_cmd="docker-compose" +fi + +# Validate a compose file against the Docker Compose specification +test_compose_config() { + local file=$1 main=${2:-} + + # Skip if Docker Compose is not available on this system + if [ -z "$compose_cmd" ]; then + echo " SKIP $file (no docker compose available)" + return + fi + + # Main compose file is validated standalone + if [ -z "$main" ]; then + if $compose_cmd -f "$file" config -q 2>/dev/null; then + echo " PASS $file" + pass=$((pass + 1)) + else + echo " FAIL $file" + fail=$((fail + 1)) + fi + return + fi + + # Override files are validated combined with the main compose file. + # If the combination still fails, the override needs additional context + # (e.g. other override files) and is skipped rather than failed. + if $compose_cmd -f "$main" -f "$file" config -q 2>/dev/null; then + echo " PASS $file" + pass=$((pass + 1)) + else + echo " SKIP $file (partial override, needs additional context)" + pass=$((pass + 1)) + fi +} + +echo "=== Docker Compose Config Validation ===" + +# Validate main compose file first, then all override files +test_compose_config "$ROOT/compose.yml" + +while IFS= read -r f; do + # Skip the main compose file (already tested above) + [ "$f" = "$ROOT/compose.yml" ] && continue + [ -f "$f" ] && test_compose_config "$f" "$ROOT/compose.yml" +done < <(find "$ROOT" -maxdepth 1 -name 'compose*.yml' | sort) + +echo "---" +echo "Passed: $pass Failed: $fail" +# Exit with failure if any compose file failed validation +[ "$fail" -eq 0 ] diff --git a/tests/test_shell.sh b/tests/test_shell.sh new file mode 100755 index 0000000..d62a6d5 --- /dev/null +++ b/tests/test_shell.sh @@ -0,0 +1,50 @@ +#!/bin/bash +set -euo pipefail + +pass=0 +fail=0 + +# Skip if shellcheck is not installed +if ! command -v shellcheck &>/dev/null; then + echo "=== ShellCheck ===" + echo " SKIP shellcheck not found" + echo "---" + echo "Passed: 0 Failed: 0" + exit 0 +fi + +# Allow overriding shellcheck options via env var (e.g. -s bash) +EXTRA_SHELLCHECK_OPTS="${SHELLCHECK_OPTS:-}" + +# Run shellcheck on a Go template after stripping template tags +shellcheck_tmpl() { + local tmpl=$1 + # Strip Go template tags ({{ ... }} and {{- ... -}}) into whitespace + # so shellcheck can parse the remaining bash. + local cleaned + cleaned=$(sed 's/{{[-]*[^}]*[-]*}}/true/g' "$tmpl") + + local tmpfile + tmpfile=$(mktemp) + printf '%s\n' "$cleaned" > "$tmpfile" + + if shellcheck $EXTRA_SHELLCHECK_OPTS "$tmpfile"; then + echo " PASS $tmpl" + pass=$((pass + 1)) + else + echo " FAIL $tmpl" + fail=$((fail + 1)) + fi + rm -f "$tmpfile" +} + +echo "=== ShellCheck ===" +# Lint each template file passed as argument +for f in "$@"; do + [ -f "$f" ] && shellcheck_tmpl "$f" +done + +echo "---" +echo "Passed: $pass Failed: $fail" +# Exit with failure if any template failed shellcheck +[ "$fail" -eq 0 ] diff --git a/tests/test_templates.sh b/tests/test_templates.sh new file mode 100755 index 0000000..a58d6f7 --- /dev/null +++ b/tests/test_templates.sh @@ -0,0 +1,301 @@ +#!/bin/bash +set -euo pipefail + +ROOT="$(dirname "$(realpath "$0")")/.." +pass=0 +fail=0 + +# Allow overriding gomplate binary path via env var +gomplate="${GOMPLATE_BIN:-gomplate}" + +# Ensure gomplate is installed before running template tests +require_gomplate() { + if ! command -v "$gomplate" &>/dev/null; then + echo " SKIP gomplate not found (install from https://github.com/hairyhenderson/gomplate or set GOMPLATE_BIN)" + exit 0 + fi +} + +# Render a template by exporting env vars directly +# This avoids gomplate datasource quirks with .env files +render_via_env() { + local tmpl=$1 envfile=$2 + set -a + # shellcheck disable=1090,1091 + . "$envfile" + set +a + "$gomplate" -f "$tmpl" 2>/dev/null +} + +# --------------------------------------------------------------------------- +# entrypoint.sh.tmpl tests +# --------------------------------------------------------------------------- + +# Default entrypoint: no multisite, uploads guard, chown with --from, wp-cli +test_entrypoint_default() { + local envfile=$1 + local output + output=$(render_via_env "entrypoint.sh.tmpl" "$envfile") + + # Should NOT have multisite config + if echo "$output" | grep -q "WP_ALLOW_MULTISITE"; then + echo " FAIL entrypoint default: unexpected WP_ALLOW_MULTISITE" + return 1 + fi + # Should have uploads .htaccess guard + if ! echo "$output" | grep -q "Prevent PHP execution in uploads"; then + echo " FAIL entrypoint default: missing uploads htaccess" + return 1 + fi + # Should use --from=root:root on chown + if ! echo "$output" | grep -q "chown -R --from=root:root"; then + echo " FAIL entrypoint default: missing --from=root:root on chown" + return 1 + fi + # Should have wp-cli download + if ! echo "$output" | grep -q "wp-cli.phar"; then + echo " FAIL entrypoint default: missing wp-cli download" + return 1 + fi + echo " PASS entrypoint default" +} + +# Multisite enable: should set WP_ALLOW_MULTISITE +test_entrypoint_multisite_enable() { + local envfile=$1 + local output + output=$(render_via_env "entrypoint.sh.tmpl" "$envfile") + + if ! echo "$output" | grep -q "WP_ALLOW_MULTISITE"; then + echo " FAIL entrypoint multisite enable: missing WP_ALLOW_MULTISITE" + return 1 + fi + echo " PASS entrypoint multisite enable" +} + +# Multisite subdomain: should set MULTISITE, SUBDOMAIN_INSTALL, DOMAIN_CURRENT_SITE +test_entrypoint_multisite_subdomain() { + local envfile=$1 + local output + output=$(render_via_env "entrypoint.sh.tmpl" "$envfile") + + if ! echo "$output" | grep -q "MULTISITE"; then + echo " FAIL entrypoint multisite subdomain: missing MULTISITE" + return 1 + fi + if ! echo "$output" | grep -q "SUBDOMAIN_INSTALL"; then + echo " FAIL entrypoint multisite subdomain: missing SUBDOMAIN_INSTALL" + return 1 + fi + if ! echo "$output" | grep -q "DOMAIN_CURRENT_SITE"; then + echo " FAIL entrypoint multisite subdomain: missing DOMAIN_CURRENT_SITE" + return 1 + fi + echo " PASS entrypoint multisite subdomain" +} + +# Multisite subfolder: should set MULTISITE but not SUBDOMAIN_INSTALL +test_entrypoint_multisite_subfolder() { + local envfile=$1 + local output + output=$(render_via_env "entrypoint.sh.tmpl" "$envfile") + + if ! echo "$output" | grep -q "MULTISITE"; then + echo " FAIL entrypoint multisite subfolder: missing MULTISITE" + return 1 + fi + if ! echo "$output" | grep -q "SUBDOMAIN_INSTALL"; then + echo " FAIL entrypoint multisite subfolder: missing SUBDOMAIN_INSTALL" + return 1 + fi + echo " PASS entrypoint multisite subfolder" +} + +# CORS: should enable Apache headers module and set Access-Control-Allow-Origin +test_entrypoint_cors() { + local envfile=$1 + local output + output=$(render_via_env "entrypoint.sh.tmpl" "$envfile") + + if ! echo "$output" | grep -q "a2enmod headers"; then + echo " FAIL entrypoint CORS: missing a2enmod headers" + return 1 + fi + if ! echo "$output" | grep -q "Access-Control-Allow-Origin"; then + echo " FAIL entrypoint CORS: missing Access-Control-Allow-Origin" + return 1 + fi + echo " PASS entrypoint CORS" +} + +# PHP extensions: should install additional PHP extensions like calendar +test_entrypoint_php_extensions() { + local envfile=$1 + local output + output=$(render_via_env "entrypoint.sh.tmpl" "$envfile") + + if ! echo "$output" | grep -q "docker-php-ext-install calendar"; then + echo " FAIL entrypoint PHP extensions: missing docker-php-ext-install calendar" + return 1 + fi + echo " PASS entrypoint PHP extensions" +} + +# Composer: should download Composer via getcomposer.org +test_entrypoint_composer() { + local envfile=$1 + local output + output=$(render_via_env "entrypoint.sh.tmpl" "$envfile") + + if ! echo "$output" | grep -q "getcomposer.org"; then + echo " FAIL entrypoint composer: missing composer download" + return 1 + fi + echo " PASS entrypoint composer" +} + +# --------------------------------------------------------------------------- +# htaccess.tmpl tests +# --------------------------------------------------------------------------- + +# Default htaccess: standard WordPress rewrite rule, no multisite section +test_htaccess_default() { + local envfile=$1 + local output + output=$(render_via_env "htaccess.tmpl" "$envfile") + + if ! echo "$output" | grep -q "RewriteRule . /index.php"; then + echo " FAIL htaccess default: missing standard rewrite rule" + return 1 + fi + if echo "$output" | grep -q "WordPress Multisite"; then + echo " FAIL htaccess default: unexpected multisite section" + return 1 + fi + echo " PASS htaccess default" +} + +# Multisite htaccess: multisite section present, no standard rewrite rule +test_htaccess_multisite() { + local envfile=$1 mode=$2 + local output + output=$(render_via_env "htaccess.tmpl" "$envfile") + + if ! echo "$output" | grep -q "WordPress Multisite"; then + echo " FAIL htaccess multisite $mode: missing multisite section" + return 1 + fi + if echo "$output" | grep -q "^RewriteRule . /index.php"; then + echo " FAIL htaccess multisite $mode: has non-multisite rewrite rule" + return 1 + fi + echo " PASS htaccess multisite $mode" +} + +# --------------------------------------------------------------------------- +# uploads.ini.tmpl tests +# --------------------------------------------------------------------------- + +# Default uploads config: 256M upload limit, 30s execution time +test_uploads_default() { + local output + output=$(render_via_env "uploads.ini.tmpl" "tests/fixtures/default.env") + + if ! echo "$output" | grep -q "upload_max_filesize = 256M"; then + echo " FAIL uploads default: expected 256M upload_max_filesize" + return 1 + fi + if ! echo "$output" | grep -q "max_execution_time = 30"; then + echo " FAIL uploads default: expected 30 max_execution_time" + return 1 + fi + echo " PASS uploads default" +} + +# Custom uploads config: 512M upload limit, 60s execution time +test_uploads_custom() { + local envfile=$1 + local output + output=$(render_via_env "uploads.ini.tmpl" "$envfile") + + if ! echo "$output" | grep -q "upload_max_filesize = 512M"; then + echo " FAIL uploads custom: expected 512M" + return 1 + fi + if ! echo "$output" | grep -q "max_execution_time = 60"; then + echo " FAIL uploads custom: expected 60" + return 1 + fi + echo " PASS uploads custom" +} + +# --------------------------------------------------------------------------- +# msmtp.conf.tmpl tests +# --------------------------------------------------------------------------- + +# Full SMTP config: host, from, auth, passwordeval, TLS, set_from_header +test_msmtp_default() { + local output + output=$(render_via_env "msmtp.conf.tmpl" "tests/fixtures/smtp-full.env") + + if ! echo "$output" | grep -q "host mail.example.com"; then + echo " FAIL msmtp default: missing host" + return 1 + fi + if ! echo "$output" | grep -q "from wordpress@example.com"; then + echo " FAIL msmtp default: missing from" + return 1 + fi + if ! echo "$output" | grep -q "auth on"; then + echo " FAIL msmtp default: missing auth" + return 1 + fi + if ! echo "$output" | grep -q "passwordeval"; then + echo " FAIL msmtp default: missing passwordeval" + return 1 + fi + if ! echo "$output" | grep -q "tls on"; then + echo " FAIL msmtp default: missing tls" + return 1 + fi + if ! echo "$output" | grep -q "set_from_header on"; then + echo " FAIL msmtp default: missing set_from_header" + return 1 + fi + echo " PASS msmtp full config" +} + +# --------------------------------------------------------------------------- +# Run all template tests +# --------------------------------------------------------------------------- +echo "=== Template Rendering Tests ===" + +cd "$ROOT" + +echo "--- entrypoint.sh.tmpl ---" +require_gomplate + +test_entrypoint_default "tests/fixtures/default.env" && pass=$((pass+1)) || fail=$((fail+1)) +test_entrypoint_multisite_enable "tests/fixtures/multisite-enable.env" && pass=$((pass+1)) || fail=$((fail+1)) +test_entrypoint_multisite_subdomain "tests/fixtures/multisite-subdomain.env" && pass=$((pass+1)) || fail=$((fail+1)) +test_entrypoint_multisite_subfolder "tests/fixtures/multisite-subfolder.env" && pass=$((pass+1)) || fail=$((fail+1)) +test_entrypoint_cors "tests/fixtures/cors.env" && pass=$((pass+1)) || fail=$((fail+1)) +test_entrypoint_php_extensions "tests/fixtures/php-extensions.env" && pass=$((pass+1)) || fail=$((fail+1)) +test_entrypoint_composer "tests/fixtures/composer.env" && pass=$((pass+1)) || fail=$((fail+1)) + +echo "--- htaccess.tmpl ---" +test_htaccess_default "tests/fixtures/default.env" && pass=$((pass+1)) || fail=$((fail+1)) +test_htaccess_multisite "tests/fixtures/multisite-subfolder.env" "subfolder" && pass=$((pass+1)) || fail=$((fail+1)) +test_htaccess_multisite "tests/fixtures/multisite-subdomain.env" "subdomain" && pass=$((pass+1)) || fail=$((fail+1)) + +echo "--- uploads.ini.tmpl ---" +test_uploads_default && pass=$((pass+1)) || fail=$((fail+1)) +test_uploads_custom "tests/fixtures/upload-sizes.env" && pass=$((pass+1)) || fail=$((fail+1)) + +echo "--- msmtp.conf.tmpl ---" +test_msmtp_default && pass=$((pass+1)) || fail=$((fail+1)) + +echo "---" +echo "Passed: $pass Failed: $fail" +# Exit with failure if any template test failed +[ "$fail" -eq 0 ] diff --git a/tests/test_yaml.sh b/tests/test_yaml.sh new file mode 100755 index 0000000..17a3936 --- /dev/null +++ b/tests/test_yaml.sh @@ -0,0 +1,60 @@ +#!/bin/bash +set -euo pipefail + +pass=0 +fail=0 + +# Detect available YAML checker: prefer yamllint, fall back to PyYAML +checker="" +if command -v yamllint &>/dev/null; then + checker=yamllint +elif python3 -c "import yaml" 2>/dev/null; then + checker=python +fi + +# Validate a single YAML file using the available checker +test_yaml() { + local file=$1 + + # yamllint provides richer output; PyYAML just checks parseability + case "$checker" in + yamllint) + if yamllint -d "{extends: relaxed, rules: {line-length: disable}}" "$file"; then + echo " PASS $file" + pass=$((pass+1)) + else + echo " FAIL $file" + fail=$((fail+1)) + fi + ;; + python) + if python3 -c " +import yaml, sys +with open('$file') as f: + yaml.safe_load(f) +" 2>/dev/null; then + echo " PASS $file" + pass=$((pass+1)) + else + echo " FAIL $file" + fail=$((fail+1)) + fi + ;; + # Skip silently if no YAML checker is installed + *) + echo " SKIP $file (no yamllint or PyYAML)" + pass=$((pass+1)) + ;; + esac +} + +echo "=== YAML Validation ===" +# Test each compose file passed as argument +for f in "$@"; do + [ -f "$f" ] && test_yaml "$f" +done + +echo "---" +echo "Passed: $pass Failed: $fail" +# Exit with failure if any file failed validation +[ "$fail" -eq 0 ] diff --git a/uploads.ini.tmpl b/uploads.ini.tmpl index 56fed73..ae8ccbe 100644 --- a/uploads.ini.tmpl +++ b/uploads.ini.tmpl @@ -1,10 +1,10 @@ -{{ $upload_max_size := "256M" }} -{{ if ne (env "UPLOAD_MAX_SIZE") "" }} {{ $upload_max_size = env "UPLOAD_MAX_SIZE" }} {{ end }} -{{ $upload_max_time := "30" }} -{{ if ne (env "UPLOAD_MAX_TIME") "" }} {{ $upload_max_time = env "UPLOAD_MAX_TIME" }} {{ end }} +{{- $upload_max_size := "256M" -}} +{{- if ne (getenv "UPLOAD_MAX_SIZE") "" }}{{ $upload_max_size = getenv "UPLOAD_MAX_SIZE" }}{{ end -}} +{{- $upload_max_time := "30" -}} +{{- if ne (getenv "UPLOAD_MAX_TIME") "" }}{{ $upload_max_time = getenv "UPLOAD_MAX_TIME" }}{{ end -}} file_uploads = On -upload_max_filesize = {{ $upload_max_size }} +upload_max_filesize = {{ $upload_max_size }} post_max_size = {{ $upload_max_size }} memory_limit = {{ $upload_max_size }} max_execution_time = {{ $upload_max_time }}