|
12 | 12 | */ |
13 | 13 |
|
14 | 14 |
|
15 | | -var redisURL = process.env.REDIS_URL ? {url: process.env.REDIS_URL} : {}; |
| 15 | +var redisURL = process.env.REDIS_URL ? {url: process.env.REDIS_URL} : undefined; |
16 | 16 | // for express |
17 | 17 | var express = require('express'), |
18 | 18 | cons = require('consolidate'), |
| 19 | + spawn = require('child_process').spawn, |
19 | 20 | http = require('http'), |
| 21 | + os = require('os'), |
| 22 | + fs = require('fs'), |
| 23 | + which = require('which'), |
| 24 | + pathtool = require('path'), |
| 25 | + AdmZip = require('adm-zip'), |
20 | 26 | app = express(); |
21 | 27 |
|
22 | 28 | var redis = require('redis'), |
23 | | - rcl = redis.createClient(redisURL); |
| 29 | + rcl = redisURL ? redis.createClient(redisURL): redis.createClient(); |
24 | 30 |
|
25 | 31 | var server = http.createServer(app); |
26 | 32 | var wflib = require('../wflib').init(rcl); |
@@ -88,27 +94,138 @@ app.get('/apps', function(req, res) { |
88 | 94 | }); |
89 | 95 |
|
90 | 96 | // creates a new workflow instance ('app') |
91 | | -// body must be a valid workflow description in JSON |
| 97 | +// body can be: |
| 98 | +// - a valid workflow description in JSON |
| 99 | +// - or a complete workflow directory packed as zip |
| 100 | +// FIXME: validate workflow description |
| 101 | +// FIXME: add proper/more detailed error info instead of "badRequest(res)" |
92 | 102 | app.post('/apps', function(req, res) { |
93 | | - var wfJson = req.body; |
94 | | - var baseUrl = ''; |
95 | | - //onsole.log(wfJson); |
96 | | - |
97 | | - // FIXME: validate workflow description |
98 | | - // FIXME: add proper/more detailed error info instead of "badRequest(res)" |
99 | | - wflib.createInstance(wfJson, baseUrl, function(err, appId) { |
100 | | - if (err) return badRequest(res); |
101 | | - engine[appId] = new Engine({"emulate": "false"}, wflib, appId, function(err) { |
102 | | - if (err) return badRequest(res); |
103 | | - engine[appId].runInstance(function(err) { |
104 | | - if (err) return badRequest(res); |
105 | | - res.header('Location', req.url + '/' + appId); |
106 | | - res.send(201, null); |
107 | | - //res.redirect(req.url + '/' + appId, 302); |
108 | | - // TODO: implement sending all input signals (just like -s flag in runwf.js) |
109 | | - }); |
110 | | - }); |
111 | | - }); |
| 103 | + var ctype = req.headers["content-type"]; |
| 104 | + |
| 105 | + function makeId6() { |
| 106 | + var id = []; |
| 107 | + var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; |
| 108 | + |
| 109 | + for (var i=0; i<6; i++ ) { |
| 110 | + id[i] = possible.charAt(Math.floor(Math.random() * possible.length)); |
| 111 | + } |
| 112 | + |
| 113 | + return id.join(""); |
| 114 | + } |
| 115 | + |
| 116 | + var runWorkflow = function(wfDir, appId) { |
| 117 | + var config = {"emulate":"false", "workdir": wfDir}; |
| 118 | + engine[appId] = new Engine(config, wflib, appId, function(err) { |
| 119 | + if (err) return badRequest(res); |
| 120 | + engine[appId].runInstance(function(err) { |
| 121 | + if (err) return badRequest(res); |
| 122 | + res.header('Location', req.url + '/' + appId); |
| 123 | + res.send(201, null); |
| 124 | + //res.redirect(req.url + '/' + appId, 302); |
| 125 | + // TODO: implement sending all input signals (just like -s flag in runwf.js) |
| 126 | + }); |
| 127 | + }); |
| 128 | + } |
| 129 | + |
| 130 | + // 1. Workflow can be sent as a zipped directory |
| 131 | + if (ctype == "application/zip") { |
| 132 | + var hfid = wflib.hfid, zipData = [], size = 0; |
| 133 | + |
| 134 | + req.on('data', function (data) { |
| 135 | + zipData.push(data); |
| 136 | + size += data.length; |
| 137 | + console.log('Got chunk: ' + data.length + ' total: ' + size); |
| 138 | + }); |
| 139 | + |
| 140 | + req.on('end', function () { |
| 141 | + var tmpdir = pathtool.join(os.tmpdir(), "HF-" + hfid); |
| 142 | + if (!fs.existsSync(tmpdir)){ |
| 143 | + fs.mkdirSync(tmpdir); |
| 144 | + } |
| 145 | + |
| 146 | + var wfDir = pathtool.join(tmpdir, makeId6()); |
| 147 | + fs.mkdirSync(wfDir); |
| 148 | + |
| 149 | + var wffile; |
| 150 | + console.log("total size = " + size); |
| 151 | + |
| 152 | + var buf = new Buffer(size); |
| 153 | + for (var i=0, len = zipData.length, pos = 0; i < len; i++) { |
| 154 | + zipData[i].copy(buf, pos); |
| 155 | + pos += zipData[i].length; |
| 156 | + } |
| 157 | + |
| 158 | + var zip = new AdmZip(buf); |
| 159 | + var zipEntries = zip.getEntries(); |
| 160 | + console.log("ZIP ENTRIES:", zipEntries.length); |
| 161 | + zip.extractAllTo(wfDir); |
| 162 | + |
| 163 | + // Make sure this works correctly both when the zip contains a directory, or just files |
| 164 | + process.chdir(wfDir); |
| 165 | + var files = fs.readdirSync(wfDir); |
| 166 | + if (files.length == 1) { |
| 167 | + var fstats = fs.lstatSync(files[0]); |
| 168 | + if (fstats.isDirectory()) { |
| 169 | + wfDir = pathtool.join(wfDir, files[0]); |
| 170 | + process.chdir(wfDir); |
| 171 | + } |
| 172 | + } |
| 173 | + wffile = pathtool.join(wfDir, "workflow.json"); |
| 174 | + console.log("WF FILE:", wffile); |
| 175 | + |
| 176 | + // if there is a "package.json" file, install dependencies (npm install -d) |
| 177 | + // TODO: improve error checking etc. |
| 178 | + if (fs.existsSync("package.json")) { // TODO: check if it is a file |
| 179 | + // find path to npm executable in a portable way |
| 180 | + // (using which, TODO: test portability) |
| 181 | + var npmexec = which.sync('npm'); // TODO: throws if not found |
| 182 | + var proc = spawn(npmexec, ["install", "-d"]); |
| 183 | + |
| 184 | + proc.stdout.on('data', function(data) { |
| 185 | + console.log(data.toString().trimRight()); |
| 186 | + }); |
| 187 | + |
| 188 | + proc.stderr.on('data', function(data) { |
| 189 | + console.log(data.toString().trimRight()); |
| 190 | + }); |
| 191 | + |
| 192 | + // TODO: check exit code |
| 193 | + proc.on('exit', function(code, signal) { |
| 194 | + wflib.createInstanceFromFile(wffile, '', function(err, appId, wfJson) { |
| 195 | + runWorkflow(wfDir, appId); |
| 196 | + }); |
| 197 | + }); |
| 198 | + } else { |
| 199 | + wflib.createInstanceFromFile(wffile, '', function(err, appId, wfJson) { |
| 200 | + runWorkflow(wfDir, appId); |
| 201 | + }); |
| 202 | + } |
| 203 | + }); |
| 204 | + |
| 205 | + req.on('error', function(e) { |
| 206 | + console.log("ERROR ERROR: " + e.message); |
| 207 | + }); |
| 208 | + |
| 209 | + return; |
| 210 | + |
| 211 | + // TODO: switch to "mkdtemp" after migrating to Node 6.x |
| 212 | + /*fs.mkdtemp(tmpdir, (err, dir) => { |
| 213 | + console.log(dir); |
| 214 | + return res.send(201, null); |
| 215 | + });*/ |
| 216 | + } |
| 217 | + |
| 218 | + // 2. Workflow can also be sent as a JSON |
| 219 | + if (ctype == "application/json") { |
| 220 | + var wfJson = req.body, baseUrl = ''; |
| 221 | + //onsole.log(wfJson); |
| 222 | + var hfid = wflib.hfid, zipData = [], size = 0; |
| 223 | + wflib.createInstance(wfJson, baseUrl, function(err, appId) { |
| 224 | + if (err) return badRequest(res); |
| 225 | + runWorkflow(wfDir, appId); |
| 226 | + }); |
| 227 | + } |
| 228 | + |
112 | 229 | }); |
113 | 230 |
|
114 | 231 | // returns workflow instance ('app') info |
|
0 commit comments