diff --git a/.circleci/config.yml b/.circleci/config.yml index 6a0e59010..10a07b892 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -9,6 +9,8 @@ jobs: - run: phpcs --standard=phpcs.xml -s + - run: helm unittest ./chart + build: docker: - image: wunderio/silta-circleci:v0.1 @@ -91,14 +93,8 @@ jobs: --set nginx.image=$DOCKER_REPO_HOST/$DOCKER_REPO_PROJ/$CIRCLE_PROJECT_REPONAME-nginx:$CIRCLE_SHA1 \ --set mariadb.rootUser.password=$DB_ROOT_PASS \ --set mariadb.db.password=$DB_USER_PASS \ - --namespace=$CIRCLE_PROJECT_REPONAME - - # Wait for the kubernetes deployment to be complete. - - run: kubectl rollout status deployment/$RELEASE_NAME-drupal --namespace=$CIRCLE_PROJECT_REPONAME --watch - - # TODO: output the right domain. - - run: | - echo "Deployed $RELEASE_NAME successfully, your site is available here: http://$DOMAIN" + --namespace=$CIRCLE_PROJECT_REPONAME \ + --wait workflows: version: 2 diff --git a/chart/Chart.yaml b/chart/Chart.yaml index b08dced65..fd2b02ecf 100644 --- a/chart/Chart.yaml +++ b/chart/Chart.yaml @@ -1,3 +1,3 @@ name: drupal -version: 0.1.0 +version: 0.1.1 diff --git a/chart/templates/NOTES.txt b/chart/templates/NOTES.txt new file mode 100644 index 000000000..45a495ceb --- /dev/null +++ b/chart/templates/NOTES.txt @@ -0,0 +1 @@ +Deployed {{ .Release.Name }} successfully, your site is available here: http://{{- template "drupal.domain" . }} \ No newline at end of file diff --git a/chart/templates/_helpers.tpl b/chart/templates/_helpers.tpl index 9e69a3c84..035abe462 100644 --- a/chart/templates/_helpers.tpl +++ b/chart/templates/_helpers.tpl @@ -3,6 +3,60 @@ app: {{ printf "%s-%s" .Release.Name .Chart.Name | trunc 63 }} version: {{ .Chart.Version }} release: {{ .Release.Name }} {{- end }} -{{- define "drupal.full_name" -}} -{{- printf "%s-%s" .Release.Name .Chart.Name | trunc 63 -}} + +{{- define "drupal.domain" -}} +{{ regexReplaceAll "[^[:alnum:]]" .Values.branchname "-" | lower }}.{{ .Release.Namespace }}.{{ .Values.clusterDomain }} {{- end -}} + +{{- define "drupal.php-container" }} +image: {{ .Values.drupal.image | quote }} +env: {{ include "drupal.env" . }} +ports: + - containerPort: 9000 + name: drupal +volumeMounts: + - name: drupal-public-files + mountPath: /var/www/html/web/sites/default/files + {{- if .Values.drupal.privateFiles.enabled }} + - name: drupal-private-files + mountPath: /var/www/html/private + {{- end }} +{{- end }} + +{{- define "drupal.volumes" }} +- name: drupal-public-files + persistentVolumeClaim: + claimName: {{ .Release.Name }}-public-files +{{- if .Values.drupal.privateFiles.enabled }} +- name: drupal-private-files + persistentVolumeClaim: + claimName: {{ .Release.Name }}-private-files +{{- end }} +{{- end }} + +{{- define "drupal.env" }} +- name: DB_USER + value: "{{ .Values.mariadb.db.user }}" +- name: DB_NAME + value: "{{ .Values.mariadb.db.name }}" +- name: DB_HOST + value: {{ .Release.Name }}-mariadb +- name: DB_PASS + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-mariadb + key: mariadb-password +- name: HASH_SALT + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-secrets-drupal + key: hashsalt +{{- range $key, $val := .Values.drupal.env }} +- name: {{ $key }} + value: {{ $val | quote }} +{{- end }} +{{- if .Values.drupal.privateFiles.enabled }} +- name: PRIVATE_FILES_PATH + value: '/var/www/html/private' +{{- end }} +{{- end }} \ No newline at end of file diff --git a/chart/templates/cron.yaml b/chart/templates/cron.yaml deleted file mode 100644 index 6c68e49c8..000000000 --- a/chart/templates/cron.yaml +++ /dev/null @@ -1,33 +0,0 @@ -apiVersion: batch/v1beta1 -kind: CronJob -metadata: - name: {{ .Release.Name }}-cron -spec: - schedule: "0 * * * *" - concurrencyPolicy: Forbid - backOffLimit: 3 - jobTemplate: - spec: - template: - spec: - containers: - - name: drupal-cron - image: "{{ .Values.drupal.image }}" - command: - - drush - - cron - env: - - name: PHP_FPM_CLEAR_ENV - value: "no" - - name: DB_USER - value: "{{ .Values.mariadb.db.user }}" - - name: DB_NAME - value: "{{ .Values.mariadb.db.name }}" - - name: DB_HOST - value: "{{ .Release.Name }}-mariadb" - - name: DB_PASS - valueFrom: - secretKeyRef: - name: "{{ .Release.Name }}-mariadb" - key: mariadb-password - restartPolicy: OnFailure diff --git a/chart/templates/drupal-cron.yaml b/chart/templates/drupal-cron.yaml new file mode 100644 index 000000000..14de9d2e9 --- /dev/null +++ b/chart/templates/drupal-cron.yaml @@ -0,0 +1,21 @@ +apiVersion: batch/v1beta1 +kind: CronJob +metadata: + name: {{ .Release.Name }}-cron +spec: + schedule: "0 * * * *" + concurrencyPolicy: Forbid + backOffLimit: 3 + jobTemplate: + spec: + template: + spec: + containers: + - name: drupal-cron + {{ include "drupal.php-container" . | indent 12 }} + command: + - drush + - cron + restartPolicy: OnFailure + volumes: + {{ include "drupal.volumes" . | indent 12 }} diff --git a/chart/templates/drupal-deployment.yaml b/chart/templates/drupal-deployment.yaml new file mode 100644 index 000000000..d5924d99b --- /dev/null +++ b/chart/templates/drupal-deployment.yaml @@ -0,0 +1,51 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Release.Name }}-drupal + labels: + app: drupal + release: {{ .Release.Name }} +spec: + selector: + matchLabels: + app: drupal + release: {{ .Release.Name }} + strategy: + type: Recreate + template: + metadata: + labels: + app: drupal + release: {{ .Release.Name }} + spec: + containers: + # php-fpm container. + - name: drupal + {{ include "drupal.php-container" . | indent 8}} + + # Nginx container + - name: nginx + image: {{ .Values.nginx.image | quote }} + resources: + requests: + cpu: "50m" + env: + - name: NGINX_STATIC_CONTENT_OPEN_FILE_CACHE + value: "off" + - name: NGINX_ERROR_LOG_LEVEL + value: debug + - name: NGINX_BACKEND_HOST + value: localhost + - name: NGINX_SERVER_ROOT + value: /var/www/html/web + ports: + - containerPort: 80 + name: drupal + volumeMounts: + - name: drupal-public-files + mountPath: /var/www/html/web/sites/default/files + + imagePullSecrets: + - name: gcr + volumes: + {{ include "drupal.volumes" . | indent 8}} diff --git a/chart/templates/drupal-secret.yaml b/chart/templates/drupal-secret.yaml new file mode 100644 index 000000000..564f59d37 --- /dev/null +++ b/chart/templates/drupal-secret.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Release.Name }}-secrets-drupal + labels: + release: "{{ .Release.Name }}" +type: Opaque +data: + {{- if .Values.drupal.hashsalt }} + hashsalt: "{{ .Values.drupal.hashsalt | b64enc }}" + {{ else }} + hashsalt: {{ "{{ randAlphaNum 10 }}" | b64enc }} + {{- end }} \ No newline at end of file diff --git a/chart/templates/drupal-service.yaml b/chart/templates/drupal-service.yaml new file mode 100644 index 000000000..dc4585f9c --- /dev/null +++ b/chart/templates/drupal-service.yaml @@ -0,0 +1,23 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }}-drupal + labels: + app: drupal + release: {{ .Release.Name }} + annotations: + getambassador.io/config: | + --- + apiVersion: ambassador/v0 + kind: Mapping + name: {{ .Release.Name }}-drupal + host: {{ template "drupal.domain" . }} + prefix: / + service: {{ .Release.Name }}-drupal.{{ .Release.Namespace }}.svc.cluster.local:80 +spec: + type: NodePort + ports: + - port: 80 + selector: + app: drupal + release: {{ .Release.Name }} \ No newline at end of file diff --git a/chart/templates/drupal-volumes.yaml b/chart/templates/drupal-volumes.yaml new file mode 100644 index 000000000..71fccd48b --- /dev/null +++ b/chart/templates/drupal-volumes.yaml @@ -0,0 +1,31 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ .Release.Name }}-public-files + labels: + app: drupal + release: {{ .Release.Name }} +spec: + storageClassName: nfs + accessModes: + - ReadWriteMany + resources: + requests: + storage: 1Gi +--- +{{- if .Values.drupal.privateFiles.enabled }} +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ .Release.Name }}-private-files + labels: + app: drupal + release: {{ .Release.Name }} +spec: + storageClassName: nfs + accessModes: + - ReadWriteMany + resources: + requests: + storage: 1Gi +{{- end }} \ No newline at end of file diff --git a/chart/templates/drupal.yaml b/chart/templates/drupal.yaml deleted file mode 100644 index 4d00ff62b..000000000 --- a/chart/templates/drupal.yaml +++ /dev/null @@ -1,92 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: {{ .Release.Name }}-drupal - labels: - app: drupal - release: {{ .Release.Name }} -spec: - type: NodePort - ports: - - port: 80 - selector: - app: drupal - release: {{ .Release.Name }} ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: {{ .Release.Name }}-drupal - labels: - app: drupal - release: {{ .Release.Name }} -spec: - selector: - matchLabels: - app: drupal - release: {{ .Release.Name }} - strategy: - type: Recreate - template: - metadata: - labels: - app: drupal - release: {{ .Release.Name }} - spec: - containers: - - image: {{ .Values.nginx.image | quote }} - name: nginx - resources: - requests: - cpu: "50m" - env: - - name: NGINX_STATIC_CONTENT_OPEN_FILE_CACHE - value: "off" - - name: NGINX_ERROR_LOG_LEVEL - value: debug - - name: NGINX_BACKEND_HOST - value: localhost - - name: NGINX_SERVER_ROOT - value: /var/www/html/web - ports: - - containerPort: 80 - name: drupal - volumeMounts: - - name: drupal-files-volume - mountPath: /var/www/html/web/sites/default/files - - - image: {{ .Values.drupal.image | quote }} - name: drupal - env: - - name: DB_USER - value: "{{ .Values.mariadb.db.user }}" - - name: DB_NAME - value: "{{ .Values.mariadb.db.name }}" - - name: DB_HOST - value: {{ .Release.Name }}-mariadb - - name: DB_PASS - valueFrom: - secretKeyRef: - name: {{ .Release.Name }}-mariadb - key: mariadb-password - - name: HASH_SALT - valueFrom: - secretKeyRef: - name: {{ .Release.Name }}-secrets-drupal - key: hashsalt - {{- range $key, $val := .Values.drupal.env }} - - name: {{ $key }} - value: {{ $val | quote }} - {{- end }} - ports: - - containerPort: 9000 - name: drupal - volumeMounts: - - name: drupal-files-volume - mountPath: /var/www/html/web/sites/default/files - imagePullSecrets: - - name: gcr - volumes: - - name: drupal-files-volume - persistentVolumeClaim: - claimName: {{ .Release.Name }}-public-files diff --git a/chart/templates/ingress.yaml b/chart/templates/ingress.yaml deleted file mode 100644 index fde073c5c..000000000 --- a/chart/templates/ingress.yaml +++ /dev/null @@ -1,35 +0,0 @@ -apiVersion: extensions/v1beta1 -kind: Ingress -metadata: - name: {{ .Release.Name }} - labels: - release: "{{ .Release.Name }}" - annotations: - {{- range $key, $value := .Values.ingress.annotations }} - {{ $key }}: {{ $value | quote }} - {{- end }} - ingress.kubernetes.io/rewrite-target: / - kubernetes.io/ingress.class: "gce" -spec: - backend: - serviceName: {{ .Release.Name }}-drupal - servicePort: 80 - rules: - - host: {{ .Values.ingress.hostname }} - http: - paths: - - path: / - backend: - serviceName: {{ .Release.Name }}-drupal - servicePort: 80 - - host: {{ regexReplaceAll "[^[:alnum:]]" .Values.branchname "-" | lower }}.{{ .Release.Namespace }}.silta.wdr.io - http: - paths: - - path: / - backend: - serviceName: {{ .Release.Name }}-drupal - servicePort: 80 -{{- if .Values.ingress.tls }} - tls: -{{ toYaml .Values.ingress.tls | indent 4 }} -{{- end -}} \ No newline at end of file diff --git a/chart/templates/postinstall.yaml b/chart/templates/postinstall.yaml index 359660a3a..df33ed9e4 100644 --- a/chart/templates/postinstall.yaml +++ b/chart/templates/postinstall.yaml @@ -17,42 +17,11 @@ spec: restartPolicy: Never containers: - name: postinstall - image: {{ .Values.drupal.image | quote }} + {{ include "drupal.php-container" . | indent 8 }} command: ["/bin/sh", "-c"] args: - - cd web; - bootstrapped=$(drush status --field=bootstrap); - if [[ $bootstrapped = *'Success'* ]]; then drush updatedb -n; drush config-import -n; drush entity-updates -n; - else drush site-install -n config_installer; conf_count=$(ls ../config/sync/ | wc -l); if [ $conf_count -lt 2 ]; then drush site-install minimal -y; fi; fi; - env: - - name: PHP_FPM_CLEAR_ENV - value: "no" - - name: DB_USER - value: "{{ .Values.mariadb.db.user }}" - - name: DB_NAME - value: "{{ .Values.mariadb.db.name }}" - - name: DB_HOST - value: "{{ .Release.Name }}-mariadb" - - name: DB_PASS - valueFrom: - secretKeyRef: - name: "{{ .Release.Name }}-mariadb" - key: mariadb-password - - name: HASH_SALT - valueFrom: - secretKeyRef: - name: {{ .Release.Name }}-secrets-drupal - key: hashsalt - {{- range $key, $val := .Values.drupal.env }} - - name: {{ $key }} - value: {{ $val | quote }} - {{- end }} - volumeMounts: - - name: drupal-files-volume - mountPath: /var/www/html/web/sites/default/files + - {{ .Values.postinstall.commands | quote }} imagePullSecrets: - name: gcr volumes: - - name: drupal-files-volume - persistentVolumeClaim: - claimName: {{ .Release.Name }}-public-files \ No newline at end of file + {{ include "drupal.volumes" . | indent 8 }} \ No newline at end of file diff --git a/chart/templates/postupgrade.yaml b/chart/templates/postupgrade.yaml new file mode 100644 index 000000000..51f77d732 --- /dev/null +++ b/chart/templates/postupgrade.yaml @@ -0,0 +1,27 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: "{{ .Release.Name }}-postupgrade" + labels: + app: postupgrade + release: {{ .Release.Name }} + annotations: + # This is what defines this resource as a hook. Without this line, the + # job is considered part of the release. + "helm.sh/hook": post-upgrade + "helm.sh/hook-weight": "-5" + "helm.sh/hook-delete-policy": hook-succeeded,hook-failed +spec: + template: + spec: + restartPolicy: Never + containers: + - name: postupgrade + {{ include "drupal.php-container" . | indent 8 }} + command: ["/bin/sh", "-c"] + args: + - {{ .Values.postupgrade.commands | quote }} + imagePullSecrets: + - name: gcr + volumes: + {{ include "drupal.volumes" . | indent 8 }} \ No newline at end of file diff --git a/chart/templates/secrets.yml b/chart/templates/secrets.yml deleted file mode 100644 index 7cf10ef18..000000000 --- a/chart/templates/secrets.yml +++ /dev/null @@ -1,9 +0,0 @@ -apiVersion: v1 -kind: Secret -metadata: - name: {{ .Release.Name }}-secrets-drupal - labels: - release: "{{ .Release.Name }}" -type: Opaque -data: - hashsalt: {{ .Values.secrets.hashsalt | b64enc }} diff --git a/chart/templates/volumes.yaml b/chart/templates/volumes.yaml deleted file mode 100644 index 2b23fea6b..000000000 --- a/chart/templates/volumes.yaml +++ /dev/null @@ -1,14 +0,0 @@ -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: {{ .Release.Name }}-public-files - labels: - app: drupal - release: {{ .Release.Name }} -spec: - storageClassName: nfs - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 1Gi \ No newline at end of file diff --git a/chart/tests/drupal_cron_test.yaml b/chart/tests/drupal_cron_test.yaml new file mode 100644 index 000000000..662ea70a4 --- /dev/null +++ b/chart/tests/drupal_cron_test.yaml @@ -0,0 +1,36 @@ +suite: drupal cron jobs +templates: + - drupal-cron.yaml +tests: + - it: is a cron job + asserts: + - isKind: + of: CronJob + + - it: uses the right docker image + set: + drupal.image: 'drupal-12345' + asserts: + - equal: + path: spec.jobTemplate.spec.template.spec.containers[0].image + value: 'drupal-12345' + + - it: sets environment variables correctly + set: + drupal.env: + foo: bar + asserts: + - contains: + path: spec.jobTemplate.spec.template.spec.containers[0].env + content: + name: foo + value: bar + + - it: has public files mounted + asserts: + - contains: + path: spec.jobTemplate.spec.template.spec.volumes + content: + name: "drupal-public-files" + persistentVolumeClaim: + claimName: RELEASE-NAME-public-files \ No newline at end of file diff --git a/chart/tests/drupal_deployment_test.yaml b/chart/tests/drupal_deployment_test.yaml new file mode 100644 index 000000000..e3b329883 --- /dev/null +++ b/chart/tests/drupal_deployment_test.yaml @@ -0,0 +1,31 @@ +suite: drupal deployment +templates: + - drupal-deployment.yaml +tests: + - it: is a deployment + asserts: + - isKind: + of: Deployment + + - it: uses the right docker images + set: + drupal.image: 'drupal-12345' + nginx.image: 'nginx-12345' + asserts: + - equal: + path: spec.template.spec.containers[0].image + value: 'drupal-12345' + - equal: + path: spec.template.spec.containers[1].image + value: 'nginx-12345' + + - it: sets environment variables correctly + set: + drupal.env: + foo: bar + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: foo + value: bar diff --git a/chart/tests/drupal_postinstall_test.yaml b/chart/tests/drupal_postinstall_test.yaml new file mode 100644 index 000000000..d908ac317 --- /dev/null +++ b/chart/tests/drupal_postinstall_test.yaml @@ -0,0 +1,36 @@ +suite: drupal post-install hook +templates: + - postinstall.yaml +tests: + - it: is a helm hook + asserts: + - isKind: + of: Job + + - it: uses the right docker image + set: + drupal.image: 'drupal-12345' + asserts: + - equal: + path: spec.template.spec.containers[0].image + value: 'drupal-12345' + + - it: sets environment variables correctly + set: + drupal.env: + foo: bar + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: foo + value: bar + + - it: has public files mounted + asserts: + - contains: + path: spec.template.spec.volumes + content: + name: "drupal-public-files" + persistentVolumeClaim: + claimName: RELEASE-NAME-public-files \ No newline at end of file diff --git a/chart/tests/private_files_test.yaml b/chart/tests/private_files_test.yaml new file mode 100644 index 000000000..3cc0a2a0c --- /dev/null +++ b/chart/tests/private_files_test.yaml @@ -0,0 +1,63 @@ +suite: drupal private files +templates: + - drupal-deployment.yaml + - drupal-volumes.yaml + - postinstall.yaml + - drupal-cron.yaml +tests: + - it: private files should be disabled by default + asserts: + # No private files volume is mounted. + - notContains: + path: spec.template.spec.volumes + content: + name: "drupal-private-files" + persistentVolumeClaim: + claimName: RELEASE-NAME-private-files + # Only one volume is defined. + - hasDocuments: + count: 1 + template: drupal-volumes.yaml + + - it: private files should work when enabled + set: + drupal.privateFiles.enabled: true + asserts: + # A private files volume is mounted + - contains: + path: spec.template.spec.volumes + content: + name: "drupal-private-files" + persistentVolumeClaim: + claimName: RELEASE-NAME-private-files + + # The environment variable is set + - contains: + path: spec.template.spec.containers[0].env + content: + name: PRIVATE_FILES_PATH + value: '/var/www/html/private' + + # There are two volumes defined. + - template: drupal-volumes.yaml + hasDocuments: + count: 2 + + # Private files are available for cron jobs. + - template: drupal-cron.yaml + contains: + path: spec.jobTemplate.spec.template.spec.volumes + content: + name: "drupal-private-files" + persistentVolumeClaim: + claimName: RELEASE-NAME-private-files + + # Private files are available for in the post-install hooks. + - template: postinstall.yaml + contains: + path: spec.template.spec.volumes + content: + name: "drupal-private-files" + persistentVolumeClaim: + claimName: RELEASE-NAME-private-files + diff --git a/chart/values.yaml b/chart/values.yaml index df2c6c1bc..ea13293f7 100644 --- a/chart/values.yaml +++ b/chart/values.yaml @@ -4,14 +4,18 @@ nginx: image: 'you need to pass in a value for nginx.image to your helm chart' drupal: image: 'you need to pass in a value for drupal.image to your helm chart' + # will be generated random hashsalt if not provided here + # need to be base64 encoded + hashsalt: "template-hash-salt" env: #these are added to drupal environment PHP_FPM_CLEAR_ENV: "no" + # Create a volume for private files. + privateFiles: + enabled: false + mariadb: - master: - persistence: - storageClass: standard replication: enabled: false rootUser: @@ -21,8 +25,20 @@ mariadb: user: drupal password: "{{ randAlphaNum 10 }}" # pass in a value to your helm chart to override this -ingress: - hostname: "silta.wdr.io" +clusterDomain: "silta.wdr.io" +branchname: "default" + +postinstall: + commands: | + cd web; + conf_count=$(ls ../config/sync/ | wc -l); + if [ $conf_count -gt 1 ]; then drush site-install -n config_installer; + else drush site-install minimal -y; + fi; -secrets: - hashsalt: "template-hash-salt" +postupgrade: + commands: | + cd web; + conf_count=$(ls ../config/sync/ | wc -l); + if [ $conf_count -gt 1 ]; then drush updatedb -n; drush config-import -n; drush entity-updates -n; + fi; diff --git a/web/sites/default/settings.php b/web/sites/default/settings.php index e49801eaf..6f5db8540 100644 --- a/web/sites/default/settings.php +++ b/web/sites/default/settings.php @@ -39,6 +39,13 @@ 'node_modules', ]; +/** + * If a volume has been set for private files, tell Drupal about it. + */ +if (getenv('PRIVATE_FILES_PATH')) { + $settings['file_private_path'] = getenv('PRIVATE_FILES_PATH'); +} + /** * Load local development override configuration, if available. *