Compare commits

...

20 Commits

Author SHA1 Message Date
f77763887c left note about deleting remote dir on remove jobs 2019-01-28 15:55:16 +01:00
420b230978 joblimit quick fix 2019-01-15 18:20:26 +01:00
d8c1ed992f fix email activation refresh 2019-01-15 18:11:41 +01:00
1835ef8be1 remove linting 2019-01-15 17:34:29 +01:00
032d8beae7 minor updates of deps, versioning and formatting 2018-10-23 08:37:29 +02:00
965e277948 more code cleanup to include standard functions in session
also now using local font awesome 4.7.0
2018-10-14 21:36:03 +02:00
30fb7ae1a2 added profiles, for joblimit and future features
also fixes to file upload, parameter reading and error handling
2018-10-14 19:13:47 +02:00
f28dcb2888 further cleanup and lock-down of required deps for views 2018-10-14 13:26:50 +02:00
687dec948e mustache templating, reorganisation and cleanup of code 2018-10-14 12:53:35 +02:00
527201ad89 improved file upload and font 2018-10-14 09:58:49 +02:00
fa63f76791 added codemirror optional js for syntax highlight of dockerfiles 2018-10-14 09:36:18 +02:00
81e0954a71 more web usage simplification and dashboard in place 2018-10-13 23:30:00 +02:00
de76c4887d deeper refactoring of web and failjure error propagation
this includes improvements that should be backported to the leiningen
template for dyne apps
2018-10-13 13:01:12 +02:00
d162d16116 moar logos to stand on the shoulders of giants 2018-10-13 13:01:12 +02:00
b1bb0a28ec Format the zsh command more nicely. 2018-10-11 14:55:48 +02:00
4da1117a3f Introduce stage3 downloads.
This expands the sync_jobs.py parameter to contain the release codename,
which is respectfully documented in the README.md file.
2018-10-11 14:51:52 +02:00
2be199a214 switched css to bulma, removed all javascript 2018-10-10 02:04:56 +02:00
fe944519ac completed basic gui functionalities linking to run and start 2018-10-08 16:41:55 +02:00
1e60140a6c Correct zshcmd for each of the sdks. 2018-10-08 14:20:49 +02:00
3ab620a588 Remove the blenddir after completing the job. 2018-10-08 14:18:41 +02:00
30 changed files with 11378 additions and 413 deletions

View File

@@ -4,13 +4,23 @@
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
"bulma": {
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/bulma/-/bulma-0.7.2.tgz",
"integrity": "sha512-6JHEu8U/1xsyOst/El5ImLcZIiE2JFXgvrz8GGWbnDLwTNRPJzdAM0aoUM1Ns0avALcVb6KZz9NhzmU53dGDcQ=="
},
"codemirror": {
"version": "5.40.2",
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.40.2.tgz",
"integrity": "sha512-yoWuvEiD3v5vTwdoMc/wu/Ld6dh9K/yEiEBTKOPGM+/pN0gTAqFNtrLHv1IJ1UJvzFpNRvMi92XCi3+8/iIaEw=="
},
"dockerlint": { "dockerlint": {
"version": "0.3.9", "version": "0.3.9",
"resolved": "https://registry.npmjs.org/dockerlint/-/dockerlint-0.3.9.tgz", "resolved": "https://registry.npmjs.org/dockerlint/-/dockerlint-0.3.9.tgz",
"integrity": "sha512-gps1IlRWx0hqhG7qZNYoF/Ae8wpnnPDGV0eYC60FdH2UscS4hZ+NFYX3Pusj/GImjLD/Pxkp/wib7CBb63yzZw==", "integrity": "sha512-gps1IlRWx0hqhG7qZNYoF/Ae8wpnnPDGV0eYC60FdH2UscS4hZ+NFYX3Pusj/GImjLD/Pxkp/wib7CBb63yzZw==",
"requires": { "requires": {
"sty": "0.6.1", "sty": "^0.6.1",
"subarg": "1.0.0" "subarg": "^1.0.0"
} }
}, },
"minimist": { "minimist": {
@@ -28,7 +38,7 @@
"resolved": "https://registry.npmjs.org/subarg/-/subarg-1.0.0.tgz", "resolved": "https://registry.npmjs.org/subarg/-/subarg-1.0.0.tgz",
"integrity": "sha1-9izxdYHplrSPyWVpn1TAauJouNI=", "integrity": "sha1-9izxdYHplrSPyWVpn1TAauJouNI=",
"requires": { "requires": {
"minimist": "1.2.0" "minimist": "^1.1.0"
} }
} }
} }

View File

@@ -25,8 +25,9 @@
[me.raynes/fs "1.4.6"] [me.raynes/fs "1.4.6"]
[me.raynes/conch "0.8.0"] [me.raynes/conch "0.8.0"]
;; time from joda-time ;; time from joda-time
[clj-time "0.14.4"] [clj-time "0.15.0"]
[clojure-humanize "0.2.2"]] [clojure-humanize "0.2.2"]
[de.ubercode.clostache/clostache "1.4.0"]]
:aliases {"test" "midje"} :aliases {"test" "midje"}
:source-paths ["src"] :source-paths ["src"]
:resource-paths ["resources"] :resource-paths ["resources"]
@@ -39,10 +40,12 @@
:uberwar {:init toaster.ring/init :uberwar {:init toaster.ring/init
:handler toaster.handler/app} :handler toaster.handler/app}
:mail toaster.handler :mail toaster.handler
:npm {:dependencies [[dockerlint "0.3.9"]]} :npm {:dependencies [[dockerlint "0.3.9"]
[bulma "0.7.2"]
[codemirror "5.40.2"]]}
:profiles { :dev {:dependencies [[javax.servlet/servlet-api "2.5"] :profiles { :dev {:dependencies [[javax.servlet/servlet-api "2.5"]
[ring/ring-mock "0.3.2"] [ring/ring-mock "0.3.2"]
[midje "1.9.2"]] [midje "1.9.4"]]
:plugins [[lein-midje "3.1.3"] :plugins [[lein-midje "3.1.3"]
[lein-npm "0.6.2"]] [lein-npm "0.6.2"]]
:aot :all :aot :all

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,346 @@
/* BASICS */
.CodeMirror {
/* Set height, width, borders, and global font properties here */
font-family: monospace;
height: 300px;
color: black;
direction: ltr;
}
/* PADDING */
.CodeMirror-lines {
padding: 4px 0; /* Vertical padding around content */
}
.CodeMirror pre {
padding: 0 4px; /* Horizontal padding of content */
}
.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
background-color: white; /* The little square between H and V scrollbars */
}
/* GUTTER */
.CodeMirror-gutters {
border-right: 1px solid #ddd;
background-color: #f7f7f7;
white-space: nowrap;
}
.CodeMirror-linenumbers {}
.CodeMirror-linenumber {
padding: 0 3px 0 5px;
min-width: 20px;
text-align: right;
color: #999;
white-space: nowrap;
}
.CodeMirror-guttermarker { color: black; }
.CodeMirror-guttermarker-subtle { color: #999; }
/* CURSOR */
.CodeMirror-cursor {
border-left: 1px solid black;
border-right: none;
width: 0;
}
/* Shown when moving in bi-directional text */
.CodeMirror div.CodeMirror-secondarycursor {
border-left: 1px solid silver;
}
.cm-fat-cursor .CodeMirror-cursor {
width: auto;
border: 0 !important;
background: #7e7;
}
.cm-fat-cursor div.CodeMirror-cursors {
z-index: 1;
}
.cm-fat-cursor-mark {
background-color: rgba(20, 255, 20, 0.5);
-webkit-animation: blink 1.06s steps(1) infinite;
-moz-animation: blink 1.06s steps(1) infinite;
animation: blink 1.06s steps(1) infinite;
}
.cm-animate-fat-cursor {
width: auto;
border: 0;
-webkit-animation: blink 1.06s steps(1) infinite;
-moz-animation: blink 1.06s steps(1) infinite;
animation: blink 1.06s steps(1) infinite;
background-color: #7e7;
}
@-moz-keyframes blink {
0% {}
50% { background-color: transparent; }
100% {}
}
@-webkit-keyframes blink {
0% {}
50% { background-color: transparent; }
100% {}
}
@keyframes blink {
0% {}
50% { background-color: transparent; }
100% {}
}
/* Can style cursor different in overwrite (non-insert) mode */
.CodeMirror-overwrite .CodeMirror-cursor {}
.cm-tab { display: inline-block; text-decoration: inherit; }
.CodeMirror-rulers {
position: absolute;
left: 0; right: 0; top: -50px; bottom: -20px;
overflow: hidden;
}
.CodeMirror-ruler {
border-left: 1px solid #ccc;
top: 0; bottom: 0;
position: absolute;
}
/* DEFAULT THEME */
.cm-s-default .cm-header {color: blue;}
.cm-s-default .cm-quote {color: #090;}
.cm-negative {color: #d44;}
.cm-positive {color: #292;}
.cm-header, .cm-strong {font-weight: bold;}
.cm-em {font-style: italic;}
.cm-link {text-decoration: underline;}
.cm-strikethrough {text-decoration: line-through;}
.cm-s-default .cm-keyword {color: #708;}
.cm-s-default .cm-atom {color: #219;}
.cm-s-default .cm-number {color: #164;}
.cm-s-default .cm-def {color: #00f;}
.cm-s-default .cm-variable,
.cm-s-default .cm-punctuation,
.cm-s-default .cm-property,
.cm-s-default .cm-operator {}
.cm-s-default .cm-variable-2 {color: #05a;}
.cm-s-default .cm-variable-3, .cm-s-default .cm-type {color: #085;}
.cm-s-default .cm-comment {color: #a50;}
.cm-s-default .cm-string {color: #a11;}
.cm-s-default .cm-string-2 {color: #f50;}
.cm-s-default .cm-meta {color: #555;}
.cm-s-default .cm-qualifier {color: #555;}
.cm-s-default .cm-builtin {color: #30a;}
.cm-s-default .cm-bracket {color: #997;}
.cm-s-default .cm-tag {color: #170;}
.cm-s-default .cm-attribute {color: #00c;}
.cm-s-default .cm-hr {color: #999;}
.cm-s-default .cm-link {color: #00c;}
.cm-s-default .cm-error {color: #f00;}
.cm-invalidchar {color: #f00;}
.CodeMirror-composing { border-bottom: 2px solid; }
/* Default styles for common addons */
div.CodeMirror span.CodeMirror-matchingbracket {color: #0b0;}
div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #a22;}
.CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); }
.CodeMirror-activeline-background {background: #e8f2ff;}
/* STOP */
/* The rest of this file contains styles related to the mechanics of
the editor. You probably shouldn't touch them. */
.CodeMirror {
position: relative;
overflow: hidden;
background: white;
}
.CodeMirror-scroll {
overflow: scroll !important; /* Things will break if this is overridden */
/* 30px is the magic margin used to hide the element's real scrollbars */
/* See overflow: hidden in .CodeMirror */
margin-bottom: -30px; margin-right: -30px;
padding-bottom: 30px;
height: 100%;
outline: none; /* Prevent dragging from highlighting the element */
position: relative;
}
.CodeMirror-sizer {
position: relative;
border-right: 30px solid transparent;
}
/* The fake, visible scrollbars. Used to force redraw during scrolling
before actual scrolling happens, thus preventing shaking and
flickering artifacts. */
.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
position: absolute;
z-index: 6;
display: none;
}
.CodeMirror-vscrollbar {
right: 0; top: 0;
overflow-x: hidden;
overflow-y: scroll;
}
.CodeMirror-hscrollbar {
bottom: 0; left: 0;
overflow-y: hidden;
overflow-x: scroll;
}
.CodeMirror-scrollbar-filler {
right: 0; bottom: 0;
}
.CodeMirror-gutter-filler {
left: 0; bottom: 0;
}
.CodeMirror-gutters {
position: absolute; left: 0; top: 0;
min-height: 100%;
z-index: 3;
}
.CodeMirror-gutter {
white-space: normal;
height: 100%;
display: inline-block;
vertical-align: top;
margin-bottom: -30px;
}
.CodeMirror-gutter-wrapper {
position: absolute;
z-index: 4;
background: none !important;
border: none !important;
}
.CodeMirror-gutter-background {
position: absolute;
top: 0; bottom: 0;
z-index: 4;
}
.CodeMirror-gutter-elt {
position: absolute;
cursor: default;
z-index: 4;
}
.CodeMirror-gutter-wrapper ::selection { background-color: transparent }
.CodeMirror-gutter-wrapper ::-moz-selection { background-color: transparent }
.CodeMirror-lines {
cursor: text;
min-height: 1px; /* prevents collapsing before first draw */
}
.CodeMirror pre {
/* Reset some styles that the rest of the page might have set */
-moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
border-width: 0;
background: transparent;
font-family: inherit;
font-size: inherit;
margin: 0;
white-space: pre;
word-wrap: normal;
line-height: inherit;
color: inherit;
z-index: 2;
position: relative;
overflow: visible;
-webkit-tap-highlight-color: transparent;
-webkit-font-variant-ligatures: contextual;
font-variant-ligatures: contextual;
}
.CodeMirror-wrap pre {
word-wrap: break-word;
white-space: pre-wrap;
word-break: normal;
}
.CodeMirror-linebackground {
position: absolute;
left: 0; right: 0; top: 0; bottom: 0;
z-index: 0;
}
.CodeMirror-linewidget {
position: relative;
z-index: 2;
padding: 0.1px; /* Force widget margins to stay inside of the container */
}
.CodeMirror-widget {}
.CodeMirror-rtl pre { direction: rtl; }
.CodeMirror-code {
outline: none;
}
/* Force content-box sizing for the elements where we expect it */
.CodeMirror-scroll,
.CodeMirror-sizer,
.CodeMirror-gutter,
.CodeMirror-gutters,
.CodeMirror-linenumber {
-moz-box-sizing: content-box;
box-sizing: content-box;
}
.CodeMirror-measure {
position: absolute;
width: 100%;
height: 0;
overflow: hidden;
visibility: hidden;
}
.CodeMirror-cursor {
position: absolute;
pointer-events: none;
}
.CodeMirror-measure pre { position: static; }
div.CodeMirror-cursors {
visibility: hidden;
position: relative;
z-index: 3;
}
div.CodeMirror-dragcursors {
visibility: visible;
}
.CodeMirror-focused div.CodeMirror-cursors {
visibility: visible;
}
.CodeMirror-selected { background: #d9d9d9; }
.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
.CodeMirror-crosshair { cursor: crosshair; }
.CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; }
.CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; }
.cm-searching {
background-color: #ffa;
background-color: rgba(255, 255, 0, .4);
}
/* Used to force a border model for a node */
.cm-force-border { padding-right: .1px; }
@media print {
/* Hide the cursor when printing */
.CodeMirror div.CodeMirror-cursors {
visibility: hidden;
}
}
/* See issue #2901 */
.cm-tab-wrap-hack:after { content: ''; }
/* Help users use markselection to safely style text background */
span.CodeMirror-selectedtext { background: none; }

File diff suppressed because one or more lines are too long

View File

@@ -1,9 +1,10 @@
body { body {
font-family: "arial"; /* padding-top: 65px; */
font-size: 22px; }
font-smooth: always;
-webkit-font-smoothing: antialiased; /* horizontal margins on wide screens */
padding-top: 65px; @media (min-width: 768px) {
.container { width: 36em; }
} }
code { code {
@@ -31,93 +32,102 @@ code {
h1, h2, h3, h4 { } h1, h2, h3, h4 { }
html,body {
font-family: 'Ubuntu', sans-serif;
font-size: 14px;
font-weight: 300;
font-smooth: always;
-webkit-font-smoothing: antialiased;
}
.hero.is-success {
background: #F2F6FA;
}
.hero .nav, .hero.is-success .nav {
-webkit-box-shadow: none;
box-shadow: none;
}
.box {
margin-top: 5rem;
}
.avatar {
margin-top: -70px;
padding-bottom: 20px;
}
.avatar img {
padding: 5px;
background: #fff;
border-radius: 50%;
-webkit-box-shadow: 0 2px 3px rgba(10,10,10,.1), 0 0 0 1px rgba(10,10,10,.1);
box-shadow: 0 2px 3px rgba(10,10,10,.1), 0 0 0 1px rgba(10,10,10,.1);
}
input {
font-weight: 300;
}
p { p {
letter-spacing: 0.01rem; font-weight: 700;
font-style: normal; }
line-height: 1.5; p.subtitle {
padding-top: 1rem;
} }
img { /* inputfile js for bulma from https://jsfiddle.net/chintanbanugaria/uzva5byy/ */
width: auto;
height: auto; .inputfile {
max-width: 100%; width: 0.1px;
vertical-align: middle; height: 0.1px;
overflow: hidden; opacity: 0;
overflow: hidden;
z-index: -1;
margin-top:10px;
} }
.inputfile + label {
/* Sortable tables */
table.sortable thead { /* 20px */
background-color:#eee; width: 50%;
color:#666666; text-overflow: ellipsis;
font-weight: bold; white-space: nowrap;
cursor: default; cursor: pointer;
display: inline-block;
overflow: hidden;
padding-left: 0.75em;
padding-right: 0.75em;
border: 1px solid #dbdbdb;
height: 2.285em;
box-shadow: inset 0 1px 2px rgba(10, 10, 10, 0.1);
line-height: 1.5rem;
border-radius: 3px;
padding-top: 0.3em;
/* 10px 20px */
} }
.no-js .inputfile + label {
table.sortable td { display: none;
font-size: .8em;
font-family: "arial"
} }
.inputfile:focus + label,
.input-form { .inputfile.has-focus + label {
clear: right; border-color: #00d1b2;
} }
.inputfile + label * {
li { /* pointer-events: none; */
/* padding: .5em; */ /* in case of FastClick lib use */
} }
.inputfile + label svg {
.secrets { width: 1em;
padding: 1em; height: 1em;
line-height: 1.5rem;
vertical-align: middle;
margin-top: -0.25em;
/* 4px */
margin-right: 0.25em;
/* 4px */
} }
.inputfile-2 + label {
.password { border: 1px solid #dbdbdb;
font-size: 1.5;
padding: 1em;
/* display: inline-block; */
} }
.inputfile-2:focus + label,
.inputfile-2.has-focus + label,
.navbar-text { .inputfile-2 + label:hover {
margin-top: 22px; border-color: #00d1b2;
}
.password .content {
font-size: 1em;
}
.slices {
}
.slices ul {
list-style: none;
}
.slices .content {
font-size: .5em;
}
.qrcode {
float: left;
margin-left: -19px;
}
.card {
border: solid 3px #888;
border-radius: 5px;
padding: 1.5em 2.2em .8em 2.2em;
}
.card .gravatar {
margin-top: 19px;
}
.balance {
font-size: 2em;
margin: 1em 0;
}
.wallet-details {
display: inline-block;
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View File

@@ -0,0 +1,211 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"), require("../../addon/mode/simple"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror", "../../addon/mode/simple"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
var from = "from";
var fromRegex = new RegExp("^(\\s*)\\b(" + from + ")\\b", "i");
var shells = ["run", "cmd", "entrypoint", "shell"];
var shellsAsArrayRegex = new RegExp("^(\\s*)(" + shells.join('|') + ")(\\s+\\[)", "i");
var expose = "expose";
var exposeRegex = new RegExp("^(\\s*)(" + expose + ")(\\s+)", "i");
var others = [
"arg", "from", "maintainer", "label", "env",
"add", "copy", "volume", "user",
"workdir", "onbuild", "stopsignal", "healthcheck", "shell"
];
// Collect all Dockerfile directives
var instructions = [from, expose].concat(shells).concat(others),
instructionRegex = "(" + instructions.join('|') + ")",
instructionOnlyLine = new RegExp("^(\\s*)" + instructionRegex + "(\\s*)(#.*)?$", "i"),
instructionWithArguments = new RegExp("^(\\s*)" + instructionRegex + "(\\s+)", "i");
CodeMirror.defineSimpleMode("dockerfile", {
start: [
// Block comment: This is a line starting with a comment
{
regex: /^\s*#.*$/,
sol: true,
token: "comment"
},
{
regex: fromRegex,
token: [null, "keyword"],
sol: true,
next: "from"
},
// Highlight an instruction without any arguments (for convenience)
{
regex: instructionOnlyLine,
token: [null, "keyword", null, "error"],
sol: true
},
{
regex: shellsAsArrayRegex,
token: [null, "keyword", null],
sol: true,
next: "array"
},
{
regex: exposeRegex,
token: [null, "keyword", null],
sol: true,
next: "expose"
},
// Highlight an instruction followed by arguments
{
regex: instructionWithArguments,
token: [null, "keyword", null],
sol: true,
next: "arguments"
},
{
regex: /./,
token: null
}
],
from: [
{
regex: /\s*$/,
token: null,
next: "start"
},
{
// Line comment without instruction arguments is an error
regex: /(\s*)(#.*)$/,
token: [null, "error"],
next: "start"
},
{
regex: /(\s*\S+\s+)(as)/i,
token: [null, "keyword"],
next: "start"
},
// Fail safe return to start
{
token: null,
next: "start"
}
],
single: [
{
regex: /(?:[^\\']|\\.)/,
token: "string"
},
{
regex: /'/,
token: "string",
pop: true
}
],
double: [
{
regex: /(?:[^\\"]|\\.)/,
token: "string"
},
{
regex: /"/,
token: "string",
pop: true
}
],
array: [
{
regex: /\]/,
token: null,
next: "start"
},
{
regex: /"(?:[^\\"]|\\.)*"?/,
token: "string"
}
],
expose: [
{
regex: /\d+$/,
token: "number",
next: "start"
},
{
regex: /[^\d]+$/,
token: null,
next: "start"
},
{
regex: /\d+/,
token: "number"
},
{
regex: /[^\d]+/,
token: null
},
// Fail safe return to start
{
token: null,
next: "start"
}
],
arguments: [
{
regex: /^\s*#.*$/,
sol: true,
token: "comment"
},
{
regex: /"(?:[^\\"]|\\.)*"?$/,
token: "string",
next: "start"
},
{
regex: /"/,
token: "string",
push: "double"
},
{
regex: /'(?:[^\\']|\\.)*'?$/,
token: "string",
next: "start"
},
{
regex: /'/,
token: "string",
push: "single"
},
{
regex: /[^#"']+[\\`]$/,
token: null
},
{
regex: /[^#"']+$/,
token: null,
next: "start"
},
{
regex: /[^#"']+/,
token: null
},
// Fail safe return to start
{
token: null,
next: "start"
}
],
meta: {
lineComment: "#"
}
});
CodeMirror.defineMIME("text/x-dockerfile", "dockerfile");
});

View File

@@ -0,0 +1,216 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
CodeMirror.defineSimpleMode = function(name, states) {
CodeMirror.defineMode(name, function(config) {
return CodeMirror.simpleMode(config, states);
});
};
CodeMirror.simpleMode = function(config, states) {
ensureState(states, "start");
var states_ = {}, meta = states.meta || {}, hasIndentation = false;
for (var state in states) if (state != meta && states.hasOwnProperty(state)) {
var list = states_[state] = [], orig = states[state];
for (var i = 0; i < orig.length; i++) {
var data = orig[i];
list.push(new Rule(data, states));
if (data.indent || data.dedent) hasIndentation = true;
}
}
var mode = {
startState: function() {
return {state: "start", pending: null,
local: null, localState: null,
indent: hasIndentation ? [] : null};
},
copyState: function(state) {
var s = {state: state.state, pending: state.pending,
local: state.local, localState: null,
indent: state.indent && state.indent.slice(0)};
if (state.localState)
s.localState = CodeMirror.copyState(state.local.mode, state.localState);
if (state.stack)
s.stack = state.stack.slice(0);
for (var pers = state.persistentStates; pers; pers = pers.next)
s.persistentStates = {mode: pers.mode,
spec: pers.spec,
state: pers.state == state.localState ? s.localState : CodeMirror.copyState(pers.mode, pers.state),
next: s.persistentStates};
return s;
},
token: tokenFunction(states_, config),
innerMode: function(state) { return state.local && {mode: state.local.mode, state: state.localState}; },
indent: indentFunction(states_, meta)
};
if (meta) for (var prop in meta) if (meta.hasOwnProperty(prop))
mode[prop] = meta[prop];
return mode;
};
function ensureState(states, name) {
if (!states.hasOwnProperty(name))
throw new Error("Undefined state " + name + " in simple mode");
}
function toRegex(val, caret) {
if (!val) return /(?:)/;
var flags = "";
if (val instanceof RegExp) {
if (val.ignoreCase) flags = "i";
val = val.source;
} else {
val = String(val);
}
return new RegExp((caret === false ? "" : "^") + "(?:" + val + ")", flags);
}
function asToken(val) {
if (!val) return null;
if (val.apply) return val
if (typeof val == "string") return val.replace(/\./g, " ");
var result = [];
for (var i = 0; i < val.length; i++)
result.push(val[i] && val[i].replace(/\./g, " "));
return result;
}
function Rule(data, states) {
if (data.next || data.push) ensureState(states, data.next || data.push);
this.regex = toRegex(data.regex);
this.token = asToken(data.token);
this.data = data;
}
function tokenFunction(states, config) {
return function(stream, state) {
if (state.pending) {
var pend = state.pending.shift();
if (state.pending.length == 0) state.pending = null;
stream.pos += pend.text.length;
return pend.token;
}
if (state.local) {
if (state.local.end && stream.match(state.local.end)) {
var tok = state.local.endToken || null;
state.local = state.localState = null;
return tok;
} else {
var tok = state.local.mode.token(stream, state.localState), m;
if (state.local.endScan && (m = state.local.endScan.exec(stream.current())))
stream.pos = stream.start + m.index;
return tok;
}
}
var curState = states[state.state];
for (var i = 0; i < curState.length; i++) {
var rule = curState[i];
var matches = (!rule.data.sol || stream.sol()) && stream.match(rule.regex);
if (matches) {
if (rule.data.next) {
state.state = rule.data.next;
} else if (rule.data.push) {
(state.stack || (state.stack = [])).push(state.state);
state.state = rule.data.push;
} else if (rule.data.pop && state.stack && state.stack.length) {
state.state = state.stack.pop();
}
if (rule.data.mode)
enterLocalMode(config, state, rule.data.mode, rule.token);
if (rule.data.indent)
state.indent.push(stream.indentation() + config.indentUnit);
if (rule.data.dedent)
state.indent.pop();
var token = rule.token
if (token && token.apply) token = token(matches)
if (matches.length > 2 && rule.token && typeof rule.token != "string") {
state.pending = [];
for (var j = 2; j < matches.length; j++)
if (matches[j])
state.pending.push({text: matches[j], token: rule.token[j - 1]});
stream.backUp(matches[0].length - (matches[1] ? matches[1].length : 0));
return token[0];
} else if (token && token.join) {
return token[0];
} else {
return token;
}
}
}
stream.next();
return null;
};
}
function cmp(a, b) {
if (a === b) return true;
if (!a || typeof a != "object" || !b || typeof b != "object") return false;
var props = 0;
for (var prop in a) if (a.hasOwnProperty(prop)) {
if (!b.hasOwnProperty(prop) || !cmp(a[prop], b[prop])) return false;
props++;
}
for (var prop in b) if (b.hasOwnProperty(prop)) props--;
return props == 0;
}
function enterLocalMode(config, state, spec, token) {
var pers;
if (spec.persistent) for (var p = state.persistentStates; p && !pers; p = p.next)
if (spec.spec ? cmp(spec.spec, p.spec) : spec.mode == p.mode) pers = p;
var mode = pers ? pers.mode : spec.mode || CodeMirror.getMode(config, spec.spec);
var lState = pers ? pers.state : CodeMirror.startState(mode);
if (spec.persistent && !pers)
state.persistentStates = {mode: mode, spec: spec.spec, state: lState, next: state.persistentStates};
state.localState = lState;
state.local = {mode: mode,
end: spec.end && toRegex(spec.end),
endScan: spec.end && spec.forceEnd !== false && toRegex(spec.end, false),
endToken: token && token.join ? token[token.length - 1] : token};
}
function indexOf(val, arr) {
for (var i = 0; i < arr.length; i++) if (arr[i] === val) return true;
}
function indentFunction(states, meta) {
return function(state, textAfter, line) {
if (state.local && state.local.mode.indent)
return state.local.mode.indent(state.localState, textAfter, line);
if (state.indent == null || state.local || meta.dontIndentStates && indexOf(state.state, meta.dontIndentStates) > -1)
return CodeMirror.Pass;
var pos = state.indent.length - 1, rules = states[state.state];
scan: for (;;) {
for (var i = 0; i < rules.length; i++) {
var rule = rules[i];
if (rule.data.dedent && rule.data.dedentIfLineStart !== false) {
var m = rule.regex.exec(textAfter);
if (m && m[0]) {
pos--;
if (rule.next || rule.push) rules = states[rule.next || rule.push];
textAfter = textAfter.slice(m[0].length);
continue scan;
}
}
}
break;
}
return pos < 0 ? 0 : state.indent[pos];
};
}
});

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,24 @@
<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" enctype="multipart/form-data" method="post">
<div class="has-label is-fullwidth">
<label class="file-label">
<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>
<div class="field">
<div class="control">
<input class="button is-block is-info is-large is-fullwidth" name="submit" type="submit" value="submit" />
</div></div>
</form>
<script>
var file = document.getElementById("file");
file.onchange = function(){
if(file.files.length > 0) {
document.getElementById('filename').innerHTML = file.files[0].name;
}
};
</script>
</div>

View File

@@ -0,0 +1,28 @@
<footer class="footer">
<div class="content has-text-centered">
<p class="has-text-grey">toaster.do transforms your Docker prototype into an installable <a href="https://devuan.org">Devuan GNU+Linux</a> image, choose any supported target architecture.</p>
<div class="columns is-variable is-8">
<div class="column is-one-fifth">
<figure class="image"><img alt="European project DECODE (H2020 nr. 732546)" src="/static/img/ec_logo.png" /></figure>
</div>
<div class="column is-one-fifth">
<a href="https://decodeproject.eu">
<figure class="image"><img alt="DECODE project" src="/static/img/decode_logo.png" /></figure>
</a>
</div>
<div class="column is-one-fifth">
<a href="https://www.dyne.org">
<figure class="image"><img alt="Software by Dyne.org" src="/static/img/swbydyne.png" title="Software by Dyne.org" /></figure>
</a>
</div>
<div class="column is-one-fifth">
<a href="https://devuan.org">
<figure class="image"><img alt="powered by Devuan GNU+Linux" src="/static/img/devuan_logo.png" /></figure>
</a>
</div>
<div class="column is-one-fifth">
<figure class="image"><img alt="Affero GPLv3 License" src="/static/img/AGPLv3.png" title="Affero GPLv3 License" /></figure>
</div>
</div>
</div>
</footer>

View File

@@ -0,0 +1,27 @@
<section class="hero is-fullheight">
<div class="hero-body">
<div class="container has-text-centered">
<div class="column is-4 is-offset-4">
<h3 class="title has-text-grey">toaster.do</h3>
<h4 class="subtitle has-text-grey">from Docker to VM in a few clicks, powered by <a href="https://decodeproject.eu">DECODEproject.EU</a></h4>
<p class="subtitle has-text-grey">please login to operate</p>
<div class="box">
<figure class="avatar"><img src="/static/img/cafudda.jpg" /></figure>
<form action="/login" method="post">
<div class="field">
<div class="control has-icons-left">
<input class="input is-large" name="username" placeholder="Email" type="email" /><span class="icon is-small is-left"><i class="fa fa-envelope fa-xs"></i></span></div>
</div>
<div class="field">
<div class="control has-icons-left"><input class="input is-large" name="password" placeholder="Password" type="password" /><span class="icon is-small is-left"><i class="fa fa-lock fa-xs"></i></span></div>
</div>
<div class="field">
<p class="control"><input class="button is-block is-info is-large has-icons-left is-fullwidth" type="submit" value="Login" /></p>
</div>
</form>
</div>
<p class="subtitle has-text-grey">...or <a href="/signup">signup for a new account</a></p>
</div>
</div>
</div>
</section>

View File

@@ -0,0 +1,33 @@
<section class="hero is-fullheight">
<div class="hero-body">
<div class="container has-text-centered">
<div class="column is-4 is-offset-4">
<h3 class="title has-text-grey">toaster.do</h3>
<h4 class="subtitle has-text-grey">sign up for a new account</h4>
<div class="box">
<figure class="avatar"><img src="/static/img/cafudda.jpg" /></figure>
<form action="/signup" method="post">
<div class="field">
<div class="control"><input class="input" name="name" placeholder="Name" type="text" /></div>
</div>
<div class="field">
<div class="control"><input class="input" name="email" placeholder="Email" type="email" /></div>
</div>
<div class="field">
<div class="control"><input class="input" name="password" placeholder="Password" type="password" /></div>
</div>
<div class="field">
<div class="control"><input class="input" name="repeat-password" placeholder="Repeat password" type="password" /></div>
</div>
<div class="field">
<div class="control"><input class="button is-block is-info is-large is-fullwidth" type="submit" value="Sign In" /></div>
</div>
</form>
</div>
<p class="subtitle has-text-grey">...or <a href="/login">login with an existing account</a></p>
</div>
</div>
</div>
</section>

View File

@@ -0,0 +1,15 @@
<head>
<meta charset="utf-8" />
<meta content="IE=edge,chrome=1" http-equiv="X-UA-Compatible" />
<meta content="width=device-width, initial-scale=1" name="viewport" />
<title>toaster.do :: rapid integration for decentralized services, from embedded to the cloud</title>
<link href="https://fonts.googleapis.com/css?family=Ubuntu" rel="stylesheet" />
<script src="/static/js/codemirror.js" type="text/javascript"></script>
<script src="/static/js/codemirror-simple.js" type="text/javascript"></script>
<script src="/static/js/codemirror-dockerfile.js" type="text/javascript"></script>
<link href="/static/css/bulma.min.css" rel="stylesheet" type="text/css" />
<link href="/static/css/json-html.css" rel="stylesheet" type="text/css" />
<link href="/static/css/codemirror.css" rel="stylesheet" type="text/css" />
<link href="/static/css/font-awesome.min.css" rel="stylesheet" type="text/css" />
<link href="/static/css/toaster.css" rel="stylesheet" type="text/css" />
</head>

View File

@@ -0,0 +1,110 @@
;; Copyright (C) 2015-2018 Dyne.org foundation
;; Sourcecode designed, written and maintained by
;; Denis Roio <jaromil@dyne.org>
;; This program is free software: you can redistribute it and/or modify
;; it under the terms of the GNU Affero General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU Affero General Public License for more details.
;; You should have received a copy of the GNU Affero General Public License
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
(ns toaster.bulma
(:require [clojure.java.io :as io]
[clojure.data.csv :as csv]
[taoensso.timbre :as log]
[yaml.core :as yaml]
[hiccup.page :as page]
[hiccup.form :as hf]))
(defn button
([url text] (button url text [:p]))
([url text field] (button url text field "button"))
([url text field type]
(hf/form-to [:post url]
field ;; can be an hidden key/value field (project,
;; person, etc using hf/hidden-field)
[:p {:class "control"}
(hf/submit-button {:class (str "button " type)} text)])))
(defn button-cancel-submit [argmap]
[:div
{:class
(str "row col-md-6 btn-group btn-group-lg "
(:btn-group-class argmap))
:role "group"}
(button
(:cancel-url argmap) "Cancel"
(:cancel-params argmap)
"btn-primary btn-lg btn-danger col-md-3")
(button
(:submit-url argmap) "Submit"
(:submit-params argmap)
"btn-primary btn-lg btn-success col-md-3")])
;; highlight functions do no conversion, take the format they highlight
;; render functions take edn and convert to the highlight format
;; download functions all take an edn and convert it in target format
;; edit functions all take an edn and present an editor in the target format
(defn render-yaml
"renders an edn into an highlighted yaml"
[data]
[:span
[:pre [:code {:class "yaml"}
(yaml/generate-string data)]]
[:script "hljs.initHighlightingOnLoad();"]])
(defn highlight-yaml
"renders a yaml text in highlighted html"
[data]
[:span
[:pre [:code {:class "yaml"}
data]]
[:script "hljs.initHighlightingOnLoad();"]])
(defn highlight-json
"renders a json text in highlighted html"
[data]
[:span
[:pre [:code {:class "json"}
data]]
[:script "hljs.initHighlightingOnLoad();"]])
(defn download-csv
"takes an edn, returns a csv plain/text for download"
[data]
{:headers {"Content-Type"
"text/plain; charset=utf-8"}
:body (with-out-str (csv/write-csv *out* data))})
(defn edit-edn
"renders an editor for the edn in yaml format"
[data]
[:div;; {:class "form-group"}
[:textarea {:class "form-control"
:rows "20" :data-editor "yaml"
:id "config" :name "editor"}
(yaml/generate-string data)]
[:script {:src "/static/js/ace.js"
:type "text/javascript" :charset "utf-8"}]
[:script {:type "text/javascript"}
(slurp (io/resource "public/static/js/ace-embed.js"))]
;; code to embed the ace editor on all elements in page
;; that contain the attribute "data-editor" set to the
;; mode language of choice
[:input {:class "btn btn-success btn-lg pull-top"
:type "submit" :value "submit"}]])
(def brand-img "/static/img/cafudda.jpg")

View File

@@ -18,152 +18,167 @@
(ns toaster.handler (ns toaster.handler
(:require (:require
[clojure.string :as str] [clojure.string :as str]
[clojure.java.io :as io] [clojure.java.io :as io]
[clojure.data.json :as json] [clojure.data.json :as json]
[compojure.core :refer :all] [compojure.core :refer :all]
[compojure.handler :refer :all] [compojure.handler :refer :all]
[compojure.route :as route] [compojure.route :as route]
[compojure.response :as response] [compojure.response :as response]
[ring.adapter.jetty :refer :all] [ring.adapter.jetty :refer :all]
[ring.middleware.session :refer :all] [ring.middleware.session :refer :all]
[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]]
[failjure.core :as f] [failjure.core :as f]
[taoensso.timbre :as log] [taoensso.timbre :as log]
[just-auth.core :as auth] [just-auth.core :as auth]
[toaster.session :as s] [toaster.session :as s]
[toaster.config :as conf] [toaster.config :as conf]
[toaster.webpage :as web] [toaster.ring :as ring]
[toaster.ring :as ring] [toaster.views :as views]
[toaster.views :as views] [toaster.profiles :as profile])
[toaster.jobs :as job])
(:gen-class)) (:gen-class))
(defonce config (conf/load-config "toaster" conf/default-settings)) (defonce config (conf/load-config "toaster" conf/default-settings))
(defn auth-wrap
"Comfortably wrap routes using a function pointer to be called if an
account is correctly authenticated in the currently running session.
(fun) is passed 3 hash-maps args: request, config and account."
[request fun]
(f/attempt-all
[db (s/check-database)
config (s/check-config request)
account (if (conf/q config [:webserver :mock-auth])
{:email "mock@dyne.org"
:name "MockUser"
:activated true}
;; else
(s/check-account request))]
(fun request config account)
(f/when-failed [e]
(s/render [:body
(s/notify (f/message e) "is-error")
(s/resource s/login)]))))
(defroutes (defroutes
app-routes app-routes
(GET "/" request (web/render "Hello World!")) ;; web/readme)) (GET "/" request
(f/attempt-all
[db (s/check-database)
conf (s/check-config request)]
(f/if-let-ok? [account (s/check-account request)]
(s/render [:body (views/dashboard account)])
;; else
(s/render [:body (s/resource s/login)]))
(f/when-failed[e]
(s/render [:body (s/error "backend missing" e)]))))
;; NEW ROUTES HERE ;; NEW ROUTES HERE
(GET "/upload" request
(->> (fn [req conf acct]
(web/render acct views/dockerfile-upload-form))
(s/check request)))
(POST "/dockerfile" request (POST "/dockerfile" request
(->> views/dockerfile-upload-post (->> (fn [req conf acct]
(s/check request))) (s/render acct
[:body
(s/upload req conf acct views/add-job)
(views/dashboard acct)]))
(auth-wrap request)))
(GET "/edit" request (POST "/remove" request
(->> (fn [req conf acct] (->> (fn [req conf acct]
(web/render acct views/dockerfile-edit-form)) (s/render acct [:body
(s/check request))) (views/remove-job req conf acct)
(views/dashboard acct)]))
(auth-wrap request)))
(GET "/list" request (POST "/start" request
(->> views/list-jobs (->> (fn [req conf acct]
(s/check request))) (s/render acct [:body
(views/start-job req conf acct)
(views/dashboard acct)]))
(auth-wrap request)))
(POST "/view" request
(->> (fn [req conf acct]
(s/render acct [:body
(views/view-job req conf acct)
(views/dashboard acct)]))
(auth-wrap request)))
(GET "/error" request
(->> (fn [req conf acct]
(s/render acct [:body
(s/notify "Generic Error Page" "is-error")
(views/dashboard acct)]))
(auth-wrap request)))
;; JUST-AUTH ROUTES ;; JUST-AUTH ROUTES
(GET "/login" request (GET "/login" request (s/render-template s/login))
(f/attempt-all
[acct (s/check-account request)]
(web/render acct
[:div
[:h1 (str "Already logged in with account: "
(:email acct))]
[:h2 [:a {:href "/logout"} "Logout"]]])
(f/when-failed [e]
(web/render web/login-form))))
(POST "/login" request (POST "/login" request
(f/attempt-all (f/attempt-all
[username (s/param request :username) [username (s/param request :username)
password (s/param request :password) password (s/param request :password)
logged (auth/sign-in logged (auth/sign-in
@ring/auth username password {})] @ring/auth username password {})]
;; TODO: pass :ip-address in last argument map ;; TODO: pass :ip-address in last argument map
(let [session {:session {:config config (let [session {:session {:config config
:auth logged}}] :auth logged}}]
(conj session (conj session
(views/list-jobs request config logged))) (s/render logged [:body (views/dashboard logged)])))
;; (web/render (f/when-failed [e]
;; logged (s/render
;; [:div [:body
;; [:h1 "Logged in: " username] (s/error "Login failed" e)]))))
;; views/welcome-menu])))
(f/when-failed [e]
(web/render-error-page
(str "Login failed: " (f/message e))))))
(GET "/session" request
(-> (:session request) web/render-yaml web/render))
(GET "/logout" request (GET "/logout" request
(conj {:session {:config config}} (conj {:session {:config config}}
(web/render [:h1 "Logged out."]))) (s/render [:body
(GET "/signup" request [:h1 {:class "title"} "Logged out."]])))
(web/render web/signup-form))
(GET "/signup" request (s/render-template s/signup))
(POST "/signup" request (POST "/signup" request
(f/attempt-all (s/adduser request
[name (s/param request :name) (fn [name email]
email (s/param request :email) (s/render
password (s/param request :password) [:body
repeat-password (s/param request :repeat-password) (s/notify (str "Account created: "
activation {:activation-uri name " &lt;" email "&gt;") "is-success")
(get-in request [:headers "host"])}] [:h1 {:class "title"} "Check email for activation."]]))))
(web/render
(if (= password repeat-password)
(f/try*
(f/if-let-ok?
[signup (auth/sign-up @ring/auth
name
email
password
activation
[])]
[:div
[:h2 (str "Account created: "
name " &lt;" email "&gt;")]
[:h3 "Account pending activation."]]
(web/render-error
(str "Failure creating account: "
(f/message signup)))))
(web/render-error
"Repeat password didnt match")))
(f/when-failed [e]
(web/render-error-page
(str "Sign-up failure: " (f/message e))))))
(GET "/activate/:email/:activation-id" (GET "/activate/:email/:activation-id"
[email activation-id :as request] [email activation-id :as request]
(let [activation-uri (let [activation-uri
(str "http://" (str "http://"
(get-in request [:headers "host"]) (get-in request [:headers "host"])
"/activate/" email "/" activation-id)] "/activate/" email "/" activation-id)]
(web/render (f/attempt-all
[:div [act (auth/activate-account
(f/if-let-failed? @ring/auth email
[act (auth/activate-account {:activation-link activation-uri})]
@ring/auth email (s/render email
{:activation-link activation-uri})] [:body
(web/render-error (s/notify "Account succesfully activated" "is-success")
[:div (views/dashboard email)])
[:h1 "Failure activating account"] (f/when-failed [e]
[:h2 (f/message act)] (s/render
[:p (str "Email: " email " activation-id: " activation-id)]]) [:body
[:h1 (str "Account activated - " email)])]))) (s/error "Failure activating account" e)])))))
;; -- end of JUST-AUTH ;; -- end of JUST-AUTH
(POST "/" request (POST "/" request
;; generic endpoint for canceled operations ;; generic endpoint for canceled operations
(web/render (s/check-account request) (s/render (s/check-account request)
[:div {:class (str "alert alert-danger") :role "alert"} (s/notify
(s/param request :message)])) (s/param request :message) "is-error")))
(route/resources "/") (route/resources "/")
(route/not-found (web/render-error-page "Page Not Found")) (route/not-found (s/render [:body (s/notify "Page Not Found" "is-error")]))
) ;; end of routes ) ;; end of routes
(def app (def app
@@ -180,7 +195,7 @@
(defn -main [] (defn -main []
(println "Starting ring server") (println "Starting ring server")
(ring/init ring/app-defaults) (ring/init ring/app-defaults)
;(run-jetty app {:port 6060 ;(run-jetty app {:port 6060
; :host "localhost" ; :host "localhost"
; :join? true}) ; :join? true})
) )

View File

@@ -1,24 +1,39 @@
(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.webpage :as web] [toaster.ring :refer [jobs]]
[toaster.config :refer :all] [toaster.profiles :as profile]
[toaster.ring :refer [jobs]] [hiccup.form :as hf]))
[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"
(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])))
(defn- sync_jobs [config arg1 arg2] (defn sync_jobs [config arg1 arg2]
(with-programs (with-programs
[ssh] [ssh]
(try (try
@@ -26,46 +41,50 @@
(ssh-host config) "sync_jobs.py" (ssh-host config) "sync_jobs.py"
arg1 arg2) arg1 arg2)
(str/split #"\n")) (str/split #"\n"))
(catch Exception e (f/fail (str "ERROR in sync_jobs.py - " (.getMessage e))))))) (catch Exception e (f/fail (str "ERROR in sync_jobs.py "
arg1 " " arg2 " - " (.getMessage e)))))))
(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-" 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)) [ ;; r_lint (dockerlint path)
r_job (log/spy (sync_jobs config "-a" jobname)) r_mkdir (ssh "-i" (q config [:jenkins :key])
r_store (log/spy (db/store! (ssh-host config) "mkdir" "-p" jobdir)
@jobs :jobid r_scp (scp "-i" (q config [:jenkins :key])
(log/spy {:jobid jobname path (str (ssh-host config) ":" jobdir "/Dockerfile"))
:email (:email account) r_job (sync_jobs config "-a" jobname)
:account (dissoc account :password :activation-link) r_store (db/store! @jobs :jobid
:lint (if (.contains r_lint "is OK") true false) {:jobid jobname
:timestamp tstamp :email (:email account)
:type "vm_amd64"})))] :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"
(web/render-error-page :dockerfile (slurp path)})]
(str "Job add failure: " (f/message e)))))))) {;; :lint r_lint
:job r_job}
(f/when-failed [e]
(defn listall [config account] (f/fail (str "Job add '" jobname "' failure :: " (f/message e))))))
(sync_jobs config "-l" (:email account)) (f/fail (str "Cannot find profile " (:email account) " :: "
;;(db/query @jobs {:email (:email account)}) (f/message joblimit)))))))
)

View File

@@ -0,0 +1,38 @@
(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]
;; TODO: quick fix for default joblimit but needs better solution
(let [jl (f/ok-> id fetch-profile (get :joblimit))]
(if (nil? jl) 3 jl)))
(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

@@ -1,19 +1,32 @@
(ns toaster.session (ns toaster.session
(:refer-clojure :exclude [get]) (:refer-clojure :exclude [get])
(:require (:require
[clojure.java.io :as io]
[toaster.config :as conf] [toaster.config :as conf]
[taoensso.timbre :as log] [taoensso.timbre :as log]
[failjure.core :as f] [failjure.core :as f]
[just-auth.core :as auth] [just-auth.core :as auth]
[toaster.ring :as ring] [hiccup.page :as page :refer [html5]]
[toaster.webpage :as web])) [clostache.parser :refer [render-resource]]
[toaster.ring :as ring]))
(defn resource
"renders a template, optionally passing it an hash-map of parameters."
([template] (resource template {}))
([template params] (render-resource template params)))
(defonce login "templates/body_loginform.html")
(defonce signup "templates/body_signupform.html")
(defonce head "templates/html_head.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?
@@ -29,7 +42,7 @@
(if (contains? session :config) (if (contains? session :config)
(:config session) (:config session)
(conf/load-config "toaster" conf/default-settings)) (conf/load-config "toaster" conf/default-settings))
(f/fail "Session not found. "))) (f/fail "Session not found.")))
(defn check-account [request] (defn check-account [request]
;; check if login is present in session ;; check if login is present in session
@@ -39,22 +52,122 @@
user user
(f/when-failed [e] (f/when-failed [e]
(->> e f/message (->> e f/message
(str "Unauthorized access. ") (str "Unauthorized access: ")
f/fail)))) f/fail))))
(defn check-database [] (defn check-database []
(if-let [db @ring/db] (if-let [db @ring/db]
db db
(f/fail "No connection to database. "))) (f/fail "No connection to database.")))
(defn check [request fun]
(defn notify
"render a notification message without ending the page"
([msg] (notify msg ""))
([msg class]
;; support also is-error (not included as notify class in bulma
(let [tclass (if (= class "is-error") "is-danger" class)]
(cond ;; log to console using timbre
(= tclass "is-danger") (log/error msg)
(= 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]
)))
;; shortcut
(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"
([body] (render nil body))
([account body]
{:headers {"Content-Type"
"text/html; charset=utf-8"}
:body (page/html5
(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))
(defn fail [msg err] (f/fail (str msg " :: " (f/message err))))
(defn upload
"manages the upload of a file and calls a function with its path.
(callback) is called with 3 args: path, config and account"
[request config account callback]
(f/attempt-all (f/attempt-all
[db (check-database) [tempfile (param request [:file :tempfile])
config (check-config request) filename (param request [:file :filename])
account (check-account request)] filesize (param request [:file :size])]
(fun request config account) (if (> filesize 64000)
(f/when-failed [e] ;; TODO: put filesize limit in config
(web/render (f/fail "file too big to upload (64KB limit)")
[:div ;; else
(web/render-error (f/message e)) (let [file (io/copy tempfile (io/file "/tmp" filename))
web/login-form])))) path (str "/tmp/" filename)]
(io/delete-file tempfile)
(if (not (.exists (io/file path)))
(f/fail (str "uploaded file not found: " filename))
;; file is now in 'tmp' var
(callback path config account))))
(f/when-failed [e]
(error "Upload file error" e))))
(defn adduser
"manages the creation of a user (pending activation) and calls a fun callback.
(fun) takes 2 args: the name and email of the user."
[request fun]
(f/attempt-all
[name (param request :name)
email (param request :email)
password (param request :password)
repeat-password (param request :repeat-password)
activation {:activation-uri
(get-in request [:headers "host"])}]
(if (not= password repeat-password)
(f/fail "repeated password did not match")
(f/try*
(f/attempt-all
[signup (auth/sign-up @ring/auth
name
email
password
activation
[])]
(fun name email)
(f/when-failed [e]
(fail (str "failure creating account '"email"'") e)))))
(f/when-failed [e]
(render
[:body
(error "Sign-up failure" e)
(resource signup)]))))

View File

@@ -1,113 +1,117 @@
;; Copyright (C) 2018 Dyne.org foundation
;; Sourcecode designed, written and maintained by
;; Denis Roio <jaromil@dyne.org>
;; This program is free software: you can redistribute it and/or modify
;; it under the terms of the GNU Affero General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU Affero General Public License for more details.
;; You should have received a copy of the GNU Affero General Public License
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
(ns toaster.views (ns toaster.views
(:require (:require
[clojure.java.io :as io] [clojure.string :as str :refer [replace]]
[clojure.string :as str] [clojure.contrib.humanize :as humanize :refer [datetime]]
[clojure.contrib.humanize :as humanize :refer [datetime]] ;; [clojure.data.json :as json :refer [read-str]]
;; [clojure.data.json :as json :refer [read-str]] [toaster.bulma :as web :refer [button render-yaml]]
[toaster.webpage :as web] [toaster.session :as s :refer [notify resource param]]
[toaster.session :as s] [toaster.ring :as ring :refer [jobs]]
[toaster.ring :as ring] [toaster.jobs :as job :refer [add sync_jobs]]
[toaster.jobs :as job] [failjure.core :as f :refer [attempt-all when-failed if-let-ok?]]
[failjure.core :as f] [auxiliary.string :refer [strcasecmp]]
[auxiliary.string :refer [strcasecmp]] [toaster.config :refer [q]]
[toaster.config :as conf] [taoensso.timbre :as log]
[taoensso.timbre :as log :refer [debug]] ;; [clj-time.core :as time]
[me.raynes.conch :as sh :refer [with-programs]] [clj-time.coerce :as tc]
[clj-time.core :as time] [clj-storage.core :as db]
[clj-time.coerce :as tc] [hiccup.form :as hf]))
[clj-time.local :as tl]
[hiccup.form :as hf]))
(def dockerfile-upload-form ;; TODO: templated
[:div {:class "container-fluid"} (defn- box-list [account joblist]
[:h1 "Upload a Dockerfile to toast"] [:div {:class "box"}
[:p " Choose the file in your computer and click 'Submit' to [:h1 {:class "title"} (str "List all toaster jobs for " (:name account))]
proceed to validation."] [:table {:class "table is-fullwidth is-hoverable"}
[:div {:class "form-group"} [:thead nil
[:form {:action "dockerfile" :method "post" [:tr nil [:th nil "Date"] [:th nil "Type"] [:th nil "Status"] [:th nil "Actions"]]]
:class "form-shell" [:tbody nil
:enctype "multipart/form-data"} (for [j joblist]
[:fieldset {:class "fieldset btn btn-default btn-file btn-lg"} (let [type (:type j) ;; (-> j (str/split #"-") second)
[:input {:name "file" :type "file"}]] tstamp (:timestamp j) ;; (-> j (str/split #"-") last)
;; [:fieldset {:class "fieldset-submit"} jobid (:jobid j)
[:input {:class "btn btn-primary btn-lg" joburl (str/replace jobid #"@" "AT")]
:id "field-submit" :type "submit" [:tr nil
:name "submit" :value "submit"}]]]]) [:td {:class "date"} (-> tstamp Long/valueOf tc/from-long humanize/datetime)]
[:td {:class "job"} [:a {:href (str "https://sdk.dyne.org:4443/view/web-sdk-builds/job/" joburl)} type]]
[:td {:class "status"} [:a {:href (str "https://sdk.dyne.org:4443/job/" joburl "/lastBuild/console")}
[:img {:src (str "https://sdk.dyne.org:4443/job/" joburl "/badge/icon")}]]]
[:td {:class "actions"}
[:div {:class "field is-grouped"}
(web/button "/view" "\uD83D\uDC41" (hf/hidden-field "jobid" jobid))
(web/button "/start" "▶" (hf/hidden-field "jobid" jobid))
(web/button "/remove" "\uD83D\uDDD1" (hf/hidden-field "jobid" jobid))]]]
))]]])
(def dockerfile-edit-form (defn add-job [path config account]
[:div {:class "container-fluid"}
[:h1 "Edit your Dockerfile to toast"]
[:div {:class "form-group"}
[:form {:action "dockerfile" :method "post"
:class "form-shell"
:enctype "multipart/form-data"}
[:fieldset {:class "fieldset btn btn-default btn-file btn-lg"}
[:textarea {:name "editor" :id "editor"
:rows 30 :cols 72 } "FROM: dyne/devuan:ascii"]
[:input {:class "btn btn-primary btn-lg"
:id "field-submit" :type "submit"
:name "submit" :value "submit"}]]]]
[:script "var editor = ace.edit(\"editor\");
editor.setTheme(\"ace/theme/monokai\");
editor.session.setMode(\"ace/mode/dockerfile\");"]])
(def welcome-menu
[:div {:class "container-fluid"}
[:div {:class "row-fluid"}
[:div {:class "row"} [:a {:href "/list"} "List all your toaster jobs"]]
[:div {:class "row"} [:a {:href "/upload"} "Upload a new toaster job"]]]])
(defn dockerfile-upload-post [request config account]
(web/render
account
(let
[tempfile (get-in request [:params :file :tempfile])
filename (get-in request [:params :file :filename])
params (:params request)]
(cond
(> (get-in params [:file :size]) 64000)
;; max upload size in bytes
;; TODO: put in config
(web/render-error-page params "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)))
(web/render-error-page
(log/spy :error
[:h1 (str "Uploaded file not found: " filename)]))
;; file is now in 'tmp' var
[:div {:class "container-fluid"}
[:h1 "Job uploaded and added"]
[:p "Log messages:"]
(web/render-yaml (job/add path config account))]))))))
(defn list-jobs [request config account]
(f/attempt-all (f/attempt-all
[joblist (job/listall config account)] [newjob (job/add path config account)]
(web/render (s/notify "New toaster job succesfully added" "is-success")
account ;; else when job/add is not-ok
[:div {:class "container-fluid"} (f/when-failed [e]
[:h1 (str "List all toaster jobs for " (:name account))] (s/error "Error adding job" e))))
[:table {:class "sortable table"}
[:thead nil (defn dashboard
[:tr nil [:th nil "Date"] [:th nil "Type"] [:th nil "Actions"]]] ([account] (dashboard {} {} account))
[:tbody nil ([request config account]
(for [j joblist] (f/attempt-all
(let [type (-> j (str/split #"-") second) [joblist (db/query @ring/jobs {:email (:email account)})]
tstamp (-> j (str/split #"-") last)] [:div {:class "container has-text-centered"}
[:tr nil
[:td {:class "date"} (-> tstamp Long/valueOf tc/from-long tl/to-local-date-time [:span
humanize/datetime)] ;(if (> 0 (count joblist))
[:td {:class "job"} [:a {:href (str "https://sdk.dyne.org:4443/view/web-sdk-builds/job/" (box-list account joblist)
(str/replace j #"@" "AT"))} type]] ;)
[:td {:class "start-job"} (web/button "/start" "Start" (hf/hidden-field "job" j)) (s/resource "templates/body_addjob.html") ]]
(web/button "/remove" "Remove" (hf/hidden-field "job" j))]]
))]]])
(f/when-failed [e] (f/when-failed [e]
(web/render-error-page (s/error "Job list failure" e)))))
(str "Job list failure: " (f/message e))))
)) (defn remove-job [request config account]
(f/attempt-all
[jobid (s/param request :jobid)
jobfound (db/query @ring/jobs {:jobid jobid})
r_rmjob (db/delete! @ring/jobs jobid)
r_sync (job/sync_jobs config "-d" jobid)
;; TODO: rm -rf the jobdir created via ssh in jobs/add
]
(s/notify (str "Job removed :: " jobid) "is-primary")
(f/when-failed [e]
(s/error "Failure removing job" e))))
(defn start-job [request config account]
(f/attempt-all
[jobid (s/param request :jobid)
jobfound (db/query @ring/jobs {:jobid jobid})
r_sync (job/sync_jobs config "-r" jobid)]
(s/notify (str "Job started: " jobid) "is-success")
(f/when-failed [e]
(s/error "Failure starting job" e))))
(defn view-job [request config account]
(f/attempt-all
[jobid (s/param request :jobid)
jobfound (db/fetch @ring/jobs jobid)
dockerfile (-> jobfound :dockerfile)]
[:div {:class "box"}
[:h1 {:class "title"} (str "Viewing job: " jobid)]
[:form [:textarea {:id "code" :name "code" } dockerfile]]
[:script "var editor = CodeMirror.fromTextArea(document.getElementById(\"code\"),
{ lineNumbers: true, mode: \"dockerfile\" });"]]
(f/when-failed [e]
(s/error "Failure viewing job" e))))

View File

@@ -38,6 +38,7 @@
(declare render-error) (declare render-error)
(declare render-error-page) (declare render-error-page)
(declare render-static) (declare render-static)
(declare render-error)
(defn q [req] (defn q [req]
"wrapper to retrieve parameters" "wrapper to retrieve parameters"
@@ -98,6 +99,14 @@
[:div {:class "container-fluid"} body] [:div {:class "container-fluid"} body]
(render-footer)])})) (render-footer)])}))
(defn render-success
"render a successful message without ending the page"
[succ]
[:div {:class "alert alert-success" :role "alert"}
[:span {:class "far fa-check-circle"
:aria-hidden "true" :style "padding: .5em"}]
[:span {:class "sr-only"} "Success:" ]
succ])
(defn render-error (defn render-error
"render an error message without ending the page" "render an error message without ending the page"
@@ -293,7 +302,7 @@
;; (defonce readme ;; (defonce readme
;; (slurp (io/resource "public/static/README.html"))) ;; (slurp (io/resource "public/static/README.html")))
(defonce login-form (def login-form
[:div [:div
[:h1 "Login for your account" [:h1 "Login for your account"
[:form {:action "/login" [:form {:action "/login"
@@ -310,7 +319,7 @@
:class "btn btn-primary btn-lg btn-block" :class "btn btn-primary btn-lg btn-block"
:style "margin-top: 1em"}]]]]) :style "margin-top: 1em"}]]]])
(defonce signup-form (def signup-form
[:div [:div
[:h1 "Sign Up for a toaster account" [:h1 "Sign Up for a toaster account"
[:form {:action "/signup" [:form {:action "/signup"

View File

@@ -1,11 +1,13 @@
webserver: webserver:
anti-forgery: false anti-forgery: false
ssl-redirect: false ssl-redirect: false
mock-auth: false
jenkins: jenkins:
host: "bridge.toaster" host: "bridge.toaster"
user: "jenkins" user: "jenkins"
key: "../id_ed25519" key: "../id_ed25519"
url: "https://sdk.dyne.org:4443"
just-auth: just-auth:
email-server: "mail.dyne.org" email-server: "mail.dyne.org"

View File

@@ -43,26 +43,26 @@ optional arguments:
The `jobname` argument should be in a specific format. It should contain The `jobname` argument should be in a specific format. It should contain
the requester's email, which sdk was chosen, the requested architecture, the requester's email, which sdk was chosen, the requested architecture,
and a timestamp. codename, and a timestamp.
In case of vm-sdk or live-sdk, these would look like: In case of vm-sdk or live-sdk, these would look like:
``` ```
parazyd@dyne.org-vm_amd64-1537977964 parazyd@dyne.org-vm_amd64_ascii-1537977964
parazyd@dyne.org-live_amd64-1537977964 parazyd@dyne.org-live_amd64_beowulf-1537977964
``` ```
In case of arm-sdk, we also need to know the board we're building for: In case of arm-sdk, we also need to know the board we're building for:
``` ```
parazyd@dyne.org-arm_armhf_sunxi-1537977964 parazyd@dyne.org-arm_armhf_ascii_sunxi-1537977964
``` ```
All of this combined, the required command to add a new job to Jenkins All of this combined, the required command to add a new job to Jenkins
would look something like the following: would look something like the following:
``` ```
sync_jobs.py -a parazyd@dyne.org-vm_amd64-1537977964 sync_jobs.py -a parazyd@dyne.org-vm_amd64_ascii-1537977964
``` ```
In case of removing or building an existing job, all of the above applies the In case of removing or building an existing job, all of the above applies the

View File

@@ -20,23 +20,38 @@ def add_job(jobname):
desc = 'WebSDK build for: %s\nStarted: %s' % (info[0], info[2]) desc = 'WebSDK build for: %s\nStarted: %s' % (info[0], info[2])
sdk = info[1].split('_')[0] sdk = info[1].split('_')[0]
arch = info[1].split('_')[1] arch = info[1].split('_')[1]
codename = info[1].split('_')[2]
blenddir = join(jobpath, jobname) blenddir = join(jobpath, jobname)
blendfile = join(blenddir, 'Dockerfile') blendfile = join(blenddir, 'Dockerfile')
if sdk == 'arm': if codename == 'ascii':
board = info[1].split('_')[2] relvars = 'release=ascii && version=2.0.0'
zshcmd = 'load devuan %s %s' % (board, blendfile) elif codename == 'beowulf':
elif sdk == 'live': relvars = 'release=beowulf && version=3.0.0'
zshcmd = 'load devuan %s %s' % (arch, blendfile) else:
elif sdk == 'vm': # Default to Ascii
zshcmd = 'load devuan %s' % (blendfile) relvars = 'release=ascii && version=2.0.0'
command = "zsh -f -c 'source sdk && %s && build_image_dist'" % zshcmd if sdk == 'arm':
board = info[1].split('_')[3]
zshcmd = '\
load devuan %s %s && %s && build_image_dist' % (board, blendfile, relvars)
elif sdk == 'live':
zshcmd = '\
load devuan %s %s && %s && build_iso_dist' % (arch, blendfile, relvars)
elif sdk == 'vm':
zshcmd = '\
load devuan %s && %s && build_vagrant_dist' % (blendfile, relvars)
command = "zsh -f -c 'source sdk && %s'" % zshcmd
command = html.escape(command) command = html.escape(command)
replacements = [('DESC', desc), replacements = [('DESC', desc),
('SDK', sdk), ('SDK', sdk),
('ARCH', arch), ('ARCH', arch),
('CODENAME', codename),
('COMMAND', command), ('COMMAND', command),
('BLENDDIR', blenddir)] ('BLENDDIR', blenddir)]

View File

@@ -34,9 +34,16 @@
<builders> <builders>
<hudson.tasks.Shell> <hudson.tasks.Shell>
<command> <command>
scp -r sdk:{{{BLENDDIR}}} {{{BLENDDIR}}} scp -r sdk:{{{BLENDDIR}}} {{{BLENDDIR}}} || exit 1
git submodule update --init --recursive --checkout git submodule update --init --recursive --checkout || exit 1
{{{COMMAND}}}
mkdir -p tmp
cd tmp
wget https://sdk.dyne.org:4443/job/devuan-{{{CODENAME}}}-{{{ARCH}}}-stage3/lastSuccessfulBuild/artifact/tmp/bootstrap-devuan-{{{ARCH}}}-stage3.tgz
cd -
{{{COMMAND}}} || exit 1
rm -rf {{{BLENDDIR}}}
</command> </command>
</hudson.tasks.Shell> </hudson.tasks.Shell>
</builders> </builders>