← Back to team overview

wordpress-charmers team mailing list archive

[Merge] ~barryprice/charm-k8s-wordpress/+git/wordpress-k8s-image-builder:master into charm-k8s-wordpress:master

 

Barry Price has proposed merging ~barryprice/charm-k8s-wordpress/+git/wordpress-k8s-image-builder:master into charm-k8s-wordpress:master.

Commit message:
Basic swift config for openstack-objectstorage plugin, no input validation yet

Requested reviews:
  Wordpress Charmers (wordpress-charmers)

For more details, see:
https://code.launchpad.net/~barryprice/charm-k8s-wordpress/+git/wordpress-k8s-image-builder/+merge/383234
-- 
Your team Wordpress Charmers is requested to review the proposed merge of ~barryprice/charm-k8s-wordpress/+git/wordpress-k8s-image-builder:master into charm-k8s-wordpress:master.
diff --git a/.gitignore b/.gitignore
index 33ab628..174f4ff 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,14 @@
+<<<<<<< .gitignore
 .tox
 .coverage
 __pycache__
+=======
+*.pyc
+*.swp
+*~
+__pycache__
+files/plugins/
+files/themes/
+.coverage
+.tox/
+>>>>>>> .gitignore
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..e58cfac
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,74 @@
+ARG DIST_RELEASE
+
+FROM ubuntu:${DIST_RELEASE}
+
+LABEL maintainer="wordpress-charmers@xxxxxxxxxxxxxxxxxxx"
+
+# HTTPS_PROXY used when we RUN curl to download Wordpress itself
+ARG HTTPS_PROXY
+ARG BUILD_DATE
+ARG PKGS_TO_INSTALL
+
+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 ${PKGS_TO_INSTALL} \
+    && 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 ./files/docker-php.conf $APACHE_CONFDIR/conf-available/docker-php.conf
+RUN a2enconf docker-php \
+    && a2dismod mpm_event \
+    && 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 our collected themes and plugins into the appropriate paths
+COPY --chown=www-data:www-data ./files/plugins/ /var/www/html/wp-content/plugins/
+COPY --chown=www-data:www-data ./files/themes/ /var/www/html/wp-content/themes/
+
+# wp-info.php contains template variables which our ENTRYPOINT script will populate
+COPY ./files/wp-info.php ./files/wp-config.php /var/www/html/
+
+# Copy our helper scripts and their wrapper into their own directory
+COPY ./files/_add_option.php \
+    ./files/_enable_plugin.php \
+    ./files/_get_option.php \
+    ./files/plugin_handler.py \
+    /srv/wordpress-helpers/
+
+# Make the wrapper executable
+RUN chmod 0755 /srv/wordpress-helpers/plugin_handler.py
+
+# entrypoint script will configure Wordpress based on env variables
+COPY ./files/docker-entrypoint.sh /usr/local/bin/
+RUN chmod 0755 /usr/local/bin/docker-entrypoint.sh
+
+# Port 80 only, TLS will terminate elsewhere
+EXPOSE 80
+
+ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
+CMD apachectl -D FOREGROUND
diff --git a/Makefile b/Makefile
index cf18918..2c9c171 100644
--- a/Makefile
+++ b/Makefile
@@ -1,3 +1,4 @@
+<<<<<<< Makefile
 lint:
 	@echo "Normalising python layout with black."
 	@tox -e black
@@ -14,3 +15,68 @@ clean:
 	@git clean -fXd
 
 .PHONY: lint test unittest clean
+=======
+DIST_RELEASE ?= bionic
+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)-latest \
+		.
+
+build: lint deps fetch build-image
+	@echo "Pushing to the prod-is-external registry."
+	@docker tag wordpress:$(DIST_RELEASE)-latest prod-is-external.docker-registry.canonical.com/wordpress:$(DIST_RELEASE)-latest
+	@docker push prod-is-external.docker-registry.canonical.com/wordpress:$(DIST_RELEASE)-latest
+
+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
+>>>>>>> Makefile
diff --git a/fetcher.py b/fetcher.py
new file mode 100755
index 0000000..410748e
--- /dev/null
+++ b/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': 'lp:wordpress-launchpad-integration'},
+    'openstack-objectstorage': {'url': 'lp:~canonical-sysadmins/wordpress/openstack-objectstorage'},
+    'teams-integration': {'url': 'lp:wordpress-teams-integration'},
+    'xubuntu-team-members': {'url': 'lp:~canonical-sysadmins/wordpress/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': 'lp:ubuntu-community-webthemes/light-wordpress-theme'},
+    'mscom': {'url': 'lp:~canonical-sysadmins/wordpress/wp-theme-mscom'},
+    'twentyeleven': {'url': 'lp:~canonical-sysadmins/wordpress/wp-theme-twentyeleven'},
+    'ubuntu-cloud-website': {'url': 'lp:ubuntu-cloud-website'},
+    'ubuntu-community': {'url': 'lp:~canonical-sysadmins/wordpress/wp-theme-ubuntu-community'},
+    'ubuntu-community-wordpress-theme': {'url': 'lp:ubuntu-community-wordpress-theme'},
+    'ubuntu-fi-new': {'url': 'lp:~canonical-sysadmins/wordpress/wp-theme-ubuntu-fi'},
+    'ubuntu-light': {'url': 'lp:~canonical-sysadmins/wordpress/wp-theme-ubuntu-light'},
+    'ubuntustudio-wp': {'url': 'lp:~canonical-sysadmins/wordpress/wp-theme-ubuntustudio-wp'},
+    'wordpress_launchpad': {'url': 'lp:~canonical-sysadmins/wordpress/wp-theme-launchpad'},
+    'xubuntu-theme': {'url': 'lp:~canonical-sysadmins/wordpress/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.PIPE)
+
+
+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.PIPE)
+
+
+if __name__ == '__main__':
+    get_plugins(zip_plugins_to_get, branch_plugins_to_get)
+    get_themes(branch_themes_to_get)
diff --git a/files/_add_option.php b/files/_add_option.php
new file mode 100644
index 0000000..cbfe8a8
--- /dev/null
+++ b/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/files/_enable_plugin.php b/files/_enable_plugin.php
new file mode 100644
index 0000000..11ed3e7
--- /dev/null
+++ b/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/files/_get_option.php b/files/_get_option.php
new file mode 100644
index 0000000..4168301
--- /dev/null
+++ b/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/files/docker-entrypoint.sh b/files/docker-entrypoint.sh
new file mode 100644
index 0000000..136a0d5
--- /dev/null
+++ b/files/docker-entrypoint.sh
@@ -0,0 +1,16 @@
+#!/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}%%%/$(pwgen 64 1)/" /var/www/html/wp-info.php
+done
+
+nohup bash -c "/srv/wordpress-helpers/plugin_handler.py &"
+
+exec "$@"
diff --git a/files/docker-php.conf b/files/docker-php.conf
new file mode 100644
index 0000000..f32d064
--- /dev/null
+++ b/files/docker-php.conf
@@ -0,0 +1,22 @@
+PassEnv SWIFT_URL
+
+<FilesMatch \.php$>
+	SetHandler application/x-httpd-php
+</FilesMatch>
+
+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>
+
+ProxyPass /wp-content/uploads/ ${SWIFT_URL}
+ProxyPassReverse /wp-content/uploads/ ${SWIFT_URL}
diff --git a/files/plugin_handler.py b/files/plugin_handler.py
new file mode 100644
index 0000000..eb9172a
--- /dev/null
+++ b/files/plugin_handler.py
@@ -0,0 +1,153 @@
+#!/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 bits
+        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/files/wp-config.php b/files/wp-config.php
new file mode 100644
index 0000000..158b0db
--- /dev/null
+++ b/files/wp-config.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.
+#
+
+/* 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,
+ * this causes our kubernetes pods to timeout during initial configuration,
+ * preventing the site from ever becoming available.
+ * */
+define( 'WP_HTTP_BLOCK_EXTERNAL', true );
+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/files/wp-info.php b/files/wp-info.php
new file mode 100644
index 0000000..5a891a2
--- /dev/null
+++ b/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/tests/unit/requirements.txt b/tests/unit/requirements.txt
index 2d27572..95d1f99 100644
--- a/tests/unit/requirements.txt
+++ b/tests/unit/requirements.txt
@@ -1,3 +1,4 @@
+<<<<<<< tests/unit/requirements.txt
 requests
 charmhelpers
 charms.reactive
@@ -5,3 +6,8 @@ freezegun
 mock
 pytest
 pytest-cov
+=======
+pytest
+pytest-cov
+PyYAML
+>>>>>>> tests/unit/requirements.txt
diff --git a/tests/unit/test_plugin_hander.py b/tests/unit/test_plugin_hander.py
new file mode 100644
index 0000000..86f353b
--- /dev/null
+++ b/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/tox.ini b/tox.ini
index 9427268..84b02c8 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,6 +1,10 @@
 [tox]
 skipsdist=True
+<<<<<<< tox.ini
 envlist = unit, functional
+=======
+envlist = build
+>>>>>>> tox.ini
 skip_missing_interpreters = True
 
 [testenv]
@@ -8,16 +12,38 @@ basepython = python3
 setenv =
   PYTHONPATH = .
 
+<<<<<<< tox.ini
 [testenv:unit]
 commands =
     pytest --ignore mod --ignore {toxinidir}/tests/functional \
       {posargs:-v  --cov=src --cov-report=term-missing --cov-branch}
 deps = -r{toxinidir}/tests/unit/requirements.txt
        -r{toxinidir}/requirements.txt
+=======
+[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
+>>>>>>> tox.ini
 setenv =
   PYTHONPATH={toxinidir}/lib
   TZ=UTC
 
+<<<<<<< tox.ini
 [testenv:functional]
 passenv =
   HOME
@@ -36,10 +62,16 @@ deps = black
 commands = flake8 src/ tests/
 deps = flake8
 
+=======
+>>>>>>> tox.ini
 [flake8]
 exclude =
     .git,
     __pycache__,
     .tox,
+<<<<<<< tox.ini
+=======
+    files/
+>>>>>>> tox.ini
 max-line-length = 120
 max-complexity = 10

References