Skip to content

Commit e532247

Browse files
committed
HF server will install dependencies specified in <wf_dir>/package.json
1 parent 6f82026 commit e532247

File tree

2 files changed

+142
-23
lines changed

2 files changed

+142
-23
lines changed

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,9 @@
4242
"docopt": "",
4343
"xpath": "",
4444
"xmldom": "",
45-
"shell-quote": "1.4.2"
45+
"shell-quote": "1.4.2",
46+
"adm-zip": "",
47+
"which": ""
4648
},
4749
"files": [
4850
"LICENSE",

server/hyperflow-server.js

Lines changed: 139 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,21 @@
1212
*/
1313

1414

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;
1616
// for express
1717
var express = require('express'),
1818
cons = require('consolidate'),
19+
spawn = require('child_process').spawn,
1920
http = require('http'),
21+
os = require('os'),
22+
fs = require('fs'),
23+
which = require('which'),
24+
pathtool = require('path'),
25+
AdmZip = require('adm-zip'),
2026
app = express();
2127

2228
var redis = require('redis'),
23-
rcl = redis.createClient(redisURL);
29+
rcl = redisURL ? redis.createClient(redisURL): redis.createClient();
2430

2531
var server = http.createServer(app);
2632
var wflib = require('../wflib').init(rcl);
@@ -88,27 +94,138 @@ app.get('/apps', function(req, res) {
8894
});
8995

9096
// 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)"
92102
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+
112229
});
113230

114231
// returns workflow instance ('app') info

0 commit comments

Comments
 (0)