From 29c31421748f9974ca70de0cc4b612511e66690f Mon Sep 17 00:00:00 2001
From: Maarten de Waard <maarten@greenhost.nl>
Date: Tue, 9 Jun 2020 10:50:30 +0200
Subject: [PATCH] write backups to remote storage

---
 Chart.yaml                                    |  2 +-
 RELEASING.md                                  |  6 +-
 templates/secrets.yaml                        |  4 ++
 templates/statefulset.yaml                    | 70 ++++++++-----------
 values-local.yaml.example                     | 23 +++---
 values.yaml                                   | 40 +++--------
 wp-cli-docker/Dockerfile                      | 13 +++-
 .../roles/wordpress-backup/tasks/main.yml     | 17 +++--
 wp-cli-docker/scripts/backup.sh               |  6 +-
 9 files changed, 82 insertions(+), 99 deletions(-)

diff --git a/Chart.yaml b/Chart.yaml
index b07f4f1..5778010 100644
--- a/Chart.yaml
+++ b/Chart.yaml
@@ -4,5 +4,5 @@ description: WordPress with a replicated MariaDB backend
 name: wordpress
 # Please only change the chart version as part of the release procedure: see
 # RELEASING.md
-version: 0.1.1
+version: 0.1.0
 icon: https://make.wordpress.org/design/files/2016/09/WordPress-logotype-wmark.png
diff --git a/RELEASING.md b/RELEASING.md
index 46da487..8438232 100644
--- a/RELEASING.md
+++ b/RELEASING.md
@@ -4,9 +4,9 @@ When releasing a new version of the wordpress-helm chart, please remember to do
 the following:
 * change the chart version in `Chart.yaml`;
 * change the default `image.tag` and `initImage.tag` in `values.yaml` to the new
-  version (e.g., "0.1.2");
-* create a git tag for the new version (e.g., "0.1.2") and push it to Gitlab
+  version (e.g., "0.1.0");
+* create a git tag for the new version (e.g., "0.1.0") and push it to Gitlab
   (any branch will do); the CI will create and push docker images tagged by that
   same version string.
   (You can push all git tags using `git push --tags`, or this specific one using
-  `git push origin 0.1.2`.)
+  `git push origin 0.1.0`.)
diff --git a/templates/secrets.yaml b/templates/secrets.yaml
index ff5625f..d124688 100644
--- a/templates/secrets.yaml
+++ b/templates/secrets.yaml
@@ -10,3 +10,7 @@ metadata:
 type: Opaque
 data:
   secret-vars.yaml: {{ tpl .Values.ansibleSecrets . | b64enc }}
+  {{- if .Values.backup.sshPrivateKey }}
+  ssh-private-key: {{ .Values.backup.sshPrivateKey | b64enc }}
+  ssh-known-hosts: {{ .Values.backup.sshKnownHosts | b64enc }}
+  {{- end }}
diff --git a/templates/statefulset.yaml b/templates/statefulset.yaml
index 2967e4a..3976adf 100644
--- a/templates/statefulset.yaml
+++ b/templates/statefulset.yaml
@@ -22,9 +22,6 @@ spec:
         # gets re-run.
         checksum/config: {{ include (print $.Template.BasePath "/ansible-vars.yaml") . | sha256sum }}
         checksum/config: {{ include (print $.Template.BasePath "/secrets.yaml") . | sha256sum }}
-        {{- if .Values.podAnnotations }}
-        {{- toYaml .Values.podAnnotations | nindent 8 }}
-        {{- end }}
     spec:
       imagePullSecrets:
         - name: {{ .Values.initImage.imagePullSecretName }}
@@ -62,9 +59,6 @@ spec:
               value: {{ .Values.database.db.name }}
             - name: WORDPRESS_TABLE_PREFIX
               value: {{ .Values.wordPressTablePrefix }}
-            # TODO: Remove this debug
-            - name: WORDPRESS_DEBUG
-              value: "true"
       containers:
         - name: {{ .Chart.Name }}
           image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
@@ -74,28 +68,11 @@ spec:
               containerPort: 80
             - name: https
               containerPort: 443
-          {{- if .Values.livenessProbe.enabled }}
-          livenessProbe:
-            httpGet:
-              path: {{ .Values.wordpress.site.probe_path }}
-              port: http
-            initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds }}
-            periodSeconds: {{ .Values.livenessProbe.periodSeconds }}
-            timeoutSeconds: {{ .Values.livenessProbe.timeoutSeconds }}
-            successThreshold: {{ .Values.livenessProbe.successThreshold }}
-            failureThreshold: {{ .Values.livenessProbe.failureThreshold }}
-          {{- end }}
-          {{- if .Values.readinessProbe.enabled }}
-          readinessProbe:
-            httpGet:
-              path: {{ .Values.wordpress.site.probe_path }}
-              port: http
-            initialDelaySeconds: {{ .Values.readinessProbe.initialDelaySeconds }}
-            periodSeconds: {{ .Values.readinessProbe.periodSeconds }}
-            timeoutSeconds: {{ .Values.readinessProbe.timeoutSeconds }}
-            successThreshold: {{ .Values.readinessProbe.successThreshold }}
-            failureThreshold: {{ .Values.readinessProbe.failureThreshold }}
-          {{- end }}
+          # livenessProbe:
+          #   httpGet:
+          #     path: /wp-login.php
+          #     port: http
+          #     timeoutSeconds: 2
           env:
             - name: WORDPRESS_DB_HOST
               value: {{ .Release.Name }}-database
@@ -110,9 +87,6 @@ spec:
               value: {{ .Values.database.db.name }}
             - name: WORDPRESS_TABLE_PREFIX
               value: {{ .Values.wordPressTablePrefix }}
-            # TODO: Remove this debug
-            - name: WORDPRESS_DEBUG
-              value: "true"
           # readinessProbe:
           #   httpGet:
           #     path: /
@@ -152,8 +126,9 @@ spec:
               value: {{ .Values.database.db.name }}
             - name: WORDPRESS_TABLE_PREFIX
               value: {{ .Values.wordPressTablePrefix }}
-            - name: BACKUP_NAME
-              value: {{ .Release.Name }}
+            - name: BACKUP_INTERVAL_SECONDS
+              # A day's worth of seconds.
+              value: "86400"
           volumeMounts:
             - name: {{ include "wordpress.name" . }}-wp-storage
               mountPath: /var/www/html
@@ -166,8 +141,13 @@ spec:
               subPath: main.yml
             - name: ansible-secrets
               mountPath: /var/local/ansible/secrets
-            - name: {{ include "wordpress.name" . }}-backups
-              mountPath: /mnt/backups
+            {{- if .Values.backup.sshPrivateKey }}
+            - name: ssh-private-key
+              mountPath: /var/local/ssh-private-key
+            - name: ssh-known-hosts
+              mountPath: /etc/ssh/ssh_known_hosts
+              subPath: ssh_known_hosts
+            {{- end }}
         {{- end }}
     {{- with .Values.nodeSelector }}
       nodeSelector:
@@ -196,18 +176,26 @@ spec:
         - name: ansible-secrets
           secret:
             secretName: {{ include "wordpress.fullname" . }}-ansible-secrets
-            # TODO: this might not be necessary
             items:
               - key: secret-vars.yaml
                 path: secret-vars.yaml
+        {{- if .Values.backup.sshPrivateKey }}
+        - name: ssh-private-key
+          secret:
+            secretName: {{ include "wordpress.fullname" . }}-ansible-secrets
+            items:
+              - key: ssh-private-key
+                path: ssh-private-key
+        - name: ssh-known-hosts
+          secret:
+            secretName: {{ include "wordpress.fullname" . }}-ansible-secrets
+            items:
+              - key: ssh-known-hosts
+                path: ssh_known_hosts
+        {{- end }}
         - name: ansible-vars
           configMap:
             name: {{ include "wordpress.fullname" . }}-ansible-vars
         - name: htuploads
           configMap:
             name: {{ include "wordpress.fullname" . }}-htuploads
-        {{- if .Values.backup.enabled }}
-        - name: {{ include "wordpress.name" . }}-backups
-          persistentVolumeClaim:
-            claimName: {{ .Values.backup.pvcName }}
-        {{- end }}
diff --git a/values-local.yaml.example b/values-local.yaml.example
index 9277203..924aac2 100644
--- a/values-local.yaml.example
+++ b/values-local.yaml.example
@@ -28,8 +28,7 @@ wordpress:
     theme: twentynineteen
     # NOTE: Make sure you use underscore and that the localisation is in full caps
     locale: en_US
-    # NOTE: Optionally set a Wordpress version number to override the default version: in values.yaml
-    # version: LOCAL-WORDPRESS-VERSION-NUMBER-OR-DELETE-THIS-LINE
+    version: 5.2.3
     # NOTE: This is the URL that points to your WordPress installation. If this
     # URL is set incorrectly your site will most likely not work. You can not
     # change it after you have run helm install once because WordPress saves the
@@ -43,10 +42,6 @@ wordpress:
     alt_enabled: false
     # alt_config: PATH-SETTING-IN-OPTIONS-TABLE
     # alt_path: SOME-LOGIN-PATH
-    # # Path used by the liveness and readiness probes to see if the site runs
-    # # correctly. Defaults to `/wp-login.php`. Be sure to make this the same as
-    # # alt_path if you use it!
-    # probe_path: /wp-login.php
   wp_content:
     # The directory to mount the files placed in wp-content. You shouldn't have to
     # change this.
@@ -125,12 +120,20 @@ database:
 
 # This will add a cronjob that performs a daily backup of the wordpress
 # database, copying an sql file created by `wp db export` to the given PVC.
-# The persistent volume is not created by this chart, because we don't want it
-# to be managed by helm, so you will need to create it via some other means.
 # backup:
 #   enabled: true
-#   pvcName: wordpress-backups
-
+#   # The target location of the backup. This can be a local directory (not
+#   # advisable) or a remote directory reachable over SSH. backup command uses
+#   # this value as the second argument for `rsync`
+#   target: <username@server.example.org:backup-dir/>
+#   # If `backup.target` is an SSH address, use this private key:
+#   sshPrivateKey: |
+#     -----BEGIN OPENSSH PRIVATE KEY-----
+#     ...
+#     -----END OPENSSH PRIVATE KEY-----
+#   # Mounted to /etc/ssh/known_hosts
+#   sshKnownHosts: |
+#     hostname keytype key
 # It's advisable to set resource limits to prevent your K8s cluster from
 # crashing
 # resources:
diff --git a/values.yaml b/values.yaml
index a54ce24..c1fd126 100644
--- a/values.yaml
+++ b/values.yaml
@@ -87,17 +87,14 @@ wordpress:
     theme_fallback: twentytwenty
     # NOTE: Make sure you use underscore and that the localisation is in full caps
     locale: en_US
-    version: 5.4.1
+    version: 5.2.3
     url: "http://localhost"
     title: "Wordpress Helm"
     ## If including a plugin to alias wp login then set an alt_path and set the alt_config 
     # NOTE: A value for alt_enabled must be set. Select either true or false
-    alt_enabled: false
+    alt_enabled: false 
     # alt_config: PATH-SETTING-IN-OPTIONS-TABLE
     # alt_path: SOME-LOGIN-PATH
-    # Path used by the liveness and readiness probes to see if the site runs
-    # correctly. Defaults to `/wp-login.php`
-    probe_path: /wp-login.php
   wp_content:
     ## The directory to mount the files placed in wp-content. You shouldn't have to
     ## change this.
@@ -169,14 +166,15 @@ ansibleVars:
 wpSalts: {}
 
 image:
-  repository: open.greenhost.net:4567/open/wordpress-helm/wordpress
-  tag: 0.1.1
+  repository: docker.greenhost.net/open/wordpress-helm/wordpress
+  tag: 0.1.0
   pullPolicy: Always
 
 initImage:
-  repository: open.greenhost.net:4567/open/wordpress-helm/wordpress-cli-ansible
-  tag: 0.1.1
+  repository: docker.greenhost.net/open/wordpress-helm/wordpress-cli-ansible
+  tag: 0.1.0
   pullPolicy: Always
+  imagePullSecretName: greenhost-registry-pull
 
 ingress:
   enabled: false
@@ -191,9 +189,6 @@ nodeSelector: {}
 tolerations: []
 
 affinity: {}
-
-podAnnotations: {}
-
 database:
   db:
     user: wordpress
@@ -266,6 +261,8 @@ backup:
 # completely stored as a b64encoded secret in Kubernetes. If you're not a
 # developer, never change this variable, only change the variables it points to.
 ansibleSecrets: |
+  BACKUP_NAME: {{ .Release.Name }}
+  BACKUP_TARGET: {{ .Values.backup.target }}
   DB_HOST: {{ .Release.Name }}-database
   DB_NAME: {{ .Values.database.db.name }}
   DB_PASS: {{ .Values.database.db.password }}
@@ -336,22 +333,3 @@ ansibleSecrets: |
     SECURE_AUTH_SALT: {{ .Values.wpSalts.SECURE_AUTH_SALT | default ( randAlphaNum 32) }}
     WP_CACHE_KEY_SALT: {{ .Values.wpSalts.WP_CACHE_KEY_SALT | default ( randAlphaNum 32) }}
     WP_CRON_CONTROL_SECRET: {{ .Values.wpSalts.WP_CRON_CONTROL_SECRET | default ( randAlphaNum 32 ) }}
-
-## Liveness and readiness probe values
-## Ref: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#container-probes
-##
-livenessProbe:
-  enabled: true
-  initialDelaySeconds: 60
-  periodSeconds: 15
-  timeoutSeconds: 5
-  failureThreshold: 3
-  successThreshold: 1
-
-readinessProbe:
-  enabled: true
-  initialDelaySeconds: 10
-  periodSeconds: 15
-  timeoutSeconds: 5
-  failureThreshold: 3
-  successThreshold: 1
diff --git a/wp-cli-docker/Dockerfile b/wp-cli-docker/Dockerfile
index ea50924..3d136ee 100644
--- a/wp-cli-docker/Dockerfile
+++ b/wp-cli-docker/Dockerfile
@@ -1,11 +1,20 @@
-FROM wordpress:cli-2.4-php7.3
+FROM wordpress:cli-2.1.0-php7.3
 
 USER root
 
-RUN apk add ansible git rsync
+RUN apk add ansible git rsync openssh
 ADD . /var/local/ansible
 ENV ANSIBLE_CONFIG /var/local/ansible/ansible.cfg
 
+# This is the homedir of user 33 in the alpine container and needs to exist and
+# be writable for ssh to work.
+RUN mkdir -p /etc/X11/fs && chown -R 33:33 /etc/X11/fs
+
+# SSH Identity file location for backup playbook. It's in /etc/X11/fs which is
+# the default home directory for the user with ID 33, which is the UID of the
+# www-data user in the Debian container that runs the site...
+RUN mkdir -p /etc/ssh/ && echo "IdentityFile /var/local/ssh-private-key/ssh-private-key" > /etc/ssh/ssh_config
+
 # Chown the files to the Debian www-data user, because that's the only WP
 # container that runs Apache too.
 RUN chown -R 33:33 /var/local/ansible; \
diff --git a/wp-cli-docker/roles/wordpress-backup/tasks/main.yml b/wp-cli-docker/roles/wordpress-backup/tasks/main.yml
index 0793499..9ffe7a5 100644
--- a/wp-cli-docker/roles/wordpress-backup/tasks/main.yml
+++ b/wp-cli-docker/roles/wordpress-backup/tasks/main.yml
@@ -7,16 +7,19 @@
   changed_when: false
 
 - block:
-  - name: Create backups directory
-    file:
-      path: "{{ backup_dir }}"
+  - name: Create temporary backups directory
+    tempfile:
       state: directory
+      suffix: backup
+    register: backup_dir
   - name: Export WordPress database to file
     shell:
-      wp {{ cli_args }} db export "{{ backup_dir }}/{{ backup_filename }}"
+      wp {{ cli_args }} db export "{{ backup_dir.path }}/{{ backup_filename }}"
     # Never overwrite an existing file.
     args:
-      creates: "{{ backup_dir }}/{{ backup_filename }}"
+      creates: "{{ backup_dir.path }}/{{ backup_filename }}"
+  - name: Copy export to backup location
+    shell:
+      rsync -a "{{ backup_dir.path }}/{{ backup_filename }}" "{{ BACKUP_TARGET }}"
   vars:
-    backup_dir: "/mnt/backups/wp-db/{{ lookup('env', 'BACKUP_NAME') }}"
-    backup_filename: "wp-db-{{ lookup('env', 'BACKUP_NAME') }}-{{ date.stdout }}.sql"
+    backup_filename: "wp-db-{{ BACKUP_NAME }}-{{ date.stdout }}.sql"
diff --git a/wp-cli-docker/scripts/backup.sh b/wp-cli-docker/scripts/backup.sh
index 078335f..341c321 100755
--- a/wp-cli-docker/scripts/backup.sh
+++ b/wp-cli-docker/scripts/backup.sh
@@ -1,14 +1,12 @@
 #!/bin/bash
 
 backupCommand="ansible-playbook backup.yml -e @secrets/secret-vars.yaml"
-# A day's worth of seconds.
-interval=86400
 
 while true
 do
     date
-    echo "Waiting for $interval seconds before starting next backup."
-    sleep $interval
+    echo "Waiting for $BACKUP_INTERVAL_SECONDS seconds before starting next backup."
+    sleep $BACKUP_INTERVAL_SECONDS
     $backupCommand
     exitCode=$?
     if [ $exitCode -ne 0 ]
-- 
GitLab