wordpress-charmers team mailing list archive
-
wordpress-charmers team
-
Mailing list archive
-
Message #00508
[Merge] ~tcuthbert/charm-k8s-wordpress:merge_stuff into charm-k8s-wordpress:master
Thomas Cuthbert has proposed merging ~tcuthbert/charm-k8s-wordpress:merge_stuff into charm-k8s-wordpress:master.
Requested reviews:
Wordpress Charmers (wordpress-charmers)
For more details, see:
https://code.launchpad.net/~tcuthbert/charm-k8s-wordpress/+git/charm-k8s-wordpress-1/+merge/394793
--
Your team Wordpress Charmers is requested to review the proposed merge of ~tcuthbert/charm-k8s-wordpress:merge_stuff into charm-k8s-wordpress:master.
diff --git a/.jujuignore b/.jujuignore
index 739b280..71eca3f 100644
--- a/.jujuignore
+++ b/.jujuignore
@@ -2,4 +2,6 @@
/image
*.py[cod]
*.charm
+image-builder
+Dockerfile
Makefile
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..ace57f9
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,101 @@
+ARG DIST_RELEASE
+
+FROM ubuntu:${DIST_RELEASE}
+
+LABEL maintainer="wordpress-charmers@xxxxxxxxxxxxxxxxxxx"
+
+# HTTPS_PROXY used when we RUN curl to download Wordpress itself
+ARG BUILD_DATE
+ARG HTTPS_PROXY
+
+# Launchpad OCI image builds don't support dynamic arg parsing. Skip until
+# https://bugs.launchpad.net/launchpad/+bug/1902010 is resolved.
+#LABEL org.label-schema.build-date=${BUILD_DATE}
+
+ENV APACHE_CONFDIR=/etc/apache2
+ENV APACHE_ENVVARS=/etc/apache2/envvars
+
+# Avoid interactive prompts
+RUN echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections
+
+# Update all packages, remove cruft, install required packages, configure apache
+RUN apt-get update && apt-get -y dist-upgrade \
+ && apt-get --purge autoremove -y \
+ && apt-get install -y apache2 \
+ bzr \
+ curl \
+ git \
+ libapache2-mod-php \
+ libgmp-dev \
+ php \
+ php-curl \
+ php-gd \
+ php-gmp \
+ php-mysql \
+ php-symfony-yaml \
+ php-xml \
+ pwgen \
+ python3 \
+ python3-yaml \
+ ssl-cert \
+ && sed -ri 's/^export ([^=]+)=(.*)$/: ${\1:=\2}\nexport \1/' "$APACHE_ENVVARS" \
+ && . "$APACHE_ENVVARS" \
+ && for dir in "$APACHE_LOCK_DIR" "$APACHE_RUN_DIR" "$APACHE_LOG_DIR"; do rm -rvf "$dir"; mkdir -p "$dir"; chown "$APACHE_RUN_USER:$APACHE_RUN_GROUP" "$dir"; chmod 777 "$dir"; done \
+ && ln -sfT /dev/stderr "$APACHE_LOG_DIR/error.log" \
+ && ln -sfT /dev/stdout "$APACHE_LOG_DIR/access.log" \
+ && ln -sfT /dev/stdout "$APACHE_LOG_DIR/other_vhosts_access.log" \
+ && chown -R --no-dereference "$APACHE_RUN_USER:$APACHE_RUN_GROUP" "$APACHE_LOG_DIR"
+
+# Configure PHP and apache2 - mod_php requires us to use mpm_prefork
+COPY ./image-builder/files/docker-php.conf $APACHE_CONFDIR/conf-available/docker-php.conf
+COPY ./image-builder/files/docker-php-swift-proxy.conf $APACHE_CONFDIR/conf-available/docker-php-swift-proxy.conf
+RUN a2enconf docker-php \
+ && a2dismod mpm_event \
+ && a2enmod headers \
+ && a2enmod mpm_prefork \
+ && a2enmod proxy \
+ && a2enmod proxy_http \
+ && a2enmod rewrite
+
+# Install the main Wordpress code, this will be our only site so /var/www/html is fine
+RUN curl -o wordpress.tar.gz -fSL "https://wordpress.org/latest.tar.gz" \
+ && tar -xzf wordpress.tar.gz -C /usr/src/ \
+ && rm wordpress.tar.gz \
+ && chown -R www-data:www-data /usr/src/wordpress \
+ && rm -rf /var/www/html \
+ && mv /usr/src/wordpress /var/www/html
+
+COPY ./image-builder/files/ /files/
+COPY ./image-builder/fetcher.py .
+RUN mkdir -p /files/themes /files/plugins
+RUN ./fetcher.py
+# Copy our collected themes and plugins into the appropriate paths
+RUN cp -r /files/plugins/* /var/www/html/wp-content/plugins/
+RUN cp -r /files/themes/* /var/www/html/wp-content/themes/
+
+# wp-info.php contains template variables which our ENTRYPOINT script will populate
+RUN install -D /files/wp-info.php /var/www/html/wp-info.php
+RUN install -D /files/wp-config.php /var/www/html/wp-config.php
+RUN chown -R www-data:www-data /var/www/html
+
+# Copy our helper scripts and their wrapper into their own directory
+RUN install /files/docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
+
+RUN install -t /srv/wordpress-helpers/ -D /files/_add_option.php \
+ /files/_enable_plugin.php \
+ /files/_get_option.php \
+ /files/plugin_handler.py \
+ /files/ready.sh
+
+# Make the wrapper executable
+RUN chmod 0755 /srv/wordpress-helpers/plugin_handler.py
+RUN chmod 0755 /srv/wordpress-helpers/ready.sh
+RUN chmod 0755 /usr/local/bin/docker-entrypoint.sh
+
+RUN rm -r /files
+
+# Port 80 only, TLS will terminate elsewhere
+EXPOSE 80
+
+ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
+CMD apachectl -D FOREGROUND
diff --git a/image-builder/Makefile b/image-builder/Makefile
new file mode 100644
index 0000000..4a7a40c
--- /dev/null
+++ b/image-builder/Makefile
@@ -0,0 +1,64 @@
+DIST_RELEASE ?= bionic
+VERSION ?= latest
+DOCKER_DEPS = \
+ apache2 \
+ curl \
+ libapache2-mod-php \
+ libgmp-dev \
+ php \
+ php-curl \
+ php-gd \
+ php-gmp \
+ php-mysql \
+ php-symfony-yaml \
+ php-xml \
+ pwgen \
+ python3 \
+ python3-yaml \
+ ssl-cert
+
+build-image:
+ @echo "Building the image."
+ @docker build \
+ --no-cache=true \
+ --build-arg BUILD_DATE=$$(date -u +'%Y-%m-%dT%H:%M:%SZ') \
+ --build-arg PKGS_TO_INSTALL='$(DOCKER_DEPS)' \
+ --build-arg DIST_RELEASE=$(DIST_RELEASE) \
+ --build-arg HTTPS_PROXY=$(HTTPS_PROXY) \
+ -t wordpress:$(DIST_RELEASE)-$(VERSION) \
+ .
+
+build: lint deps fetch build-image
+ @echo "Pushing to the prod-is-external registry."
+ @docker tag wordpress:$(DIST_RELEASE)-$(VERSION) prod-is-external.docker-registry.canonical.com/wordpress:$(DIST_RELEASE)-$(VERSION)
+ @docker push prod-is-external.docker-registry.canonical.com/wordpress:$(DIST_RELEASE)-$(VERSION)
+
+deps:
+ @echo "Checking dependencies are present"
+ @command -v bzr >/dev/null 2>&1 || { echo "I require bzr but it's not installed. Aborting." >&2; exit 1; }
+ @command -v git >/dev/null 2>&1 || { echo "I require git but it's not installed. Aborting." >&2; exit 1; }
+
+fetch:
+ @echo "Fetching plugins and themes."
+ @tox -e fetch
+
+lint: clean
+ @echo "Running flake8"
+ @tox -e lint
+
+test: lint
+ @echo "Running unit tests"
+ @tox -e unit
+
+clean:
+ @echo "Cleaning files"
+ @rm -rf ./.tox
+ @rm -rf ./.pytest_cache
+ @rm -rf ./files/plugins/*
+ @rm -rf ./files/themes/*
+ @rm -rf ./files/__pycache__
+ @rm -rf ./tests/unit/__pycache__
+ @mkdir -p ./files/plugins
+ @mkdir -p ./files/themes
+
+.PHONY: build lint clean
diff --git a/image-builder/fetcher.py b/image-builder/fetcher.py
new file mode 100755
index 0000000..5ce0b50
--- /dev/null
+++ b/image-builder/fetcher.py
@@ -0,0 +1,133 @@
+#!/usr/bin/env python3
+
+import os
+import shutil
+import subprocess
+import urllib.request
+import zipfile
+
+
+zip_plugins_to_get = {
+ # please keep these in alphabetical order
+ '404page',
+ 'all-in-one-event-calendar',
+ 'coschedule-by-todaymade',
+ 'elementor',
+ 'essential-addons-for-elementor-lite',
+ 'favicon-by-realfavicongenerator',
+ 'feedwordpress',
+ 'fruitful-shortcodes',
+ 'genesis-columns-advanced',
+ 'line-break-shortcode',
+ 'no-category-base-wpml',
+ 'openid',
+ 'post-grid',
+ 'powerpress',
+ 'redirection',
+ 'relative-image-urls',
+ 'rel-publisher',
+ 'safe-svg',
+ 'show-current-template',
+ 'simple-301-redirects',
+ 'simple-custom-css',
+ 'social-media-buttons-toolbar',
+ 'so-widgets-bundle',
+ 'svg-support',
+ 'syntaxhighlighter',
+ 'wordpress-importer',
+ 'wordpress-seo',
+ 'wp-font-awesome',
+ 'wp-lightbox-2',
+ 'wp-markdown',
+ 'wp-mastodon-share',
+ 'wp-polls',
+ 'wp-statistics',
+}
+
+branch_plugins_to_get = {
+ # please keep these in alphabetical order
+ 'launchpad-integration': {'url': 'https://git.launchpad.net/~canonical-sysadmins/wordpress-launchpad-integration/+git/wordpress-launchpad-integration'},
+ 'openstack-objectstorage': {'url': 'https://git.launchpad.net/~canonical-sysadmins/wordpress/+git/openstack-objectstorage-k8s'},
+ 'teams-integration': {'url': 'https://git.launchpad.net/~canonical-sysadmins/wordpress-teams-integration/+git/wordpress-teams-integration'},
+ 'xubuntu-team-members': {'url': 'https://git.launchpad.net/~canonical-sysadmins/wordpress/+git/wp-plugin-xubuntu-team-members'},
+}
+
+branch_themes_to_get = {
+ # please keep these in alphabetical order
+ 'fruitful': {'url': 'https://git.launchpad.net/~canonical-sysadmins/wordpress/+git/wp-theme-fruitful'},
+ 'light-wordpress-theme': {'url': 'https://git.launchpad.net/~canonical-sysadmins/ubuntu-community-webthemes/+git/light-wordpress-theme'},
+ 'mscom': {'url': 'https://git.launchpad.net/~canonical-sysadmins/wordpress/+git/wp-theme-mscom'},
+ 'twentyeleven': {'url': 'https://git.launchpad.net/~canonical-sysadmins/wordpress/+git/wp-theme-twentyeleven'},
+ 'ubuntu-cloud-website': {'url': 'https://git.launchpad.net/~canonical-sysadmins/ubuntu-cloud-website/+git/ubuntu-cloud-website'},
+ 'ubuntu-community': {'url': 'https://git.launchpad.net/~canonical-sysadmins/wordpress/+git/wp-theme-ubuntu-community'},
+ 'ubuntu-community-wordpress-theme': {'url': 'https://git.launchpad.net/~canonical-sysadmins/ubuntu-community-wordpress-theme/+git/ubuntu-community-wordpress-theme'},
+ 'ubuntu-fi-new': {'url': 'https://git.launchpad.net/~canonical-sysadmins/wordpress/+git/wp-theme-ubuntu-fi'},
+ 'ubuntu-light': {'url': 'https://git.launchpad.net/~canonical-sysadmins/wordpress/+git/wp-theme-ubuntu-light'},
+ 'ubuntustudio-wp': {'url': 'https://git.launchpad.net/~canonical-sysadmins/wordpress/+git/wp-theme-ubuntustudio-wp'},
+ 'wordpress_launchpad': {'url': 'https://git.launchpad.net/~canonical-sysadmins/wordpress/+git/wp-theme-launchpad'},
+ 'xubuntu-theme': {'url': 'https://git.launchpad.net/~canonical-sysadmins/wordpress/+git/wp-theme-xubuntu-website'},
+}
+
+
+def get_plugins(zip_plugins, branch_plugins):
+ total_zips = len(zip_plugins)
+ current_zip = 0
+ for zip_plugin in zip_plugins:
+ current_zip = current_zip + 1
+ print('Downloading {} of {} zipped plugins: {} ...'.format(current_zip, total_zips, zip_plugin))
+ url = 'https://downloads.wordpress.org/plugin/{}.latest-stable.zip'.format(zip_plugin)
+ file_name = os.path.join(os.getcwd(), 'files/plugins', os.path.basename(url))
+ with urllib.request.urlopen(url) as response, open(file_name, 'wb') as out_file:
+ shutil.copyfileobj(response, out_file)
+ with zipfile.ZipFile(file_name, 'r') as zip_ref:
+ zip_ref.extractall(os.path.join(os.getcwd(), 'files/plugins'))
+ os.remove(file_name)
+
+ total_branches = len(branch_plugins)
+ current_branch = 0
+ for branch_plugin in branch_plugins:
+ current_branch = current_branch + 1
+ print('Downloading {} of {} branched plugins: {} ...'.format(current_branch, total_branches, branch_plugin))
+ url = branch_plugins[branch_plugin].get('url')
+ basename = os.path.basename(url)
+ if basename.startswith('lp:'):
+ basename = basename[3:]
+ if basename.startswith('wp-plugin-'):
+ basename = basename[10:]
+ dest = os.path.join(os.getcwd(), 'files/plugins', basename)
+ if url.startswith('lp:'):
+ cmd = ['bzr', 'branch', url, dest]
+ elif url.startswith('https://git'):
+ cmd = ['git', 'clone', url, dest]
+ else:
+ print("ERROR: Don't know how to clone {}".format(url))
+ exit(1)
+ _ = subprocess.check_output(cmd, universal_newlines=True, stderr=subprocess.STDOUT)
+
+
+def get_themes(branch_themes):
+ total_branches = len(branch_themes)
+ current_branch = 0
+ for branch_theme in branch_themes:
+ current_branch = current_branch + 1
+ print('Downloading {} of {} branched themes: {} ...'.format(current_branch, total_branches, branch_theme))
+ url = branch_themes[branch_theme].get('url')
+ basename = os.path.basename(url)
+ if basename.startswith('lp:'):
+ basename = basename[3:]
+ if basename.startswith('wp-theme-'):
+ basename = basename[9:]
+ dest = os.path.join(os.getcwd(), 'files/themes/', basename)
+ if url.startswith('lp:'):
+ cmd = ['bzr', 'branch', url, dest]
+ elif url.startswith('https://git'):
+ cmd = ['git', 'clone', url, dest]
+ else:
+ print("ERROR: Don't know how to clone {}".format(url))
+ exit(1)
+ _ = subprocess.check_output(cmd, universal_newlines=True, stderr=subprocess.STDOUT)
+
+
+if __name__ == '__main__':
+ get_plugins(zip_plugins_to_get, branch_plugins_to_get)
+ get_themes(branch_themes_to_get)
diff --git a/image-builder/files/_add_option.php b/image-builder/files/_add_option.php
new file mode 100644
index 0000000..cbfe8a8
--- /dev/null
+++ b/image-builder/files/_add_option.php
@@ -0,0 +1,11 @@
+<?php
+
+# PHP cli helper which adds new option to wp_settings table
+#
+# Example use:
+# php ./_add_option.php akismet_strictness 0
+
+@include "wp-config.php";
+@include_once "wp-includes/option.php";
+@include_once "wp-includes/functions.php";
+add_option($argv[1], maybe_unserialize($argv[2]));
diff --git a/image-builder/files/_enable_plugin.php b/image-builder/files/_enable_plugin.php
new file mode 100644
index 0000000..11ed3e7
--- /dev/null
+++ b/image-builder/files/_enable_plugin.php
@@ -0,0 +1,14 @@
+<?php
+
+# PHP cli helper which enables a plugin. Plugin name is in
+# format returned by _list_inactive_plugins.php helper
+#
+# Example use:
+# php ./_enable_plugin.php akismet/akismet.php openid/openid.php
+
+@include "wp-config.php";
+@include_once "wp-admin/includes/plugin.php";
+$result = activate_plugins(array_slice($argv, 1));
+if ( is_wp_error( $result ) ) {
+ throw new Exception($result);
+}
diff --git a/image-builder/files/_get_option.php b/image-builder/files/_get_option.php
new file mode 100644
index 0000000..4168301
--- /dev/null
+++ b/image-builder/files/_get_option.php
@@ -0,0 +1,15 @@
+<?php
+
+# PHP cli helper which returns option value if present
+#
+# Example use:
+# php ./_get_option.php akismet_strictness
+
+@include "wp-config.php";
+@include_once "wp-includes/option.php";
+@include_once "/usr/share/php/Symfony/Component/Yaml/autoload.php";
+
+use Symfony\Component\Yaml\Yaml;
+
+$value = get_option($argv[1]);
+print Yaml::dump($value);
diff --git a/image-builder/files/docker-entrypoint.sh b/image-builder/files/docker-entrypoint.sh
new file mode 100644
index 0000000..9cfdba9
--- /dev/null
+++ b/image-builder/files/docker-entrypoint.sh
@@ -0,0 +1,22 @@
+#!/bin/bash
+set -eu
+
+sed -i -e "s/%%%WORDPRESS_DB_HOST%%%/$WORDPRESS_DB_HOST/" /var/www/html/wp-info.php
+sed -i -e "s/%%%WORDPRESS_DB_NAME%%%/$WORDPRESS_DB_NAME/" /var/www/html/wp-info.php
+sed -i -e "s/%%%WORDPRESS_DB_USER%%%/$WORDPRESS_DB_USER/" /var/www/html/wp-info.php
+sed -i -e "s/%%%WORDPRESS_DB_PASSWORD%%%/$WORDPRESS_DB_PASSWORD/" /var/www/html/wp-info.php
+
+for key in AUTH_KEY SECURE_AUTH_KEY LOGGED_IN_KEY NONCE_KEY AUTH_SALT SECURE_AUTH_SALT LOGGED_IN_SALT NONCE_SALT;
+do
+ sed -i -e "s/%%%${key}%%%/$(printenv ${key})/" /var/www/html/wp-info.php
+done
+
+# If we have passed in SWIFT_URL, then append swift proxy config.
+[ -z "${SWIFT_URL-}" ] || a2enconf docker-php-swift-proxy
+
+nohup bash -c "/srv/wordpress-helpers/plugin_handler.py &"
+
+sed -i 's/max_execution_time = 30/max_execution_time = 300/' /etc/php/7.2/apache2/php.ini
+sed -i 's/upload_max_filesize = 2M/upload_max_filesize = 10M/' /etc/php/7.2/apache2/php.ini
+
+exec "$@"
diff --git a/image-builder/files/docker-php-swift-proxy.conf b/image-builder/files/docker-php-swift-proxy.conf
new file mode 100644
index 0000000..871df91
--- /dev/null
+++ b/image-builder/files/docker-php-swift-proxy.conf
@@ -0,0 +1,4 @@
+PassEnv SWIFT_URL
+ProxyPass /wp-content/uploads/ ${SWIFT_URL}
+ProxyPassReverse /wp-content/uploads/ ${SWIFT_URL}
+Timeout 300
diff --git a/image-builder/files/docker-php.conf b/image-builder/files/docker-php.conf
new file mode 100644
index 0000000..00d1f36
--- /dev/null
+++ b/image-builder/files/docker-php.conf
@@ -0,0 +1,21 @@
+<FilesMatch \.php$>
+ SetHandler application/x-httpd-php
+</FilesMatch>
+
+<Location "/wp-admin">
+ Header Set Cache-Control "max-age=0, no-store"
+</Location>
+
+DirectoryIndex disabled
+DirectoryIndex index.php index.html
+
+<Directory /var/www/>
+ Options -Indexes
+ AllowOverride All
+ RewriteEngine On
+ RewriteBase /
+ RewriteRule ^index\.php$ - [L]
+ RewriteCond %{REQUEST_FILENAME} !-f
+ RewriteCond %{REQUEST_FILENAME} !-d
+ RewriteRule . /index.php [L]
+</Directory>
diff --git a/image-builder/files/plugin_handler.py b/image-builder/files/plugin_handler.py
new file mode 100644
index 0000000..94403ec
--- /dev/null
+++ b/image-builder/files/plugin_handler.py
@@ -0,0 +1,152 @@
+#!/usr/bin/python3
+
+import logging
+import os
+import subprocess
+import urllib.request
+from time import sleep
+from yaml import safe_load
+
+helpers_path = "/srv/wordpress-helpers"
+install_path = "/var/www/html"
+
+
+def call_php_helper(helper, stdin="", *args):
+ path = os.path.join(helpers_path, helper)
+ cmd = ["php", path]
+ cmd.extend([str(arg) for arg in args])
+ logging.info(cmd)
+ process = subprocess.Popen(
+ cmd,
+ cwd=install_path,
+ stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT,
+ )
+ return process.communicate(stdin)[0] # spit back stdout+stderr combined
+
+
+def enable_plugin(*plugins):
+ logging.info("Enabling plugins: {}".format(plugins))
+ logging.info(call_php_helper("_enable_plugin.php", "", *plugins))
+
+
+def get_option(key):
+ value = call_php_helper("_get_option.php", "", key)
+ return safe_load(value)
+
+
+def add_option(key, value):
+ # Ensure we don't overwrite settings
+ if not get_option(key):
+ logging.info("Adding option: {}".format(key))
+ call_php_helper("_add_option.php", "", key, value)
+ else:
+ logging.info('Option "{}" already in place, skipping.'.format(key))
+
+
+def encode_team_map(team_map):
+ # example: site-sysadmins=administrator,site-editors=editor,site-executives=editor
+ team_map_lines = []
+ i = 0
+ team_map_lines.append("a:{}:{{".format(len(team_map.split(","))))
+ for mapping in team_map.split(","):
+ i = i + 1
+ team, role = mapping.split("=", 2)
+ team_map_lines.append("i:{};".format(i))
+ team_map_lines.append('O:8:"stdClass":4:{')
+ team_map_lines.append('s:2:"id";')
+ team_map_lines.append("i:{};".format(i))
+ team_map_lines.append('s:4:"team";')
+ team_map_lines.append('s:{}:"{}";'.format(len(team), team))
+ team_map_lines.append('s:4:"role";')
+ team_map_lines.append('s:{}:"{}";'.format(len(role), role))
+ team_map_lines.append('s:6:"server";')
+ team_map_lines.append('s:1:"0";')
+ team_map_lines.append("}")
+ team_map_lines.append("}")
+
+ return "".join(team_map_lines)
+
+
+def enable_akismet(key):
+ enable_plugin("akismet/akismet.php")
+ add_option("akismet_strictness", "0")
+ add_option("akismet_show_user_comments_approved", "0")
+ add_option("wordpress_api_key", key)
+
+
+def enable_openid(team_map):
+ encoded_team_map = encode_team_map(team_map)
+ enable_plugin("openid/openid.php")
+ add_option("openid_required_for_registration", "1")
+ add_option("openid_teams_trust_list", encoded_team_map)
+
+
+def enable_swift(swift_config):
+ enable_plugin("openstack-objectstorage/objectstorage.php")
+ for k, v in swift_config.items():
+ add_option("object_storage_{}".format(k), v)
+
+
+def configure_wordpress():
+ url = "http://localhost"
+ sleep_time = 10
+ total_sleep_time = 0
+ max_sleep_time = 600
+ success = False
+ while success is not True:
+ if total_sleep_time > max_sleep_time:
+ return False
+ try:
+ response = urllib.request.urlopen(url, timeout=sleep_time)
+ except Exception:
+ logging.info("Waiting for Wordpress to accept connections")
+ sleep(sleep_time)
+ total_sleep_time = total_sleep_time + sleep_time
+ else:
+ if response.status == 200:
+ success = True
+ else:
+ logging.info(
+ "Waiting for Wordpress to return HTTP 200 (got {})".format(
+ response.status
+ )
+ )
+ sleep(sleep_time)
+ return True
+
+
+if __name__ == "__main__":
+ logger = logging.getLogger(__name__)
+ logging.basicConfig(
+ filename="/var/log/wordpress-plugin-handler.log", level=logging.DEBUG
+ )
+
+ if configure_wordpress():
+ # create the file to satisfy the readinessProbe
+ open(os.path.join(helpers_path, '.ready'), 'a').close()
+
+ key = os.getenv("WP_PLUGIN_AKISMET_KEY")
+ if key:
+ enable_akismet(key)
+
+ team_map = os.getenv("WP_PLUGIN_OPENID_TEAM_MAP")
+ if team_map:
+ enable_openid(team_map)
+
+ swift_url = os.getenv("SWIFT_URL")
+ if swift_url:
+ swift_config = {}
+ swift_config['url'] = swift_url
+ swift_config['auth_url'] = os.getenv("SWIFT_AUTH_URL")
+ swift_config['bucket'] = os.getenv("SWIFT_BUCKET")
+ swift_config['password'] = os.getenv("SWIFT_PASSWORD")
+ swift_config['prefix'] = os.getenv("SWIFT_PREFIX")
+ swift_config['region'] = os.getenv("SWIFT_REGION")
+ swift_config['tenant'] = os.getenv("SWIFT_TENANT")
+ swift_config['username'] = os.getenv("SWIFT_USERNAME")
+ swift_config['copy_to_swift'] = os.getenv("SWIFT_COPY_TO_SWIFT")
+ swift_config['serve_from_swift'] = os.getenv("SWIFT_SERVE_FROM_SWIFT")
+ swift_config['remove_local_file'] = os.getenv("SWIFT_REMOVE_LOCAL_FILE")
+ enable_swift(swift_config)
diff --git a/image-builder/files/ready.sh b/image-builder/files/ready.sh
new file mode 100644
index 0000000..a676bc2
--- /dev/null
+++ b/image-builder/files/ready.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+
+READY=/srv/wordpress-helpers/.ready
+
+# This script is designed to be called by the Kubernetes
+# readinessProbe. If the WP plugins haven't been enabled
+# which we know due to the $READY file not existing then
+# return a failure, replace the shell with whatever curl
+# returns for checking that the website is alive.
+if [ -f "$READY" ]; then
+ exec /usr/bin/curl --silent http://localhost
+fi
+
+exit 1
diff --git a/image-builder/files/wp-config.php b/image-builder/files/wp-config.php
new file mode 100644
index 0000000..b47b161
--- /dev/null
+++ b/image-builder/files/wp-config.php
@@ -0,0 +1,52 @@
+<?php
+#
+# " "
+# mmm m m mmm m m
+# # # # # # #
+# # # # # # #
+# # "mm"# # "mm"#
+# # #
+# "" ""
+# This file is managed by Juju. Do not make local changes.
+#
+
+/* That's all, stop editing! Happy blogging. */
+
+/** Enable container debug level logging. **/
+if ( getenv("WORDPRESS_DEBUG") ) {
+ define( 'WP_DEBUG', true );
+ define( 'WP_DEBUG_DISPLAY', false );
+ define( 'WP_DEBUG_LOG', '/dev/stderr' );
+}
+
+/** Fixes for mixed content when WordPress is behind nginx TLS reverse proxy.
+ * https://ahenriksson.com/2020/01/27/how-to-set-up-wordpress-behind-a-reverse-proxy-when-using-nginx/
+ * */
+define('FORCE_SSL_ADMIN', true);
+if ($_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https')
+ $_SERVER['HTTPS']='on';
+
+/** Absolute path to the WordPress directory. */
+if ( !defined('ABSPATH') )
+ define('ABSPATH', dirname(__FILE__) . '/');
+
+/** Pull in the config information */
+require_once(ABSPATH . 'wp-info.php');
+
+/** Sets up WordPress vars and included files. */
+require_once(ABSPATH . 'wp-settings.php');
+
+/** Prevent wordpress from attempting to update and make external requests.
+ *
+ * Our firewalls do not allow WordPress to communicate externally to wordpress.org
+ * for auto-updates, this causes our kubernetes pods to timeout during initial configuration,
+ * preventing the site from ever becoming available.
+ * */
+define( 'AUTOMATIC_UPDATER_DISABLED', true );
+define( 'WP_AUTO_UPDATE_CORE', false );
+
+$http_host = $_SERVER['HTTP_HOST'];
+define('WP_HOME',"https://$http_host");
+define('WP_SITEURL',"https://$http_host");
+
+remove_filter('template_redirect', 'redirect_canonical');
diff --git a/image-builder/files/wp-info.php b/image-builder/files/wp-info.php
new file mode 100644
index 0000000..5a891a2
--- /dev/null
+++ b/image-builder/files/wp-info.php
@@ -0,0 +1,53 @@
+<?php
+#
+# " "
+# mmm m m mmm m m
+# # # # # # #
+# # # # # # #
+# # "mm"# # "mm"#
+# # #
+# "" ""
+# This file is managed by Juju. Do not make local changes.
+#
+
+// We have to cheat a little because frontend service can terminate SSL
+// If it does it should set X-Edge-Https header to "on" to tell us original
+// request came on https
+
+if (!empty($_SERVER['HTTP_X_EDGE_HTTPS']) && 'off' != $_SERVER['HTTP_X_EDGE_HTTPS']) {
+ $_SERVER['HTTPS'] = 'on';
+}
+
+if (!empty($_SERVER['HTTPS']) && 'off' != $_SERVER['HTTPS']) {
+ define('WP_PLUGIN_URL', 'https://' . $_SERVER['HTTP_HOST'] . '/wp-content/plugins');
+ define('WP_CONTENT_URL', 'https://' . $_SERVER['HTTP_HOST'] . '/wp-content');
+ define('WP_SITEURL', 'https://' . $_SERVER['HTTP_HOST']);
+ define('WP_URL', 'https://' . $_SERVER['HTTP_HOST']);
+ define('WP_HOME', 'https://' . $_SERVER['HTTP_HOST']);
+}
+else {
+ define('WP_PLUGIN_URL', 'http://' . $_SERVER['HTTP_HOST'] . '/wp-content/plugins');
+ define('WP_CONTENT_URL', 'http://' . $_SERVER['HTTP_HOST'] . '/wp-content');
+ define('WP_SITEURL', 'http://' . $_SERVER['HTTP_HOST']);
+ define('WP_URL', 'http://' . $_SERVER['HTTP_HOST']);
+ define('WP_HOME', 'http://' . $_SERVER['HTTP_HOST']);
+}
+
+define('DB_NAME', '%%%WORDPRESS_DB_NAME%%%');
+define('DB_USER', '%%%WORDPRESS_DB_USER%%%');
+define('DB_HOST', '%%%WORDPRESS_DB_HOST%%%');
+
+define('DB_PASSWORD', '%%%WORDPRESS_DB_PASSWORD%%%');
+
+define('WP_CACHE', true);
+
+define('AUTH_KEY', '%%%AUTH_KEY%%%');
+define('SECURE_AUTH_KEY', '%%%SECURE_AUTH_KEY%%%');
+define('LOGGED_IN_KEY', '%%%LOGGED_IN_KEY%%%');
+define('NONCE_KEY', '%%%NONCE_KEY%%%');
+define('AUTH_SALT', '%%%AUTH_SALT%%%');
+define('SECURE_AUTH_SALT', '%%%SECURE_AUTH_SALT%%%');
+define('LOGGED_IN_SALT', '%%%LOGGED_IN_SALT%%%');
+define('NONCE_SALT', '%%%NONCE_SALT%%%');
+
+$table_prefix = 'wp_';
diff --git a/image-builder/tests/unit/requirements.txt b/image-builder/tests/unit/requirements.txt
new file mode 100644
index 0000000..092d46a
--- /dev/null
+++ b/image-builder/tests/unit/requirements.txt
@@ -0,0 +1,3 @@
+pytest
+pytest-cov
+PyYAML
diff --git a/image-builder/tests/unit/test_plugin_hander.py b/image-builder/tests/unit/test_plugin_hander.py
new file mode 100644
index 0000000..86f353b
--- /dev/null
+++ b/image-builder/tests/unit/test_plugin_hander.py
@@ -0,0 +1,37 @@
+import os
+import sys
+import unittest
+from unittest import mock
+
+sys.path.append(os.path.join("..", "..", os.path.dirname(__file__)))
+from files import plugin_handler # NOQA: E402
+
+
+class testWrapper(unittest.TestCase):
+ def setUp(self):
+ self.maxDiff = None
+
+ @mock.patch("files.plugin_handler.logging")
+ def test_team_mapper(self, foo):
+ given = ",".join(
+ [
+ "canonical-sysadmins=administrator",
+ "canonical-website-editors=editor",
+ "canonical-website-admins=administrator",
+ "launchpad=editor",
+ ]
+ )
+ want = "".join(
+ [
+ """a:4:{i:1;O:8:"stdClass":4:{s:2:"id";i:1;s:4:"team";s:19:"canonical-sysadmins";""",
+ """s:4:"role";s:13:"administrator";s:6:"server";s:1:"0";}""",
+ """i:2;O:8:"stdClass":4:{s:2:"id";i:2;s:4:"team";s:25:"canonical-website-editors";""",
+ """s:4:"role";s:6:"editor";s:6:"server";s:1:"0";}""",
+ """i:3;O:8:"stdClass":4:{s:2:"id";i:3;s:4:"team";s:24:"canonical-website-admins";""",
+ """s:4:"role";s:13:"administrator";s:6:"server";s:1:"0";}""",
+ """i:4;O:8:"stdClass":4:{s:2:"id";i:4;s:4:"team";s:9:"launchpad";""",
+ """s:4:"role";s:6:"editor";s:6:"server";s:1:"0";}}""",
+ ]
+ )
+ got = plugin_handler.encode_team_map(given)
+ self.assertEqual(got, want)
diff --git a/image-builder/tox.ini b/image-builder/tox.ini
new file mode 100644
index 0000000..c03998c
--- /dev/null
+++ b/image-builder/tox.ini
@@ -0,0 +1,40 @@
+[tox]
+skipsdist=True
+envlist = build
+skip_missing_interpreters = True
+
+[testenv]
+basepython = python3
+setenv =
+ PYTHONPATH = .
+
+[testenv:black]
+commands = {envbindir}/black --skip-string-normalization --line-length=120 .
+deps = black
+
+[testenv:lint]
+commands = {envbindir}/flake8
+deps = flake8
+
+[testenv:fetch]
+commands = ./fetcher.py
+passenv = HTTP_PROXY HTTPS_PROXY
+setenv =
+ BZR_HOME = /tmp
+
+[testenv:unit]
+commands =
+ pytest {posargs:-v --cov=. --cov-report=term-missing --cov-branch}
+deps = -r{toxinidir}/tests/unit/requirements.txt
+setenv =
+ PYTHONPATH={toxinidir}/lib
+ TZ=UTC
+
+[flake8]
+exclude =
+ .git,
+ __pycache__,
+ .tox,
+ files/
+max-line-length = 120
+max-complexity = 10
Follow ups