added profiles, for joblimit and future features

also fixes to file upload, parameter reading and error handling
This commit is contained in:
Jaromil 2018-10-14 19:13:47 +02:00
parent f28dcb2888
commit 30fb7ae1a2
7 changed files with 176 additions and 90 deletions

View File

@ -1,10 +1,10 @@
<div class="box"> <div class="box">
<h1 class="title">Upload a Dockerfile to toast</h1> <h1 class="title">Upload a Dockerfile to toast</h1>
<p>Choose the file in your computer and click 'Submit' to proceed to validation.</p> <p>Choose the file in your computer and click 'Submit' to proceed to validation.</p>
<form action="dockerfile" class="form-shell" enctype="multipart/form-data" method="post"> <form action="dockerfile" class="form" enctype="multipart/form-data" method="post">
<div class="file has-label is-fullwidth"> <div class="has-label is-fullwidth">
<label class="file-label"> <label class="file-label">
<input class="file-input inputfile inputfile-2" id="file" type="file" /> <input name="file" class="file-input inputfile inputfile-2" id="file" type="file" />
<label for="file"><span id="filename">Choose a Dockerfile...</span></label> <label for="file"><span id="filename">Choose a Dockerfile...</span></label>
<span class="file-cta"><span class="file-icon"><i class="fa fa-upload"></i></span><span class="file-label">Upload</span></span> <span class="file-cta"><span class="file-icon"><i class="fa fa-upload"></i></span><span class="file-label">Upload</span></span>
</label></div> </label></div>

View File

@ -38,7 +38,8 @@
[toaster.session :as s] [toaster.session :as s]
[toaster.config :as conf] [toaster.config :as conf]
[toaster.ring :as ring] [toaster.ring :as ring]
[toaster.views :as views]) [toaster.views :as views]
[toaster.profiles :as profile])
(:gen-class)) (:gen-class))
(defonce config (conf/load-config "toaster" conf/default-settings)) (defonce config (conf/load-config "toaster" conf/default-settings))
@ -81,9 +82,9 @@
(POST "/dockerfile" request (POST "/dockerfile" request
(->> (fn [req conf acct] (->> (fn [req conf acct]
(s/render acct (s/render acct
[:body [:body
(views/dockerfile-upload-post req conf acct) (views/dockerfile-upload req conf acct)
(views/dashboard acct)])) (views/dashboard acct)]))
(auth-wrap request))) (auth-wrap request)))
(POST "/remove" request (POST "/remove" request
@ -115,7 +116,7 @@
(auth-wrap request))) (auth-wrap request)))
;; JUST-AUTH ROUTES ;; JUST-AUTH ROUTES
(GET "/login" request (s/render (s/resource s/login))) (GET "/login" request (s/render-template s/login))
(POST "/login" request (POST "/login" request
(f/attempt-all (f/attempt-all
@ -142,7 +143,7 @@
(s/render [:body (s/render [:body
[:h1 {:class "title"} "Logged out."]]))) [:h1 {:class "title"} "Logged out."]])))
(GET "/signup" request (s/render (s/resource s/signup))) (GET "/signup" request (s/render-template s/signup))
(POST "/signup" request (POST "/signup" request
(f/attempt-all (f/attempt-all
[name (s/param request :name) [name (s/param request :name)
@ -194,7 +195,10 @@
[:h1 {:class "title"} "Failure activating account"] [:h1 {:class "title"} "Failure activating account"]
[:h2 {:class "subtitle"} (f/message act)] [:h2 {:class "subtitle"} (f/message act)]
[:p (str "Email: " email " activation-id: " activation-id)]] "is-error") [:p (str "Email: " email " activation-id: " activation-id)]] "is-error")
(s/notify [:h1 {:class "title"} (str "Account activated - " email)] "is-success"))]))) [:span
(s/notify (str "Account activated: " email) "is-success")
(profile/create email)]
)])))
;; -- end of JUST-AUTH ;; -- end of JUST-AUTH
(POST "/" request (POST "/" request

View File

@ -1,33 +1,33 @@
(ns toaster.jobs (ns toaster.jobs
(:require (:require
[clojure.string :as str] [clojure.string :as str]
[clojure.java.io :as io] [clojure.java.io :as io]
[clj-time.core :as time] [clj-time.core :as time]
[clj-time.coerce :as tc] [clj-time.coerce :as tc]
[clj-storage.db.mongo :refer [create-mongo-store]] [clj-storage.core :as db]
[clj-storage.core :as db] [failjure.core :as f]
[failjure.core :as f] [taoensso.timbre :as log :refer [debug]]
[taoensso.timbre :as log :refer [debug]] [me.raynes.conch :as sh :refer [with-programs]]
[me.raynes.conch :as sh :refer [with-programs]] [toaster.config :refer :all]
[toaster.config :refer :all] [toaster.ring :refer [jobs]]
[toaster.ring :refer [jobs]] [toaster.profiles :as profile]
[hiccup.form :as hf])) [hiccup.form :as hf]))
;; list of arm targets to chose from ;; list of arm targets to chose from
;"beagleboneblack" ;"beagleboneblack"
;"chromeacer" ;"chromeacer"
;"chromeveyron" ;"chromeveyron"
;"droid4" ;"droid4"
;"n900" ;"n900"
;"odroidxu4" ;"odroidxu4"
;"odroidxu" ;"odroidxu"
;"ouya" ;"ouya"
;"raspi1" ;"raspi1"
;"raspi2" ;"raspi2"
;"raspi3" ;"raspi3"
;"rock64" ;"rock64"
;"sunxi" ;"sunxi"
;"turbox-twister" ;"turbox-twister"
(defn- ssh-host [config] (defn- ssh-host [config]
(str (q config [:jenkins :user]) "@" (q config [:jenkins :host]))) (str (q config [:jenkins :user]) "@" (q config [:jenkins :host])))
@ -46,36 +46,45 @@
(defn- dockerlint [path] (defn- dockerlint [path]
(with-programs [node] (with-programs [node]
(try (try
(node "node_modules/dockerlint/bin/dockerlint.js" path) (node "node_modules/dockerlint/bin/dockerlint.js" path)
(catch Exception e (catch Exception e
(f/fail (str "ERROR in dockerlint - " (.getMessage e))))))) (f/fail (str "ERROR in dockerlint - " (.getMessage e)))))))
(defn count [id]
(f/attempt-all
[joblist (db/query @jobs {:email id})]
(clojure.core/count joblist)
(f/when-failed [e]
(f/fail "jobs/count :: " (f/message e)))))
(defn add [path config account] (defn add [path config account]
(with-programs [ssh scp node] (with-programs
(let [tstamp (tc/to-long (time/now)) [ssh scp node]
jobname (str (:email account) "-vm_amd64_ascii-" tstamp) (let [tstamp (tc/to-long (time/now))
jobdir (str "/srv/toaster/" jobname)] jobname (str (:email account) "-vm_amd64_ascii-" tstamp)
(f/attempt-all jobdir (str "/srv/toaster/" jobname)]
[r_lint (dockerlint path) (f/if-let-ok? [joblimit (profile/get-joblimit (:email account))]
r_mkdir (ssh "-i" (q config [:jenkins :key]) (if (>= (count (:email account)) joblimit)
(ssh-host config) "mkdir" "-p" jobdir) (f/fail "Job limit is reached, trash some to free slots")
r_scp (scp "-i" (q config [:jenkins :key]) (f/attempt-all
path (str (ssh-host config) ":" jobdir "/Dockerfile")) [r_lint (dockerlint path)
r_job (sync_jobs config "-a" jobname) r_mkdir (ssh "-i" (q config [:jenkins :key])
r_store (db/store! @jobs :jobid (ssh-host config) "mkdir" "-p" jobdir)
{:jobid jobname r_scp (scp "-i" (q config [:jenkins :key])
:email (:email account) path (str (ssh-host config) ":" jobdir "/Dockerfile"))
:account (dissoc account :password :activation-link) r_job (sync_jobs config "-a" jobname)
:lint (if (.contains r_lint "is OK") true false) r_store (db/store! @jobs :jobid
:timestamp tstamp {:jobid jobname
:type "vm_amd64_ascii" :email (:email account)
:dockerfile (slurp path)})] :account (dissoc account :password :activation-link)
{:lint r_lint :lint (if (.contains r_lint "is OK") true false)
:job r_job} :timestamp tstamp
(f/when-failed [e] :type "vm_amd64_ascii"
(f/fail (str "Job add '" jobname "' failure: " (f/message e)))))))) :dockerfile (slurp path)})]
{:lint r_lint
:job r_job}
(f/when-failed [e]
(f/fail (str "Job add '" jobname "' failure :: " (f/message e))))))
(f/fail (str "Cannot find profile " (:email account) " :: "
(f/message joblimit)))))))

View File

@ -0,0 +1,36 @@
(ns toaster.profiles
(:require
[clojure.string :as str]
[clojure.java.io :as io]
[clj-time.core :as time]
[clj-time.coerce :as tc]
[clj-storage.db.mongo :refer [create-mongo-store]]
[clj-storage.core :as db]
[failjure.core :as f]
[taoensso.timbre :as log :refer [debug]]
[me.raynes.conch :as sh :refer [with-programs]]
[toaster.config :refer :all]
[toaster.ring :refer [jobs profiles]]
[toaster.bulma :refer [render-yaml]]
[hiccup.form :as hf]))
(defn create [id]
(let [profile {:email id
:joblimit 3 ; default
:roles ["noob"]
:lastlogin (tc/to-long (time/now))}]
(db/store! @profiles :email profile)
(render-yaml profile)))
(defn- fetch-profile [id]
(f/if-let-ok? [profile (db/fetch @profiles id)]
profile
(f/fail (str "Profile not found " id " :: " (f/message profile)))))
(defn get-joblimit [id]
(f/ok-> id fetch-profile (get :joblimit)))
(defn has-role [id role]
(f/ok-> id fetch-profile (get :roles)
clojure.core/set (contains? role)))

View File

@ -33,11 +33,14 @@
[ring.middleware.accept :refer [wrap-accept]] [ring.middleware.accept :refer [wrap-accept]]
[ring.middleware.defaults :refer [wrap-defaults site-defaults]])) [ring.middleware.defaults :refer [wrap-defaults site-defaults]]))
;; generic webapp stores
(def config (atom {})) (def config (atom {}))
(def db (atom {})) (def db (atom {}))
(def accts (atom {})) (def accts (atom {}))
(def auth (atom {})) (def auth (atom {}))
(def jobs (atom {})) ;; app specific stores
(def jobs (atom {}))
(def profiles (atom {}))
(defn init [] (defn init []
(log/merge-config! (log/merge-config!
@ -86,6 +89,8 @@
;; ---------------------- ;; ----------------------
;; initialize jobs stores ;; initialize jobs stores
(reset! jobs (create-mongo-store @db :job-store)) (reset! jobs (create-mongo-store @db :job-store))
;; initialize profile stores
(reset! profiles (create-mongo-store @db :profile-store))
;; ------------------------------ ;; ------------------------------
;; log all results worth noticing ;; log all results worth noticing

View File

@ -14,17 +14,18 @@
([template] (resource template {})) ([template] (resource template {}))
([template params] (render-resource template params))) ([template params] (render-resource template params)))
(defonce login "templates/body_loginform.html") (defonce login "templates/body_loginform.html")
(defonce signup "templates/body_signupform.html") (defonce signup "templates/body_signupform.html")
(defonce head "templates/html_head.html") (defonce head "templates/html_head.html")
(defonce footer "templates/body_footer.html") (defonce footer "templates/body_footer.html")
(defn param [request param] (defn param [request param]
(let [value (let [p (if (coll? param)
(get-in request (into [:params] param)
(conj [:params] param))] (conj [:params] param))
value (get-in request p)]
(if (nil? value) (if (nil? value)
(f/fail (str "Parameter not found: " param)) (f/fail (str "Parameter not found: " p))
value))) value)))
;; TODO: not working? ;; TODO: not working?
@ -70,9 +71,12 @@
(= tclass "is-warning") (log/warn msg) (= tclass "is-warning") (log/warn msg)
(= tclass "is-success") (log/info msg) (= tclass "is-success") (log/info msg)
(= tclass "is-primary") (log/debug msg)) (= tclass "is-primary") (log/debug msg))
[:div {:class (str "notification " tclass " has-text-centered")} msg]))) [:div {:class (str "notification " tclass " has-text-centered")} msg]
)))
;; shortcut ;; shortcut
(defn error [msg fail] (notify (str msg " :: " (f/message fail)) "is-danger")) (defn error
([msg] (notify msg "is-danger"))
([msg fail] (notify (str msg " :: " (f/message fail)) "is-danger")))
(defn render (defn render
"render a full webpage using headers, navbar, body and footer" "render a full webpage using headers, navbar, body and footer"
@ -84,4 +88,30 @@
(resource head) (resource head)
(conj body (resource footer)))})) (conj body (resource footer)))}))
(defn render-template
"render an html from resources using headers, navbar, body and footer"
([body] (render-template nil body))
([account body]
{:headers {"Content-Type"
"text/html; charset=utf-8"}
:body (str "<!DOCTYPE html>\n<html>"
(resource head)
"\n<!-- body -->\n"
(resource body)
"\n<!-- end of body -->\n"
(resource footer))}))
(defn render-html
"render an html from resources using headers, navbar, body and footer"
([body] (render-template nil body))
([account body]
{:headers {"Content-Type"
"text/html; charset=utf-8"}
:body (str "<!DOCTYPE html>\n<html>"
(resource head)
"\n<!-- body -->\n"
body ;; html text string
"\n<!-- end of body -->\n"
(resource footer))}))
(defn render-error [err] (->> "is-danger" (notify err) render)) (defn render-error [err] (->> "is-danger" (notify err) render))

View File

@ -60,29 +60,31 @@
(web/button "/remove" "\uD83D\uDDD1" (hf/hidden-field "jobid" jobid))]]] (web/button "/remove" "\uD83D\uDDD1" (hf/hidden-field "jobid" jobid))]]]
))]]]) ))]]])
(defn dockerfile-upload-post [request config account] (defn dockerfile-upload [request config account]
(f/attempt-all (f/attempt-all
[tempfile (s/param request [:file :tempfile]) [tempfile (s/param request [:file :tempfile])
filename (s/param request [:file :filename]) filename (s/param request [:file :filename])
filesize (s/param request [:file :size])] params (log/spy (:params request))]
(cond (if (> (s/param request [:file :size]) 64000)
(> filesize 64000)
;; TODO: put filesize limit in config ;; TODO: put filesize limit in config
(s/notify "File too big in upload (64KB limit)" "is-danger") (s/error "File too big in upload (64KB limit)")
:else ;; else
(let [file (io/copy tempfile (io/file "/tmp" filename)) (let [file (io/copy tempfile (io/file "/tmp" filename))
path (str "/tmp/" filename)] path (str "/tmp/" filename)]
(io/delete-file tempfile) (io/delete-file tempfile)
(if (not (.exists (io/file path))) (if (not (.exists (io/file path)))
(s/notify (str "Uploaded file not found: " filename) "is-danger") (s/error (str "Uploaded file not found: " filename))
;; file is now in 'tmp' var ;; file is now in 'tmp' var
(f/if-let-ok? [newjob (job/add path config account)] (f/attempt-all
[:div {:class "container"} [newjob (job/add path config account)]
[:h1 {:class "title"} "Job uploaded and added"] [:div {:class "container"}
[:p "Log messages:"] [:h1 {:class "title"} "Job uploaded and added"]
(web/render-yaml newjob)] [:p "Log messages:"]
;; else when job/add is not-ok (web/render-yaml newjob)]
(s/error "Error adding job" newjob))))) ;; else when job/add is not-ok
(f/when-failed [e]
(s/error "Error adding job" e))
))))
(f/when-failed [e] (f/when-failed [e]
(s/error "Upload file error" e)))) (s/error "Upload file error" e))))
@ -107,7 +109,7 @@
jobfound (db/query @ring/jobs {:jobid jobid}) jobfound (db/query @ring/jobs {:jobid jobid})
r_rmjob (db/delete! @ring/jobs jobid) r_rmjob (db/delete! @ring/jobs jobid)
r_sync (job/sync_jobs config "-d" jobid)] r_sync (job/sync_jobs config "-d" jobid)]
(s/notify (str "Job removed: " jobid) "is-primary") (s/notify (str "Job removed :: " jobid) "is-primary")
(f/when-failed [e] (f/when-failed [e]
(s/error "Failure removing job" e)))) (s/error "Failure removing job" e))))