Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions lib/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ var audit = require('./audit');
var auditLogger = require('./audit_logger');
var rules = require('./rules');
var volumeEndpoints = require('./endpoints/volumes');
var execEndpoints = require('./endpoints/exec');

// Account users, roles and policies:
var users = require('./users');
Expand Down Expand Up @@ -548,6 +549,7 @@ module.exports = {
tags.mount(server, machineThrottle);
audit.mount(server, machineThrottle);
rules.mount(server, machineThrottle);
execEndpoints.mount(server, machineThrottle);

users.mount(server, userThrottle(config, 'users'), config);
policies.mount(server, userThrottle(config, 'policies'), config);
Expand Down
111 changes: 111 additions & 0 deletions lib/endpoints/exec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

/*
* Author: Alex Wilson <alex@uq.edu.au>
* Copyright 2019, The University of Queensland
*/

var assert = require('assert-plus');
var jsprim = require('jsprim');
var restify = require('restify');
var schemas = require('joyent-schemas').cloudapi;
var util = require('util');
var vasync = require('vasync');
var net = require('net');

function mount(server, before, pre) {
assert.object(server, 'server');
assert.ok(before, 'before');
assert.optionalArrayOfFunc(pre, 'pre');

pre = pre || [];

server.post({
path: '/:account/machines/:machine/exec',
name: 'ExecMachineCommand',
version: [ '8.4.0' ]
}, before, execCommand);

return server;
}

function execCommand(req, res, next) {
var vm = req.machine;

if (vm.brand !== 'joyent' && vm.brand !== 'joyent-minimal' &&
vm.brand !== 'lx') {
res.send(400, new restify.RestError({
statusCode: 400,
restCode: 'MachineIsHVM',
message: 'Specified machine is an HVM and cannot execute commands'
}));
next();
return;
}

if (vm.state !== 'running') {
res.send(400, new restify.RestError({
statusCode: 400,
restCode: 'MachineStopped',
message: 'Only running machines can execute commands'
}));
next();
return;
}

if (!req.params.argv || !Array.isArray(req.params.argv)) {
res.send(400, new restify.RestError({
statusCode: 400,
restCode: 'InvalidArgument',
message: 'The "argv" parameter is required and must be an Array'
}));
next();
return;
}

var params = {
'command': {
'Cmd': req.params.argv
}
};
req.sdc.cnapi.dockerExec(vm.compute_node, vm.id, params,
function afterDockerExec(err, info) {
if (err) {
req.log.error(err, 'failed to start exec job');
res.send(500, new restify.InternalServerError('Failed to ' +
'execute command'));
next();
return;
}

req.log.debug({ info: info }, 'exec job got connection info');

var data = '';
var sock = net.createConnection({
host: info.host,
port: info.port
});
sock.on('connect', function () {
res.header('content-type', 'application/x-json-stream');
sock.pipe(res);
sock.on('end', function () {
next();
});
});
sock.on('error', function (err) {
req.log.error(err, 'error from exec job socket');
res.send(500, new restify.InternalServerError('Failed to ' +
'execute command'));
next();
return;
});
});
}

module.exports = {
mount: mount
};