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"> | ||||
|     <h1 class="title">Upload a Dockerfile to toast</h1> | ||||
|     <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"> | ||||
|         <div class="file has-label is-fullwidth"> | ||||
|     <form action="dockerfile" class="form" enctype="multipart/form-data" method="post"> | ||||
|         <div class="has-label is-fullwidth"> | ||||
| 			<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> | ||||
| 				<span class="file-cta"><span class="file-icon"><i class="fa fa-upload"></i></span><span class="file-label">Upload</span></span> | ||||
| 			</label></div> | ||||
|  |  | |||
|  | @ -38,7 +38,8 @@ | |||
|    [toaster.session :as s] | ||||
|    [toaster.config :as conf] | ||||
|    [toaster.ring :as ring] | ||||
|    [toaster.views :as views]) | ||||
|    [toaster.views :as views] | ||||
|    [toaster.profiles :as profile]) | ||||
|   (:gen-class)) | ||||
| 
 | ||||
| (defonce config (conf/load-config "toaster" conf/default-settings)) | ||||
|  | @ -81,9 +82,9 @@ | |||
|   (POST "/dockerfile" request | ||||
|         (->> (fn [req conf acct] | ||||
|                (s/render acct | ||||
|                            [:body | ||||
|                             (views/dockerfile-upload-post req conf acct) | ||||
|                             (views/dashboard acct)])) | ||||
|                          [:body | ||||
|                           (views/dockerfile-upload req conf acct) | ||||
|                           (views/dashboard acct)])) | ||||
|              (auth-wrap request))) | ||||
| 
 | ||||
|   (POST "/remove" request | ||||
|  | @ -115,7 +116,7 @@ | |||
|             (auth-wrap request))) | ||||
| 
 | ||||
|   ;; JUST-AUTH ROUTES | ||||
|   (GET "/login" request (s/render (s/resource s/login))) | ||||
|   (GET "/login" request (s/render-template s/login)) | ||||
| 
 | ||||
|   (POST "/login" request | ||||
|         (f/attempt-all | ||||
|  | @ -142,7 +143,7 @@ | |||
|              (s/render [:body | ||||
|                           [: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 | ||||
|         (f/attempt-all | ||||
|          [name (s/param request :name) | ||||
|  | @ -194,7 +195,10 @@ | |||
|                [:h1 {:class "title"}    "Failure activating account"] | ||||
|                [:h2 {:class "subtitle"} (f/message act)] | ||||
|                [: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 | ||||
| 
 | ||||
|   (POST "/" request | ||||
|  |  | |||
|  | @ -1,33 +1,33 @@ | |||
| (ns toaster.jobs | ||||
|   (: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]] | ||||
|     [hiccup.form :as hf])) | ||||
|    [clojure.string :as str] | ||||
|    [clojure.java.io :as io] | ||||
|    [clj-time.core :as time] | ||||
|    [clj-time.coerce :as tc] | ||||
|    [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]] | ||||
|    [toaster.profiles :as profile] | ||||
|    [hiccup.form :as hf])) | ||||
| 
 | ||||
| ;; list of arm targets to chose from | ||||
| ;"beagleboneblack" | ||||
| ;"chromeacer" | ||||
| ;"chromeveyron" | ||||
| ;"droid4" | ||||
| ;"n900" | ||||
| ;"odroidxu4" | ||||
| ;"odroidxu" | ||||
| ;"ouya" | ||||
| ;"raspi1" | ||||
| ;"raspi2" | ||||
| ;"raspi3" | ||||
| ;"rock64" | ||||
| ;"sunxi" | ||||
| ;"turbox-twister" | ||||
|                                         ;"beagleboneblack" | ||||
|                                         ;"chromeacer" | ||||
|                                         ;"chromeveyron" | ||||
|                                         ;"droid4" | ||||
|                                         ;"n900" | ||||
|                                         ;"odroidxu4" | ||||
|                                         ;"odroidxu" | ||||
|                                         ;"ouya" | ||||
|                                         ;"raspi1" | ||||
|                                         ;"raspi2" | ||||
|                                         ;"raspi3" | ||||
|                                         ;"rock64" | ||||
|                                         ;"sunxi" | ||||
|                                         ;"turbox-twister" | ||||
| 
 | ||||
| (defn- ssh-host [config] | ||||
|   (str (q config [:jenkins :user]) "@" (q config [:jenkins :host]))) | ||||
|  | @ -46,36 +46,45 @@ | |||
| 
 | ||||
| (defn- dockerlint [path] | ||||
|   (with-programs [node] | ||||
|                  (try | ||||
|                    (node "node_modules/dockerlint/bin/dockerlint.js" path) | ||||
|                    (catch Exception e | ||||
|                      (f/fail (str "ERROR in dockerlint - " (.getMessage e))))))) | ||||
|     (try | ||||
|       (node "node_modules/dockerlint/bin/dockerlint.js" path) | ||||
|       (catch Exception 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] | ||||
|   (with-programs [ssh scp node] | ||||
|                  (let [tstamp (tc/to-long (time/now)) | ||||
|                        jobname (str (:email account) "-vm_amd64_ascii-" tstamp) | ||||
|                        jobdir (str "/srv/toaster/" jobname)] | ||||
|                    (f/attempt-all | ||||
|                      [r_lint (dockerlint path) | ||||
|                       r_mkdir (ssh "-i" (q config [:jenkins :key]) | ||||
|                                    (ssh-host config) "mkdir" "-p" jobdir) | ||||
|                       r_scp (scp "-i" (q config [:jenkins :key]) | ||||
|                                  path (str (ssh-host config) ":" jobdir "/Dockerfile")) | ||||
|                       r_job (sync_jobs config "-a" jobname) | ||||
|                       r_store (db/store! @jobs :jobid | ||||
|                                          {:jobid     jobname | ||||
|                                           :email     (:email account) | ||||
|                                           :account   (dissoc account :password :activation-link) | ||||
|                                           :lint      (if (.contains r_lint "is OK") true false) | ||||
|                                           :timestamp tstamp | ||||
|                                           :type      "vm_amd64_ascii" | ||||
|                                           :dockerfile (slurp path)})] | ||||
|                      {:lint r_lint | ||||
|                       :job  r_job} | ||||
|                      (f/when-failed [e] | ||||
|                        (f/fail (str "Job add '" jobname "' failure: " (f/message e)))))))) | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|   (with-programs | ||||
|     [ssh scp node] | ||||
|     (let [tstamp (tc/to-long (time/now)) | ||||
|           jobname (str (:email account) "-vm_amd64_ascii-" tstamp) | ||||
|           jobdir (str "/srv/toaster/" jobname)] | ||||
|       (f/if-let-ok? [joblimit (profile/get-joblimit (:email account))] | ||||
|         (if (>= (count (:email account)) joblimit) | ||||
|           (f/fail "Job limit is reached, trash some to free slots") | ||||
|           (f/attempt-all | ||||
|            [r_lint (dockerlint path) | ||||
|             r_mkdir (ssh "-i" (q config [:jenkins :key]) | ||||
|                          (ssh-host config) "mkdir" "-p" jobdir) | ||||
|             r_scp (scp "-i" (q config [:jenkins :key]) | ||||
|                        path (str (ssh-host config) ":" jobdir "/Dockerfile")) | ||||
|             r_job (sync_jobs config "-a" jobname) | ||||
|             r_store (db/store! @jobs :jobid | ||||
|                                {:jobid     jobname | ||||
|                                 :email     (:email account) | ||||
|                                 :account   (dissoc account :password :activation-link) | ||||
|                                 :lint      (if (.contains r_lint "is OK") true false) | ||||
|                                 :timestamp tstamp | ||||
|                                 :type      "vm_amd64_ascii" | ||||
|                                 :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.defaults :refer [wrap-defaults site-defaults]])) | ||||
| 
 | ||||
| ;; generic webapp stores | ||||
| (def config (atom {})) | ||||
| (def db     (atom {})) | ||||
| (def accts  (atom {})) | ||||
| (def auth   (atom {})) | ||||
| (def jobs   (atom {})) | ||||
| ;; app specific stores | ||||
| (def jobs     (atom {})) | ||||
| (def profiles (atom {})) | ||||
| 
 | ||||
| (defn init [] | ||||
|   (log/merge-config! | ||||
|  | @ -86,6 +89,8 @@ | |||
|   ;; ---------------------- | ||||
|   ;; initialize jobs stores | ||||
|   (reset! jobs (create-mongo-store @db :job-store)) | ||||
|   ;; initialize profile stores | ||||
|   (reset! profiles (create-mongo-store @db :profile-store)) | ||||
| 
 | ||||
|   ;; ------------------------------ | ||||
|   ;; log all results worth noticing | ||||
|  |  | |||
|  | @ -14,17 +14,18 @@ | |||
|   ([template] (resource template {})) | ||||
|   ([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 head "templates/html_head.html") | ||||
| (defonce head   "templates/html_head.html") | ||||
| (defonce footer "templates/body_footer.html") | ||||
| 
 | ||||
| (defn param [request param] | ||||
|   (let [value | ||||
|         (get-in request | ||||
|                 (conj [:params] param))] | ||||
|   (let [p (if (coll? param) | ||||
|             (into [:params] param) | ||||
|             (conj [:params] param)) | ||||
|         value (get-in request p)] | ||||
|     (if (nil? value) | ||||
|       (f/fail (str "Parameter not found: " param)) | ||||
|       (f/fail (str "Parameter not found: " p)) | ||||
|       value))) | ||||
| 
 | ||||
| ;; TODO: not working? | ||||
|  | @ -70,9 +71,12 @@ | |||
|        (= tclass "is-warning") (log/warn msg) | ||||
|        (= tclass "is-success") (log/info 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 | ||||
| (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 | ||||
|   "render a full webpage using headers, navbar, body and footer" | ||||
|  | @ -84,4 +88,30 @@ | |||
|             (resource head) | ||||
|             (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)) | ||||
|  |  | |||
|  | @ -60,29 +60,31 @@ | |||
|             (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 | ||||
|    [tempfile (s/param request [:file :tempfile]) | ||||
|     filename (s/param request [:file :filename]) | ||||
|     filesize (s/param request [:file :size])] | ||||
|    (cond | ||||
|      (> filesize 64000) | ||||
|     params (log/spy (:params request))] | ||||
|    (if (> (s/param request [:file :size]) 64000) | ||||
|      ;; TODO: put filesize limit in config | ||||
|      (s/notify "File too big in upload (64KB limit)" "is-danger") | ||||
|      :else | ||||
|      (s/error "File too big in upload (64KB limit)") | ||||
|      ;; else | ||||
|      (let [file (io/copy tempfile (io/file "/tmp" filename)) | ||||
|            path (str "/tmp/" filename)] | ||||
|        (io/delete-file tempfile) | ||||
|        (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 | ||||
|          (f/if-let-ok? [newjob (job/add path config account)] | ||||
|            [:div {:class "container"} | ||||
|             [:h1 {:class "title"} "Job uploaded and added"] | ||||
|             [:p "Log messages:"] | ||||
|             (web/render-yaml newjob)] | ||||
|            ;; else when job/add is not-ok | ||||
|            (s/error "Error adding job" newjob))))) | ||||
|          (f/attempt-all | ||||
|           [newjob (job/add path config account)] | ||||
|           [:div {:class "container"} | ||||
|            [:h1 {:class "title"} "Job uploaded and added"] | ||||
|            [:p "Log messages:"] | ||||
|            (web/render-yaml newjob)] | ||||
|           ;; else when job/add is not-ok | ||||
|           (f/when-failed [e] | ||||
|             (s/error "Error adding job" e)) | ||||
|           )))) | ||||
|    (f/when-failed [e] | ||||
|      (s/error "Upload file error" e)))) | ||||
| 
 | ||||
|  | @ -107,7 +109,7 @@ | |||
|     jobfound (db/query @ring/jobs {:jobid jobid}) | ||||
|     r_rmjob (db/delete! @ring/jobs 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] | ||||
|      (s/error "Failure removing job" e)))) | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue