commit b3ba803631d9f54921766aff51f47554154df0da Author: Oly Date: Fri Oct 11 16:45:52 2019 +0100 Stage deploy. diff --git a/maidstone-hackspace-config.edn b/maidstone-hackspace-config.edn new file mode 100644 index 0000000..97dd35d --- /dev/null +++ b/maidstone-hackspace-config.edn @@ -0,0 +1,62 @@ +{:registry "" + :images {:django "registry.gitlab.com/maidstone-hackspace/maidstone-hackspace-website:latest" + :nginx-ingress "nginx/nginx-ingress" + :nginx "nginx:alpine" + :redis "redis:5.0-rc" + :rabbitmq "rabbitmq:management-alpine" + :postgres "postgres"} + :environment "stage" + :vars + {:MY_DOMAIN_NAME "stage.maidstone-hackspace.org.uk" + :COMPOSE_PROJECT_NAME "hackstage" + :GUNICORN "/stage-gunicorn-mhackspace.sh" + :POSTGRES_PASSWORD "mysecretpass" + :POSTGRES_USER "postgresuser" + :DATABASE_URL "postgres://postgresuser:mysecretpass@postgres:5432/postgresuser" + :DJANGO_ADMIN_URL "trustee/" + :DJANGO_SETTINGS_MODULE "config.settings.stage" + :DJANGO_SECRET_KEY "iud%k99yw!e+z+c12uatugbn \"&lsdyd(t_byk9)@dp@lj6*c*n" + :DJANGO_ALLOWED_HOSTS "stage.maidstone-hackspace.org.uk,165.227.230.86" + :DJANGO_DEBUG "False" + :BUCKET_PREFIX_PATH "stage/" + :DJANGO_AWS_ACCESS_KEY_ID "" + :DJANGO_AWS_SECRET_ACCESS_KEY "" + :DJANGO_AWS_STORAGE_BUCKET_NAME "" + :REDIS_URL "redis://redis:6379" + :CELERY_BROKER_URL "redis://redis:6379" + :CELERY_RESULT_BACKEND "redis://redis:6379" + :DJANGO_MAILGUN_API_KEY "" + :DJANGO_SERVER_EMAIL "support@digitaloctave.com" + :MAILGUN_SENDER_DOMAIN "" + :EMAIL_USER "no-reply@digitaloctave.com" + :EMAIL_PASSWORD "5fere5V.9quQe" + :DJANGO_ACCOUNT_ALLOW_REGISTRATION "True" + :COMPRESS_ENABLED "" + :PAYMENT_ENVIRONMENT "sandbox" + :PAYMENT_REDIRECT_URL "https://test.maidstone-hackspace.org.uk" + :BRAINTREE_MERCHANT_ID "b3sdmyczd3fz6b3p" + :BRAINTREE_PUBLIC_KEY "rxb7yffm3tk758rq" + :BRAINTREE_PRIVATE_KEY "62f92d2b00a451c40fdf5fbca54f0421" + :PAYPAL_CLIENT_ID "AaGlNEvd26FiEJiJi53nfpXh19_oKetteV1NGkBi4DDYZSqBexKVgaz9Lp0SI82gYFSAYpsmxO4iDtxU" + :PAYPAL_CLIENT_SECRET "EMcIuDJE_VDNSNZS7C7NLi9DEHaDvVu9jlIYyCCHaLmrLuy_VQ6C0bbcRnyF-7B6CcN__Dn6HqUwsgMG" + :GOCARDLESS_APP_ID "MNHBS3C4X4ZG211SM70WSS7WCN8B3X1KAWZBKV9S8N6KH2RNH6YZ5Z5865RFD3H6" + :GOCARDLESS_APP_SECRET "NE4NWYDQY4FNN1B47VT9SZ318GPQND130DW7QGQ73JMVTZQZHJQNF23ZFNP48GKV" + :GOCARDLESS_ACCESS_TOKEN "CJ7G7V36VAH5KVAHTYXD8VE8M4M0S41EQXH2E1HTGV5AN5TAZBER36ERAF4CG2NR" + ;:GOCARDLESS_ACCESS_TOKEN "sandbox_-ATBL3jeCXroOBM2nhvwjNzr1utQ7UN5esbB4vIc" + :GOCARDLESS_MERCHANT_ID "11QFXD7TTA" + :MATRIX_ROOM "fmCpNwqgIiuwATlcdw:matrix.org" + :MATRIX_USERNAME "mhackspace" + :MATRIX_PASSWORD "AjwsoD952vdOo" + :BUCKET_NAME "mhackspace" + :BUCKET_ACCESS_KEY "UGCVGOUYW2XER4UNV62W" + :BUCKET_SECRET_KEY "bWB6H/LQuPUuZSAOvQuei0gtDSR4BQYz/gBOYUn+VIU" + :LDAP_ORGANISATION "Maidstone Hackspace Test" + :LDAP_DOMAIN "stage.maidstone-hackspace.org.uk" + :LDAP_ADMIN_PASSWORD "NSNZ8xjXwVxis" + :LDAP_SERVER "directory" + :LDAP_ROOT "dc \"stage, dc \"maidstone-hackspace, dc \"org, dc \"uk" + :TWITTER_CONSUMER_KEY "ZdLvlgNBiTNfTWkkKY0ub9F1j" + :TWITTER_CONSUMER_SECRET "z3q5mANhNnIHEO6pXhz5Onex41WHObbZRGZ6MS9WchoTA29qQ7" + :TWITTER_ACCESS_TOKEN "2756146621-3vJvbfIaeHMMoghaPy7D587xls3QmukyXkC4OGh" + :TWITTER_ACCESS_SECRET "PWc6oZFZCXuYxf9VWZxBidPw2AKj69gSvZX0qSz8JQFPu" + :RFID_SECRET "jkhgfkjghjhjgf,kjknsfdkjjadjhglaskjnfjvysyjhbfsckjkbh"}} diff --git a/project.clj b/project.clj new file mode 100644 index 0000000..b570636 --- /dev/null +++ b/project.clj @@ -0,0 +1,12 @@ +(defproject kube-deploy "0.1.0-SNAPSHOT" + :description "FIXME: write description" + :url "http://example.com/FIXME" + :license {:name "Eclipse Public License" + :url "http://www.eclipse.org/legal/epl-v10.html"} + :dependencies [[org.clojure/clojure "1.8.0"] + [org.clojure/data.codec "0.1.1"] + [nano-id "0.9.3"] + [brosenan/lambdakube "0.7.0"]] + :plugins [[lein-auto "0.1.3"]] + :auto {:default {:file-pattern #"\.(clj|cljs|cljx|cljc|edn)$"}} + :main kube-deploy.core) diff --git a/readme.org b/readme.org new file mode 100644 index 0000000..f21a117 --- /dev/null +++ b/readme.org @@ -0,0 +1,3 @@ +# Auto deploy script + +Based on (https://github.com/brosenan/lambdakube-example) using (https://github.com/brosenan/lambda-kube) library. diff --git a/src/kube_deploy/core.clj b/src/kube_deploy/core.clj new file mode 100644 index 0000000..df8f3cd --- /dev/null +++ b/src/kube_deploy/core.clj @@ -0,0 +1,331 @@ +(ns kube-deploy.core + (:require [lambdakube.core :as lk] + [kube-deploy.helpers :as lkh] + [lambdakube.util :as lku] + [clojure.java.shell :as sh] + [clojure.java.io :as io])) + +(def conf (atom {})) +(def config {:num-be-slaves 3 :num-fe-replicas 3}) + +(def resources-mini + {:requests + {:cpu "100m" :memory "64Mi"} + :limits + {:cpu "200m" :memory "256Mi"}}) + +(def resources-small + {:requests + {:cpu "100m" :memory "100Mi"} + :limits + {:cpu "200m" :memory "400Mi"}}) + +(def resources-medium + {:requests + {:cpu "200m" :memory "400Mi"} + :limits + {:cpu "500m" :memory "1Gi"}}) + +; will not be needed soon +(defn kube-apply-namespace + ([content file] + (kube-apply-namespace content file nil nil)) + ([content file namespace] + (kube-apply-namespace content namespace nil)) + ([content file kube-namespace kube-config] + (let [namespace-param (if (nil? namespace) nil (str "--namespace=" kube-namespace)) + config-param (if (nil? config) nil (str "--kubeconfig=" kube-config))] + (when-not (and (.exists file) + (= (slurp file) content)) + (spit file content) + (let [res (apply sh/sh (remove nil? ["kubectl" "apply" namespace-param config-param "-f" (str file)]))] + (when-not (= (:exit res) 0) + (.delete file) + (throw (Exception. (:err res))))))))) + +(defn redis [kube-injector] + (-> kube-injector + (lk/rule :redis-master-rule [] + (fn [] + (-> (lk/pod :redis-master {:app :redis + :role :master + :tier :backend}) + (lk/add-container :redis (get-in @conf [:images :redis]) + (-> {} + (lkh/add-to-spec :resources resources-small) + (lkh/add-readiness-probe-cmd ["redis-cli" "--version"]) + (lkh/add-liveness-probe-cmd ["redis-cli" "--version"]))) + (lk/deployment 1) + (lk/expose-cluster-ip :redis-master + (lk/port :redis :redis 6379 6379))))) + + (lk/rule :redis-slave-rule [:redis-master-rule :num-be-slaves] + (fn [backend-master num-be-slaves] + (-> (lk/pod :redis-slave {:app :redis + :role :slave + :tier :backend}) + (lk/add-container :redis (get-in @conf [:images :redis]) + (-> {} + (lkh/add-to-spec :resources resources-small) + (lkh/add-to-spec + :args ["--slaveof" + (:hostname backend-master) + (-> backend-master :ports :redis str)]) + (lkh/add-liveness-probe-cmd ["redis-cli" "--version"]))) + (lk/deployment num-be-slaves) + (lk/expose-cluster-ip :redis-slave + (lk/port :redis :redis 6379 6379))))))) + +(defn database [kube-injector] + (-> kube-injector + (lk/rule :postgres-master-rule [] + (fn [] + (-> (lk/pod + :postgres-master + {:app :postgres :role :master :tier :backend}) + (lk/add-container :postgres (get-in @conf [:images :postgres]) + (-> {} + (lkh/add-to-spec :resources resources-small) + (lkh/add-readiness-probe-cmd ["pg_isready"]) + (lkh/add-liveness-probe-cmd ["pg_isready"]) + (lkh/add-to-spec :envFrom [{:secretRef {:name "environment"}}]))) + + (lk/deployment 1) + (lk/expose-cluster-ip :postgres-master + (lk/port :postgres :postgres 5432 5432))))))) + +(defn rabbitmq [kube-injector] + (-> kube-injector + (lk/rule :rabbit-master-rule [] + (fn [] + (-> (lk/pod + :rabbit-master + {:app :rabbit :role :master :tier :backend}) + + (lk/add-container + :rabbit + (get-in @conf [:images :rabbitmq]) + (-> {} + (lkh/add-to-spec :resources resources-medium) + (lkh/add-liveness-probe-cmd ["rabbitmq-diagnostics" "ping" "-q"]))) + + (lk/deployment 1) + (lk/expose-cluster-ip + :rabbit-master (lk/port :rabbit :rabbit 5672 5672))))))) + +(defn hackspace [kube-injector] + (-> kube-injector + (lk/rule :hackspace-backend-master [:postgres-master-rule :redis-master-rule] + (fn [postgres redis] + (-> (lk/pod :hackspace-master {:app :hackspace + :role :master + :tier :backend} + {:imagePullSecrets [{:name "docker-registry"}]}) + + ; Add containers to the pod, one for frontend assets and one for backend + (lk/add-container + :hackspace-backend + (get-in @conf [:images :django]) + (-> {} + (lkh/add-to-spec :resources resources-medium) + (lkh/add-to-spec :command ["/gunicorn.sh"]) + (lkh/add-to-spec :ports [{:containerPort 5000 :name "gunicorn-port"}]) + (lkh/add-to-spec :envFrom [{:secretRef {:name "environment"}}]) + (lkh/add-readiness-probe-cmd ["wget" "127.0.0.1:5000"]) + (lkh/add-liveness-probe-cmd ["wget" "127.0.0.1:5000"]))) + + #_(lk/add-container + :hackspace-frontend + (get-in @conf [:images :nginx]) + (-> {} + (lkh/add-to-spec :resources resources-mini) + (lkh/add-to-spec :ports [{:containerPort 80 :name "nginx-port"}]) + (lkh/add-readiness-probe-cmd ["wget" "127.0.0.1/static/images/favicon.ico"]) + (lkh/add-liveness-probe-cmd ["wget" "127.0.0.1/static/images/favicon.ico"]))) + (lk/add-volume "static-files" {:emptyDir {}} + {:hackspace-backend "/app/staticfiles"}) + + ; order does matter here, deployment after initcontainers + (lku/wait-for-service-port postgres :postgres) + (lku/wait-for-service-port redis :redis) + + ; wrap in a deployment + (lk/deployment 2) + (lk/expose-cluster-ip :hackspace-backend + (lk/port :hackspace-master :gunicorn-port 5000 5000)) + (lk/expose-cluster-ip :hackspace-frontend + (lk/port :hackspace-master :nginx-port 80 80))))) + (lk/rule :hackspace-queue-master [] + (fn [] + (-> (lk/pod :hackspace-queue + {:app :hackspace + :role :master + :tier :backend} + {:imagePullSecrets [{:name "docker-registry"}]}) + (lk/add-container + :hackspace + (get-in @conf [:images :django]) + (-> {} + (lkh/add-to-spec :resources resources-small) + (lkh/add-to-spec :command ["/app/manage.py" "huey"]) + (lkh/add-to-spec :envFrom [{:secretRef {:name "environment"}}]))) + (lk/deployment 2)))))) + +(defn footprint [kube-injector] + (-> kube-injector + (lk/rule :footprint-backend-master [:postgres-master-rule :redis-master-rule :rabbit-master-rule] + (fn [postgres redis rabbit] + (-> (lk/pod :footprint-master {:app :footprint + :role :master + :tier :backend} + {:imagePullSecrets [{:name "docker-registry"}]}) + + ; Add containers to the pod, one for frontend assets and one for backend + (lk/add-container + :footprint-backend + (get-in @conf [:images :footprint]) + (-> {} + (lkh/add-to-spec :resources resources-medium) + (lkh/add-to-spec :command ["/gunicorn.sh"]) + (lkh/add-to-spec :ports [{:containerPort 5000 :name "gunicorn-port"}]) + (lkh/add-to-spec :envFrom [{:secretRef {:name "environment"}}]) + #_(lkh/add-readiness-probe-get "http://127.0.0.1" 5000) + (lkh/add-liveness-probe-get "http://127.0.0.1" 5000))) + + (lk/add-container + :footprint-frontend + (get-in @conf [:images :nginx]) + (-> {} + (lkh/add-to-spec :resources resources-mini) + (lkh/add-to-spec :ports [{:containerPort 80 :name "nginx-port"}]) + #_(lkh/add-readiness-probe-get "127.0.0.1/static/images/favicon.ico" 80) + (lkh/add-liveness-probe-get "http://127.0.0.1/static/images/favicon.ico" 80))) + + (lk/add-volume "static-files" {:emptyDir {}} + {:footprint-backend "/app/staticfiles" + :footprint-frontend "/usr/share/nginx/html/static/"}) + + ; order does matter here, deployment after initcontainers + (lku/wait-for-service-port postgres :postgres) + (lku/wait-for-service-port redis :redis) + (lku/wait-for-service-port rabbit :rabbit) + + ; wrap in a deployment + (lk/deployment 2) + (lk/expose-cluster-ip :footprint-backend + (lk/port :footprint-master :gunicorn-port 5000 5000)) + (lk/expose-cluster-ip :footprint-frontend + (lk/port :footprint-master :nginx-port 80 80))))) + (lk/rule :footprint-queue-master [] + (fn [] + (-> (lk/pod :footprint-queue + {:app :footprint + :role :master + :tier :backend} + {:imagePullSecrets [{:name "docker-registry"}]}) + + (lk/add-container + :footprint + (get-in @conf [:images :footprint]) + (-> {} + (lkh/add-to-spec :resources resources-small) + (lkh/add-to-spec :command ["/app/manage.py" "rundramatiq" "-p" "1" "-t" "1"]) + (lkh/add-to-spec :envFrom [{:secretRef {:name "environment"}}]))) + + (lk/deployment 2)))))) + +(defn make-secrets [kube-injector name data] + (-> kube-injector + (lk/rule :secrets [] + (fn [] + (-> (lkh/secrets (keyword name) data)))))) + +(defn make-registry-secrets [kube-injector name data] + (-> kube-injector + (lk/rule + :secrets-registry [] + (fn [] + (lkh/secrets-registry name {} (lkh/encode-secret-map data)))))) + +(defn make-namespace [kube-injector name] + (-> kube-injector + (lk/rule :namespace [] + (fn [] + (lkh/kube-namespace (keyword name)))))) + +(defn make-environment-secrets [kube-injector name data] + (-> kube-injector + (lk/rule :secrets-environment [] + (fn [] + (lkh/secrets name {} + (lkh/encode-secret-map data)))))) + +(defn make-ingress [kube-injector] + (lk/rule kube-injector :lkube-ingress [] + (fn [] + (-> (lkh/ingress "lkube-ingress" {:app :nginx-ingress}) + (lkh/add-spec :tls [{:hosts [:lkube-ingress] :secretName "lkube-tls"}]) + (lkh/add-spec :rules [{:host "lkube-ingress.35.197.252.20.nip.io" + :http {:paths + [{:path "/" + :backend + {:serviceName "footprint-backend" + :servicePort 5000}} + {:path "/static/" + :backend + {:serviceName "footprint-frontend" + :servicePort 80}}]}}]) + (lk/add-annotation :kubernetes.io/tls-acme "true") + (lk/add-annotation :kubernetes.io/ingress.class "nginx"))))) + +(defn footprint-deployment [namespace kube-config] + (reset! conf (clojure.edn/read-string (slurp (str namespace "-config.edn")))) + ; run deployment in default namespace to create a namespace called footprint + (-> (lk/injector) + (make-namespace namespace) + lk/standard-descs + (lk/get-deployable config) + (lkh/to-yaml-store (str namespace "-store.yaml")) + (kube-apply-namespace (io/file (str namespace "-deploy.yaml")) nil kube-config)) + + ; run deployment under footprint namespace + (-> (lk/injector) + (make-environment-secrets :environment (:vars @conf)) + (make-registry-secrets :docker-registry {:.dockerconfigjson (:registry @conf)}) + make-ingress + rabbitmq + database + redis + footprint + lk/standard-descs + (lk/get-deployable config) + (lkh/to-yaml-store (str namespace "-store.yaml")) + (kube-apply-namespace (io/file (str namespace "-deploy.yaml")) "footprint" kube-config))) + +(defn hackspace-deployment [namespace kube-config] + (reset! conf (clojure.edn/read-string (slurp (str namespace "-config.edn")))) + ; run deployment in default namespace to create a namespace called footprint + (-> (lk/injector) + (make-namespace namespace) + ;lk/standard-descs + (lk/get-deployable config) + (lkh/to-yaml-store (str namespace "-store.yaml")) + (kube-apply-namespace (io/file (str namespace "-deploy.yaml")) nil kube-config)) + + ; run deployment under footprint namespace + (-> (lk/injector) + (make-environment-secrets :environment (:vars @conf)) + ;(make-registry-secrets :docker-registry {:.dockerconfigjson (:registry @conf)}) + make-ingress + database + redis + hackspace + lk/standard-descs + (lk/get-deployable config) + (lkh/to-yaml-store (str namespace "-store.yaml")) + (kube-apply-namespace (io/file (str namespace "-deploy.yaml")) namespace kube-config))) + +(defn -main [] + (hackspace-deployment "maidstone-hackspace" "/home/oly/.kube/dke.yml") + (footprint-deployment "footprint" "/home/oly/.kube/ake.yml")) + diff --git a/src/kube_deploy/helpers.clj b/src/kube_deploy/helpers.clj new file mode 100644 index 0000000..b654c62 --- /dev/null +++ b/src/kube_deploy/helpers.clj @@ -0,0 +1,170 @@ +(ns kube-deploy.helpers + (:require [lambdakube.core :as lk] + [clojure.data.codec.base64 :as b64])) + +(defn kube-namespace + ([name] + {:apiVersion "v1" + :kind "Namespace" + :metadata {:name name}})) + +(defn secrets + ([name labels options] + {:apiVersion "v1" + :kind "Secret" + :type "Opaque" + :metadata {:name name :labels labels} + :data options})) + +(defn secrets-registry + ([name labels options] + {:apiVersion "v1" + :kind "Secret" + :type "kubernetes.io/dockerconfigjson" + :metadata {:name name :labels labels} + :data options})) + +(defn ingress + ([name labels] + (ingress name labels {})) + ([name labels spec] + {:apiVersion "networking.k8s.io/v1beta1" + :kind "Ingress" + :metadata {:name name :labels labels} + :spec spec})) + +(defn add-to-spec + ([container name value] + (-> container + (assoc name value)))) + +(defn add-readiness-probe-get + ([container url port] + (add-readiness-probe-get container url port 60 60)) + ([container url port delay period] + (-> container + (assoc :readinessProbe + {:periodSeconds period + :initialDelaySeconds delay + :failureThreshold 5 + :httpGet {:path url :port port}})))) + +(defn add-readiness-probe-cmd + ([container command] + (add-readiness-probe-cmd container command 60 60)) + ([container command delay period] + (-> container + (assoc :readinessProbe + {:periodSeconds period + :initialDelaySeconds delay + :failureThreshold 5 + :exec {:command command}})))) + +(defn add-liveness-probe-get + ([container url port] + (add-liveness-probe-get container url port 60 60)) + ([container url port delay period] + (-> container + (assoc :livenessProbe + {:periodSeconds period + :initialDelaySeconds delay + :failureThreshold 5 + :httpGet {:path url :port port}})))) + +(defn add-liveness-probe-cmd + ([container command] + (add-liveness-probe-cmd container command 60 60)) + ([container command delay period] + (-> container + (assoc :livenessProbe + {:periodSeconds delay + :initialDelaySeconds period + :failureThreshold 5 + :exec {:command command}})))) + +(defn add-tls [obj key val] + (-> obj + (update-in [:spec :tls] assoc key val))) + +(defn add-rules [obj key val] + (-> obj + (update-in [:spec :rules] assoc key val))) + + ;; livenessProbe: + ;; exec: + ;; command: + ;; - cat + ;; - /tmp/healthy + ;; initialDelaySeconds: 5 + ;; periodSeconds: 5 + +;; (defn add-spec [obj key val] +;; (-> obj +;; (update-in [:spec] assoc key val))) + + +(defn add-spec [obj key val] + (-> obj + (update-in [:spec] assoc key val))) + +(defn encode-secret [value] + (b64/encode (.getBytes value))) + +(defn encode-secret-map [data] + (reduce-kv + (fn [m k v] (assoc m k (encode-secret v))) + {} + data)) + +(defn to-yaml-store [$ filename] + (let [y (lk/to-yaml $)] + (spit filename y) y)) + +(defn to-yaml [$] + (let [y (lk/to-yaml $)] + (spit "test.yaml" y) + y)) + + +;; (defn add-init-container +;; ([pod name image options] +;; (let [container (-> options +;; (merge {:name name +;; :image image}))] +;; (update pod :spec field-conj :initContainers container))) +;; ([pod name image] +;; (add-init-container pod name image {}))) + +;; (defn update-container [pod cont-name f & args] +;; (let [update-cont (fn [cont] +;; (if (= (:name cont) cont-name) +;; (apply f cont args) +;; ;; else +;; cont))] +;; (-> pod +;; (update-in [:spec :containers] #(map update-cont %)) +;; (update-in [:spec :initContainers] #(map update-cont %)) +;; (update :spec #(if (empty? (:initContainers %)) +;; (dissoc % :initContainers) +;; ;; else +;; %))))) + + +;; (defn wait-for-service-port +;; ([pod dep portname] +;; (let [{:keys [hostname ports]} dep +;; cont (keyword (str "wait-for-" (name hostname) "-" (name portname)))] +;; (-> pod +;; (lk/add-init-container cont "busybox" +;; {:command ["sh" +;; "-c" +;; (str "while ! nc -z " hostname " " (ports portname) "; do sleep 1; done")]})))) +;; ([pod dep] +;; (when-not (= (-> dep :ports count) 1) +;; (throw (Exception. "Port name must be specified when waiting for a service exposing more than one port."))) +;; (let [portname (-> dep :ports first first)] +;; (wait-for-service-port pod dep portname)))) + + + + diff --git a/test/kube_deploy/core_test.clj b/test/kube_deploy/core_test.clj new file mode 100644 index 0000000..952767e --- /dev/null +++ b/test/kube_deploy/core_test.clj @@ -0,0 +1,103 @@ +(ns kube-deploy.core-test + (require [clojure.test :refer :all] + [kube-deploy.core :as lke] + [lambdakube.core :as lk] + [lambdakube.util :as lku] + [lambdakube.testing :as lkt])) + + +(defn test-module [$] + (-> $ + ;; Tests that the master-slave configuration works. We set a + ;; value in the master, and then read it from the slave, + ;; expecting it to be the same. + (lkt/test :redis-slave-configured + {:num-be-slaves 1} + [:backend-master :backend-slave] + (fn [master slave] + (-> (lk/pod :test {}) + ;; Wait for both the master and slave to be up + (lku/wait-for-service-port master :redis) + (lku/wait-for-service-port slave :redis) + ;; We use midje for the tests + (lku/add-midje-container + :test + '[[org.clojure/clojure "1.8.0"] + ;; Carmine is a Redis client library for Clojure + [com.taoensso/carmine "2.18.1"]] + ;; We pass the connection details for the Redis + ;; master and slave, as provided by our + ;; dependencies as constants to the test. + {:master-conn {:pool {} :spec {:host (:hostname master) + :port (-> master :ports :redis)}} + :slave-conn {:pool {} :spec {:host (:hostname slave) + :port (-> slave :ports :redis)}}} + '[(ns main-test + (:require [midje.sweet :refer :all] + [taoensso.carmine :as car])) + (fact + ;; Set the value in the master. + (car/wcar master-conn + (car/with-replies + (car/set "foo" "bar")) => "OK") + ;; Wait for value to propagate to slave + (Thread/sleep 1000) + ;; Get the value from the slave. + (car/wcar slave-conn + (car/with-replies + (car/get "foo")) => "bar"))])))) + ;; This tests the PHP code in the frontent. It sets value to a + ;; key and then queries it. + (lkt/test :frontend-set-and-get + {:num-be-slaves 1 + :num-fe-replicas 1} + [:frontend] + (fn [frontend] + (-> (lk/pod :test {}) + ;; We wait for the frontend to come up + (lku/wait-for-service-port frontend :web) + (lku/add-midje-container + :test + '[[org.clojure/clojure "1.8.0"] + ;; We use clj-http to query the PHP page. + [clj-http "3.9.1"]] + ;; We pass the `base-url` to the PHP page as a + ;; constant. Values are based on the `frontend` + ;; dependency. + {:base-url (str "http://" + (:hostname frontend) + ":" + (-> frontend :ports :web) + "/guestbook.php")} + '[(ns main-test + (:require [midje.sweet :refer :all] + [clj-http.client :as client] + [clojure.string :as str])) + ;; A function that makes a query to the PHP + ;; page, by constructing a URL and making a + ;; GET request. + (defn wget [query] + (let [resp (client/get (str base-url "?" (str/join "&" (for [[k v] query] + (str k "=" v)))))] + (when-not (= (:status resp) 200) + (throw (Exception. (str "Bad status" (:status resp) ": " (:body resp))))) + (:body resp))) + + ;; Set a value. + (fact + (wget {"cmd" "set" + "key" "foo" + "value" "bar"}) => "{\"message\": \"Updated\"}") + ;; Query that value. + (fact + (wget {"cmd" "get" + "key" "foo"}) => "{\"data\": \"bar\"}")])))))) + +(deftest kubetests + (is (= (-> (lk/injector) + (lke/module) + (lk/standard-descs) + (test-module) + (lkt/kube-tests "lk-ex")) ""))) + +