added profiles, for joblimit and future features
also fixes to file upload, parameter reading and error handling
This commit is contained in:
parent
f28dcb2888
commit
30fb7ae1a2
|
@ -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>
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)))))))
|
||||||
|
|
|
@ -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)))
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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))))
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue