← Back to team overview

wordpress-charmers team mailing list archive

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

 

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

Commit message:
Adding plugin handling for Akismet and Launchpad OpenID integration

Requested reviews:
  Wordpress Charmers (wordpress-charmers)

For more details, see:
https://code.launchpad.net/~barryprice/charm-k8s-wordpress/+git/wordpress-k8s-image-builder/+merge/380153
-- 
Your team Wordpress Charmers is requested to review the proposed merge of ~barryprice/charm-k8s-wordpress/+git/wordpress-k8s-image-builder:master into ~wordpress-charmers/charm-k8s-wordpress/+git/wordpress-k8s-image-builder:master.
diff --git a/.gitignore b/.gitignore
index e0f7f96..d8aaacd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,6 @@
 files/plugins/
 files/themes/
+.coverage
 .tox/
+files/__pycache__/
+tests/unit/__pycache__/
diff --git a/Dockerfile b/Dockerfile
index 24b6b0b..c866c98 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -19,7 +19,8 @@ RUN echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selectio
 # 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 php libapache2-mod-php php-mysql php-gd curl ssl-cert pwgen \
+    && apt-get install -y apache2 php libapache2-mod-php php-mysql php-gd \
+       curl ssl-cert pwgen python3 python3-yaml php-symfony-yaml \
     && 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 \
@@ -47,13 +48,22 @@ 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 /var/www/html/
-COPY ./files/wp-config.php /var/www/html/
+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
 
diff --git a/Makefile b/Makefile
index 36c7f00..a3cddc9 100644
--- a/Makefile
+++ b/Makefile
@@ -24,12 +24,18 @@ 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
 
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
index 5f67d9d..136a0d5 100644
--- a/files/docker-entrypoint.sh
+++ b/files/docker-entrypoint.sh
@@ -11,4 +11,6 @@ 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/plugin_handler.py b/files/plugin_handler.py
new file mode 100644
index 0000000..e3c8f93
--- /dev/null
+++ b/files/plugin_handler.py
@@ -0,0 +1,117 @@
+#!/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 wait_for_wordpress():
+    url = 'http://localhost'
+    sleep_time = 10
+    success = False
+    while success is not True:
+        try:
+            response = urllib.request.urlopen(url, timeout=sleep_time)
+        except Exception:
+            logging.info('Waiting for Wordpress to accept connections')
+            sleep(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)
+
+    wait_for_wordpress()
+
+    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)
diff --git a/tests/unit/requirements.txt b/tests/unit/requirements.txt
new file mode 100644
index 0000000..092d46a
--- /dev/null
+++ b/tests/unit/requirements.txt
@@ -0,0 +1,3 @@
+pytest
+pytest-cov
+PyYAML
diff --git a/tests/unit/test_plugin_hander.py b/tests/unit/test_plugin_hander.py
new file mode 100644
index 0000000..d72b865
--- /dev/null
+++ b/tests/unit/test_plugin_hander.py
@@ -0,0 +1,19 @@
+import os
+import sys
+import unittest
+from unittest import mock
+
+sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__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 = "canonical-sysadmins=administrator,canonical-website-editors=editor,canonical-website-admins=administrator,launchpad=editor"  # NOQA: E501
+        want = """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";}}"""  # NOQA: E501
+        got = plugin_handler.encode_team_map(given)
+        self.assertTrue(got == want)
diff --git a/tox.ini b/tox.ini
index 4c0b186..c03998c 100644
--- a/tox.ini
+++ b/tox.ini
@@ -22,6 +22,14 @@ 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,

Follow ups