Merge pull request #461 from resin-io/452-fix-permissions

isolate the sudo-runnable command
This commit is contained in:
Eugene Mirotin 2017-03-28 14:40:51 +03:00 committed by GitHub
commit dede4bb329
31 changed files with 401 additions and 251 deletions

View File

@ -49,10 +49,9 @@ exports.login = {
],
primary: true,
action: function(params, options, done) {
var Promise, _, auth, capitano, form, login, messages, patterns, resin;
var Promise, _, auth, form, login, messages, patterns, resin;
_ = require('lodash');
Promise = require('bluebird');
capitano = Promise.promisifyAll(require('capitano'));
resin = require('resin-sdk-preconfigured');
auth = require('resin-cli-auth');
form = require('resin-cli-form');
@ -77,8 +76,10 @@ exports.login = {
return auth.login();
}
return patterns.askLoginType().then(function(loginType) {
var capitanoRunAsync;
if (loginType === 'register') {
return capitano.runAsync('signup');
capitanoRunAsync = Promise.promisify(require('capitano').run);
return capitanoRunAsync('signup');
}
options[loginType] = true;
return login(options);
@ -110,7 +111,7 @@ exports.logout = {
exports.signup = {
signature: 'signup',
description: 'signup to resin.io',
help: 'Use this command to signup for a resin.io account.\n\nIf signup is successful, you\'ll be logged in to your new user automatically.\n\nExamples:\n\n $ resin signup\n Email: me@mycompany.com\n Username: johndoe\n Password: ***********\n\n $ resin whoami\n johndoe',
help: 'Use this command to signup for a resin.io account.\n\nIf signup is successful, you\'ll be logged in to your new user automatically.\n\nExamples:\n\n $ resin signup\n Email: johndoe@acme.com\n Password: ***********\n\n $ resin whoami\n johndoe',
action: function(params, options, done) {
var form, resin, validation;
resin = require('resin-sdk-preconfigured');
@ -124,10 +125,6 @@ exports.signup = {
name: 'email',
type: 'input',
validate: validation.validateEmail
}, {
message: 'Username:',
name: 'username',
type: 'input'
}, {
message: 'Password:',
name: 'password',

View File

@ -40,15 +40,15 @@ exports.read = {
permission: 'user',
root: true,
action: function(params, options, done) {
var Promise, config, prettyjson, umount, visuals;
var Promise, config, prettyjson, umountAsync, visuals;
Promise = require('bluebird');
config = require('resin-config-json');
visuals = require('resin-cli-visuals');
umount = Promise.promisifyAll(require('umount'));
umountAsync = Promise.promisify(require('umount').umount);
prettyjson = require('prettyjson');
return Promise["try"](function() {
return options.drive || visuals.drive('Select the device drive');
}).tap(umount.umountAsync).then(function(drive) {
}).tap(umountAsync).then(function(drive) {
return config.read(drive, options.type);
}).tap(function(configJSON) {
return console.info(prettyjson.render(configJSON));
@ -77,21 +77,21 @@ exports.write = {
permission: 'user',
root: true,
action: function(params, options, done) {
var Promise, _, config, umount, visuals;
var Promise, _, config, umountAsync, visuals;
Promise = require('bluebird');
_ = require('lodash');
config = require('resin-config-json');
visuals = require('resin-cli-visuals');
umount = Promise.promisifyAll(require('umount'));
umountAsync = Promise.promisify(require('umount').umount);
return Promise["try"](function() {
return options.drive || visuals.drive('Select the device drive');
}).tap(umount.umountAsync).then(function(drive) {
}).tap(umountAsync).then(function(drive) {
return config.read(drive, options.type).then(function(configJSON) {
console.info("Setting " + params.key + " to " + params.value);
_.set(configJSON, params.key, params.value);
return configJSON;
}).tap(function() {
return umount.umountAsync(drive);
return umountAsync(drive);
}).then(function(configJSON) {
return config.write(drive, options.type, configJSON);
});
@ -122,16 +122,16 @@ exports.inject = {
permission: 'user',
root: true,
action: function(params, options, done) {
var Promise, config, fs, umount, visuals;
var Promise, config, readFileAsync, umountAsync, visuals;
Promise = require('bluebird');
config = require('resin-config-json');
visuals = require('resin-cli-visuals');
umount = Promise.promisifyAll(require('umount'));
fs = Promise.promisifyAll(require('fs'));
umountAsync = Promise.promisify(require('umount').umount);
readFileAsync = Promise.promisify(require('fs').readFile);
return Promise["try"](function() {
return options.drive || visuals.drive('Select the device drive');
}).tap(umount.umountAsync).then(function(drive) {
return fs.readFileAsync(params.file, 'utf8').then(JSON.parse).then(function(configJSON) {
}).tap(umountAsync).then(function(drive) {
return readFileAsync(params.file, 'utf8').then(JSON.parse).then(function(configJSON) {
return config.write(drive, options.type, configJSON);
});
}).tap(function() {
@ -166,24 +166,24 @@ exports.reconfigure = {
permission: 'user',
root: true,
action: function(params, options, done) {
var Promise, capitano, config, umount, visuals;
var Promise, capitanoRunAsync, config, umountAsync, visuals;
Promise = require('bluebird');
config = require('resin-config-json');
visuals = require('resin-cli-visuals');
capitano = Promise.promisifyAll(require('capitano'));
umount = Promise.promisifyAll(require('umount'));
capitanoRunAsync = Promise.promisify(require('capitano').run);
umountAsync = Promise.promisify(require('umount').umount);
return Promise["try"](function() {
return options.drive || visuals.drive('Select the device drive');
}).tap(umount.umountAsync).then(function(drive) {
}).tap(umountAsync).then(function(drive) {
return config.read(drive, options.type).get('uuid').tap(function() {
return umount.umountAsync(drive);
return umountAsync(drive);
}).then(function(uuid) {
var configureCommand;
configureCommand = "os configure " + drive + " " + uuid;
if (options.advanced) {
configureCommand += ' --advanced';
}
return capitano.runAsync(configureCommand);
return capitanoRunAsync(configureCommand);
});
}).then(function() {
return console.info('Done');
@ -205,9 +205,9 @@ exports.generate = {
],
permission: 'user',
action: function(params, options, done) {
var Promise, _, deviceConfig, form, fs, prettyjson, resin;
var Promise, _, deviceConfig, form, prettyjson, resin, writeFileAsync;
Promise = require('bluebird');
fs = Promise.promisifyAll(require('fs'));
writeFileAsync = Promise.promisify(require('fs').writeFile);
resin = require('resin-sdk-preconfigured');
_ = require('lodash');
form = require('resin-cli-form');
@ -230,7 +230,7 @@ exports.generate = {
});
}).then(function(config) {
if (options.output != null) {
return fs.writeFileAsync(options.output, JSON.stringify(config));
return writeFileAsync(options.output, JSON.stringify(config));
}
return console.log(prettyjson.render(config));
}).nodeify(done);

View File

@ -274,11 +274,12 @@ exports.init = {
],
permission: 'user',
action: function(params, options, done) {
var Promise, capitano, helpers, patterns, resin, rimraf, tmp;
var Promise, capitanoRunAsync, helpers, patterns, resin, rimraf, tmp, tmpNameAsync;
Promise = require('bluebird');
capitano = Promise.promisifyAll(require('capitano'));
capitanoRunAsync = Promise.promisify(require('capitano').run);
rimraf = Promise.promisify(require('rimraf'));
tmp = Promise.promisifyAll(require('tmp'));
tmp = require('tmp');
tmpNameAsync = Promise.promisify(tmp.tmpName);
tmp.setGracefulCleanup();
resin = require('resin-sdk-preconfigured');
helpers = require('../utils/helpers');
@ -291,23 +292,23 @@ exports.init = {
}).then(resin.models.application.get).then(function(application) {
var download;
download = function() {
return tmp.tmpNameAsync().then(function(temporalPath) {
return capitano.runAsync("os download " + application.device_type + " --output " + temporalPath + " --version default");
}).disposer(function(temporalPath) {
return rimraf(temporalPath);
return tmpNameAsync().then(function(tempPath) {
return capitanoRunAsync("os download " + application.device_type + " --output '" + tempPath + "' --version default");
}).disposer(function(tempPath) {
return rimraf(tempPath);
});
};
return Promise.using(download(), function(temporalPath) {
return capitano.runAsync("device register " + application.app_name).then(resin.models.device.get).tap(function(device) {
var configure;
configure = "os configure " + temporalPath + " " + device.uuid;
return Promise.using(download(), function(tempPath) {
return capitanoRunAsync("device register " + application.app_name).then(resin.models.device.get).tap(function(device) {
var configureCommand;
configureCommand = "os configure '" + tempPath + "' " + device.uuid;
if (options.advanced) {
configure += ' --advanced';
configureCommand += ' --advanced';
}
return capitano.runAsync(configure).then(function() {
var message;
message = 'Initializing a device requires administrative permissions\ngiven that we need to access raw devices directly.\n';
return helpers.sudo(['os', 'initialize', temporalPath, '--type', application.device_type], message);
return capitanoRunAsync(configureCommand).then(function() {
var osInitCommand;
osInitCommand = "os initialize '" + tempPath + "' --type " + application.device_type;
return capitanoRunAsync(osInitCommand);
})["catch"](function(error) {
return resin.models.device.remove(device.uuid)["finally"](function() {
throw error;

View File

@ -60,12 +60,13 @@ general = function(params, options, done) {
console.log(messages.reachingOut);
console.log('\nPrimary commands:\n');
commands = _.reject(capitano.state.commands, function(command) {
return command.isWildcard();
return command.hidden || command.isWildcard();
});
groupedCommands = _.groupBy(commands, function(command) {
if (command.plugin) {
return 'plugins';
} else if (command.primary) {
}
if (command.primary) {
return 'primary';
}
return 'secondary';

View File

@ -31,5 +31,6 @@ module.exports = {
settings: require('./settings'),
config: require('./config'),
sync: require('./sync'),
ssh: require('./ssh')
ssh: require('./ssh'),
internal: require('./internal')
};

35
build/actions/internal.js Normal file
View File

@ -0,0 +1,35 @@
// Generated by CoffeeScript 1.12.4
/*
Copyright 2016 Resin.io
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
exports.osInit = {
signature: 'internal osinit <image> <type> <config>',
description: 'do actual init of the device with the preconfigured os image',
help: 'Don\'t use this command directly! Use `resin os initialize <image>` instead.',
hidden: true,
root: true,
action: function(params, options, done) {
var Promise, helpers, init;
Promise = require('bluebird');
init = require('resin-device-init');
helpers = require('../utils/helpers');
return Promise["try"](function() {
var config;
config = JSON.parse(params.config);
return init.initialize(params.image, params.type, config);
}).then(helpers.osProgressHandler).nodeify(done);
}
};

View File

@ -72,15 +72,15 @@ exports.add = {
help: 'Use this command to associate a new SSH key with your account.\n\nIf `path` is omitted, the command will attempt\nto read the SSH key from stdin.\n\nExamples:\n\n $ resin key add Main ~/.ssh/id_rsa.pub\n $ cat ~/.ssh/id_rsa.pub | resin key add Main',
permission: 'user',
action: function(params, options, done) {
var Promise, _, capitano, fs, resin;
var Promise, _, capitano, readFileAsync, resin;
_ = require('lodash');
Promise = require('bluebird');
fs = Promise.promisifyAll(require('fs'));
readFileAsync = Promise.promisify(require('fs').readFile);
capitano = require('capitano');
resin = require('resin-sdk-preconfigured');
return Promise["try"](function() {
if (params.path != null) {
return fs.readFileAsync(params.path, {
return readFileAsync(params.path, {
encoding: 'utf8'
});
}

View File

@ -66,18 +66,20 @@ module.exports = {
help: 'Use this command to configure or reconfigure a resinOS drive or image.\n\nExamples:\n\n $ resin local configure /dev/sdc\n $ resin local configure path/to/image.img',
root: true,
action: function(params, options, done) {
var Promise, _, denymount, inquirer, reconfix, umount;
var Promise, _, denymount, inquirer, isMountedAsync, reconfix, umount, umountAsync;
_ = require('lodash');
Promise = require('bluebird');
umount = Promise.promisifyAll(require('umount'));
umount = require('umount');
umountAsync = Promise.promisify(umount.umount);
isMountedAsync = Promise.promisify(umount.isMounted);
inquirer = require('inquirer');
reconfix = require('reconfix');
denymount = Promise.promisify(require('denymount'));
return umount.isMountedAsync(params.target).then(function(isMounted) {
return isMountedAsync(params.target).then(function(isMounted) {
if (!isMounted) {
return;
}
return umount.umountAsync(params.target);
return umountAsync(params.target);
}).then(function() {
return denymount(params.target, function(cb) {
return reconfix.readConfiguration(CONFIGURATION_SCHEMA, params.target).then(function(data) {

View File

@ -34,13 +34,13 @@ module.exports = {
],
root: true,
action: function(params, options, done) {
var Promise, _, chalk, drivelist, form, fs, imageWrite, os, umount, visuals;
var Promise, _, chalk, driveListAsync, form, fs, imageWrite, os, umountAsync, visuals;
_ = require('lodash');
os = require('os');
Promise = require('bluebird');
umount = Promise.promisifyAll(require('umount'));
umountAsync = Promise.promisify(require('umount').umount);
fs = Promise.promisifyAll(require('fs'));
drivelist = Promise.promisifyAll(require('drivelist'));
driveListAsync = Promise.promisify(require('drivelist').list);
chalk = require('chalk');
visuals = require('resin-cli-visuals');
form = require('resin-cli-form');
@ -71,7 +71,7 @@ module.exports = {
console.log(chalk.red.bold('Aborted image flash'));
process.exit(0);
}
return drivelist.listAsync().then(function(drives) {
return driveListAsync().then(function(drives) {
var selectedDrive;
selectedDrive = _.find(drives, {
device: answers.drive
@ -87,7 +87,7 @@ module.exports = {
write: new visuals.Progress('Flashing'),
check: new visuals.Progress('Validating')
};
return umount.umountAsync(selectedDrive.device).then(function() {
return umountAsync(selectedDrive.device).then(function() {
return Promise.props({
imageSize: fs.statAsync(params.image).get('size'),
imageStream: Promise.resolve(fs.createReadStream(params.image)),
@ -113,12 +113,12 @@ module.exports = {
return writer.on('done', resolve);
});
}).then(function() {
var removedrive;
var ejectAsync;
if ((os.platform() === 'win32') && (selectedDrive.mountpoint != null)) {
removedrive = Promise.promisifyAll(require('removedrive'));
return removedrive.ejectAsync(selectedDrive.mountpoint);
ejectAsync = Promise.promisify(require('removedrive').eject);
return ejectAsync(selectedDrive.mountpoint);
}
return umount.umountAsync(selectedDrive.device);
return umountAsync(selectedDrive.device);
});
}).asCallback(done);
}

View File

@ -15,7 +15,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
var commandOptions, formatVersion, resolveVersion, stepHandler;
var commandOptions, formatVersion, initWarningMessage, resolveVersion;
commandOptions = require('./command-options');
@ -120,25 +120,6 @@ exports.download = {
}
};
stepHandler = function(step) {
var _, bar, helpers, rindle, visuals;
_ = require('lodash');
rindle = require('rindle');
visuals = require('resin-cli-visuals');
helpers = require('../utils/helpers');
step.on('stdout', _.bind(process.stdout.write, process.stdout));
step.on('stderr', _.bind(process.stderr.write, process.stderr));
step.on('state', function(state) {
if (state.operation.command === 'burn') {
return;
}
return console.log(helpers.stateToString(state));
});
bar = new visuals.Progress('Writing Device OS');
step.on('burn', _.bind(bar.update, bar));
return rindle.wait(step);
};
exports.configure = {
signature: 'os configure <image> <uuid>',
description: 'configure an os image',
@ -176,16 +157,18 @@ exports.configure = {
override: override
});
}).then(function(answers) {
return init.configure(params.image, params.uuid, answers).then(stepHandler);
return init.configure(params.image, params.uuid, answers).then(helpers.osProgressHandler);
});
}).nodeify(done);
}
};
initWarningMessage = 'Note: Initializing the device may ask for administrative permissions\nbecause we need to access the raw devices directly.';
exports.initialize = {
signature: 'os initialize <image>',
description: 'initialize an os image',
help: 'Use this command to initialize a previously configured operating system image.\n\nExamples:\n\n $ resin os initialize ../path/rpi.img --type \'raspberry-pi\'',
help: "Use this command to initialize a device with previously configured operating system image.\n\n" + initWarningMessage + "\n\nExamples:\n\n $ resin os initialize ../path/rpi.img --type 'raspberry-pi'",
permission: 'user',
options: [
commandOptions.yes, {
@ -201,16 +184,14 @@ exports.initialize = {
alias: 'd'
}
],
root: true,
action: function(params, options, done) {
var Promise, form, helpers, init, patterns, umount;
var Promise, form, helpers, patterns, umountAsync;
Promise = require('bluebird');
umount = Promise.promisifyAll(require('umount'));
umountAsync = Promise.promisify(require('umount').umount);
form = require('resin-cli-form');
init = require('resin-device-init');
patterns = require('../utils/patterns');
helpers = require('../utils/helpers');
console.info('Initializing device');
console.info("Initializing device\n\n" + initWarningMessage);
return helpers.getManifest(params.image, options.type).then(function(manifest) {
var ref;
return (ref = manifest.initialization) != null ? ref.options : void 0;
@ -226,14 +207,14 @@ exports.initialize = {
return;
}
message = "This will erase " + answers.drive + ". Are you sure?";
return patterns.confirm(options.yes, message)["return"](answers.drive).then(umount.umountAsync);
return patterns.confirm(options.yes, message)["return"](answers.drive).then(umountAsync);
}).tap(function(answers) {
return init.initialize(params.image, options.type, answers).then(stepHandler);
return helpers.sudo(['internal', 'osinit', params.image, options.type, JSON.stringify(answers)]);
}).then(function(answers) {
if (answers.drive == null) {
return;
}
return umount.umountAsync(answers.drive).tap(function() {
return umountAsync(answers.drive).tap(function() {
return console.info("You can safely remove " + answers.drive + " now");
});
}).nodeify(done);

35
build/actions/util.js Normal file
View File

@ -0,0 +1,35 @@
// Generated by CoffeeScript 1.12.4
/*
Copyright 2016 Resin.io
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
exports.osInit = {
signature: 'util osinit <image> <type> <config>',
description: 'do actual init of the device with the preconfigured os image',
help: 'Don\'t use this command directly! Use `resin os initialize <image>` instead.',
hidden: true,
root: true,
action: function(params, options, done) {
var Promise, helpers, init;
Promise = require('bluebird');
init = require('resin-device-init');
helpers = require('../utils/helpers');
return Promise["try"](function() {
var config;
config = JSON.parse(params.config);
return init.initialize(params.image, params.type, config);
}).then(helpers.osProgressHandler).nodeify(done);
}
};

View File

@ -21,9 +21,9 @@ exports.wizard = {
help: 'Use this command to run a friendly wizard to get started with resin.io.\n\nThe wizard will guide you through:\n\n - Create an application.\n - Initialise an SDCard with the resin.io operating system.\n - Associate an existing project directory with your resin.io application.\n - Push your project to your devices.\n\nExamples:\n\n $ resin quickstart\n $ resin quickstart MyApp',
primary: true,
action: function(params, options, done) {
var Promise, capitano, patterns, resin;
var Promise, capitanoRunAsync, patterns, resin;
Promise = require('bluebird');
capitano = Promise.promisifyAll(require('capitano'));
capitanoRunAsync = Promise.promisify(require('capitano').run);
resin = require('resin-sdk-preconfigured');
patterns = require('../utils/patterns');
return resin.auth.isLoggedIn().then(function(isLoggedIn) {
@ -32,7 +32,7 @@ exports.wizard = {
}
console.info('Looks like you\'re not logged in yet!');
console.info('Lets go through a quick wizard to get you started.\n');
return capitano.runAsync('login');
return capitanoRunAsync('login');
}).then(function() {
if (params.name != null) {
return;
@ -42,15 +42,15 @@ exports.wizard = {
if (hasApplication) {
return applicationName;
}
return capitano.runAsync("app create " + applicationName);
return capitanoRunAsync("app create " + applicationName);
});
}).then(function(applicationName) {
return params.name = applicationName;
});
}).then(function() {
return capitano.runAsync("device init --application " + params.name);
return capitanoRunAsync("device init --application " + params.name);
}).tap(patterns.awaitDevice).then(function(uuid) {
return capitano.runAsync("device " + uuid);
return capitanoRunAsync("device " + uuid);
}).then(function() {
return resin.models.application.get(params.name);
}).then(function(application) {

View File

@ -15,7 +15,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
var Promise, Raven, _, actions, capitano, errors, events, plugins, resin, update;
var Promise, Raven, _, actions, capitano, capitanoExecuteAsync, errors, events, plugins, resin, update;
Raven = require('raven');
@ -30,7 +30,9 @@ _ = require('lodash');
Promise = require('bluebird');
capitano = Promise.promisifyAll(require('capitano'));
capitano = require('capitano');
capitanoExecuteAsync = Promise.promisify(capitano.execute);
resin = require('resin-sdk-preconfigured');
@ -179,6 +181,8 @@ capitano.command(actions.local.scan);
capitano.command(actions.local.stop);
capitano.command(actions.internal.osInit);
update.notify();
plugins.register(/^resin-plugin-(.+)$/).then(function() {
@ -187,10 +191,10 @@ plugins.register(/^resin-plugin-(.+)$/).then(function() {
return events.trackCommand(cli).then(function() {
var ref, ref1;
if ((ref = cli.global) != null ? ref.help : void 0) {
return capitano.executeAsync({
return capitanoExecuteAsync({
command: "help " + ((ref1 = cli.command) != null ? ref1 : '')
});
}
return capitano.executeAsync(cli);
return capitanoExecuteAsync(cli);
});
})["catch"](errors.handle);

View File

@ -1,5 +1,5 @@
// Generated by CoffeeScript 1.12.4
var Mixpanel, Promise, _, capitanoState, packageJSON, resin;
var Mixpanel, Promise, _, packageJSON, resin;
_ = require('lodash');
@ -9,8 +9,6 @@ Promise = require('bluebird');
resin = require('resin-sdk-preconfigured');
capitanoState = Promise.promisifyAll(require('capitano').state);
packageJSON = require('../package.json');
exports.getLoggerInstance = _.memoize(function() {
@ -18,12 +16,14 @@ exports.getLoggerInstance = _.memoize(function() {
});
exports.trackCommand = function(capitanoCommand) {
var capitanoStateGetMatchCommandAsync;
capitanoStateGetMatchCommandAsync = Promise.promisify(require('capitano').state.getMatchCommand);
return Promise.props({
resinUrl: resin.settings.get('resinUrl'),
username: resin.auth.whoami(),
mixpanel: exports.getLoggerInstance()
}).then(function(data) {
return capitanoState.getMatchCommandAsync(capitanoCommand.command).then(function(command) {
return capitanoStateGetMatchCommandAsync(capitanoCommand.command).then(function(command) {
return data.mixpanel.track("[CLI] " + (command.signature.toString()), {
distinct_id: data.username,
argv: process.argv.join(' '),

View File

@ -15,37 +15,23 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
var Promise, _, capitano, chalk, imagefs, os, president, resin, rindle;
var Promise;
Promise = require('bluebird');
capitano = Promise.promisifyAll(require('capitano'));
_ = require('lodash');
_.str = require('underscore.string');
president = Promise.promisifyAll(require('president'));
resin = require('resin-sdk-preconfigured');
imagefs = require('resin-image-fs');
rindle = require('rindle');
os = require('os');
chalk = require('chalk');
exports.getGroupDefaults = function(group) {
var _;
_ = require('lodash');
return _.chain(group).get('options').map(function(question) {
return [question.name, question["default"]];
}).object().value();
};
exports.stateToString = function(state) {
var percentage, result;
percentage = _.str.lpad(state.percentage, 3, '0') + '%';
var _str, chalk, percentage, result;
_str = require('underscore.string');
chalk = require('chalk');
percentage = _str.lpad(state.percentage, 3, '0') + '%';
result = (chalk.blue(percentage)) + " " + (chalk.cyan(state.operation.command));
switch (state.operation.command) {
case 'copy':
@ -59,16 +45,23 @@ exports.stateToString = function(state) {
}
};
exports.sudo = function(command, message) {
command = _.union(_.take(process.argv, 2), command);
console.log(message);
exports.sudo = function(command) {
var _, os, presidentExecuteAsync;
_ = require('lodash');
os = require('os');
if (os.platform() !== 'win32') {
console.log('Type your computer password to continue');
console.log('If asked please type your computer password to continue');
}
return president.executeAsync(command);
command = _.union(_.take(process.argv, 2), command);
presidentExecuteAsync = Promise.promisify(require('president').execute);
return presidentExecuteAsync(command);
};
exports.getManifest = function(image, deviceType) {
var imagefs, resin, rindle;
rindle = require('rindle');
imagefs = require('resin-image-fs');
resin = require('resin-sdk-preconfigured');
return imagefs.read({
image: image,
partition: {
@ -79,3 +72,25 @@ exports.getManifest = function(image, deviceType) {
return resin.models.device.getManifestBySlug(deviceType);
});
};
exports.osProgressHandler = function(step) {
var progressBars, rindle, visuals;
rindle = require('rindle');
visuals = require('resin-cli-visuals');
step.on('stdout', process.stdout.write.bind(process.stdout));
step.on('stderr', process.stderr.write.bind(process.stderr));
step.on('state', function(state) {
if (state.operation.command === 'burn') {
return;
}
return console.log(exports.stateToString(state));
});
progressBars = {
write: new visuals.Progress('Writing Device OS'),
check: new visuals.Progress('Validating Device OS')
};
step.on('burn', function(state) {
return progressBars[state.type].update(state);
});
return rindle.wait(step);
};

View File

@ -240,8 +240,7 @@ If signup is successful, you'll be logged in to your new user automatically.
Examples:
$ resin signup
Email: me@mycompany.com
Username: johndoe
Email: johndoe@acme.com
Password: ***********
$ resin whoami
@ -830,7 +829,10 @@ show advanced commands
## os initialize &#60;image&#62;
Use this command to initialize a previously configured operating system image.
Use this command to initialize a device with previously configured operating system image.
Note: Initializing the device may ask for administrative permissions
because we need to access the raw devices directly.
Examples:

View File

@ -73,7 +73,6 @@ exports.login =
action: (params, options, done) ->
_ = require('lodash')
Promise = require('bluebird')
capitano = Promise.promisifyAll(require('capitano'))
resin = require('resin-sdk-preconfigured')
auth = require('resin-cli-auth')
form = require('resin-cli-form')
@ -98,7 +97,8 @@ exports.login =
return patterns.askLoginType().then (loginType) ->
if loginType is 'register'
return capitano.runAsync('signup')
capitanoRunAsync = Promise.promisify(require('capitano').run)
return capitanoRunAsync('signup')
options[loginType] = true
return login(options)
@ -150,8 +150,7 @@ exports.signup =
Examples:
$ resin signup
Email: me@mycompany.com
Username: johndoe
Email: johndoe@acme.com
Password: ***********
$ resin whoami
@ -170,10 +169,6 @@ exports.signup =
name: 'email'
type: 'input'
validate: validation.validateEmail
,
message: 'Username:'
name: 'username'
type: 'input'
,
message: 'Password:'
name: 'password'

View File

@ -48,12 +48,12 @@ exports.read =
Promise = require('bluebird')
config = require('resin-config-json')
visuals = require('resin-cli-visuals')
umount = Promise.promisifyAll(require('umount'))
umountAsync = Promise.promisify(require('umount').umount)
prettyjson = require('prettyjson')
Promise.try ->
return options.drive or visuals.drive('Select the device drive')
.tap(umount.umountAsync)
.tap(umountAsync)
.then (drive) ->
return config.read(drive, options.type)
.tap (configJSON) ->
@ -94,18 +94,18 @@ exports.write =
_ = require('lodash')
config = require('resin-config-json')
visuals = require('resin-cli-visuals')
umount = Promise.promisifyAll(require('umount'))
umountAsync = Promise.promisify(require('umount').umount)
Promise.try ->
return options.drive or visuals.drive('Select the device drive')
.tap(umount.umountAsync)
.tap(umountAsync)
.then (drive) ->
config.read(drive, options.type).then (configJSON) ->
console.info("Setting #{params.key} to #{params.value}")
_.set(configJSON, params.key, params.value)
return configJSON
.tap ->
return umount.umountAsync(drive)
return umountAsync(drive)
.then (configJSON) ->
return config.write(drive, options.type, configJSON)
.tap ->
@ -144,14 +144,14 @@ exports.inject =
Promise = require('bluebird')
config = require('resin-config-json')
visuals = require('resin-cli-visuals')
umount = Promise.promisifyAll(require('umount'))
fs = Promise.promisifyAll(require('fs'))
umountAsync = Promise.promisify(require('umount').umount)
readFileAsync = Promise.promisify(require('fs').readFile)
Promise.try ->
return options.drive or visuals.drive('Select the device drive')
.tap(umount.umountAsync)
.tap(umountAsync)
.then (drive) ->
fs.readFileAsync(params.file, 'utf8').then(JSON.parse).then (configJSON) ->
readFileAsync(params.file, 'utf8').then(JSON.parse).then (configJSON) ->
return config.write(drive, options.type, configJSON)
.tap ->
console.info('Done')
@ -196,21 +196,21 @@ exports.reconfigure =
Promise = require('bluebird')
config = require('resin-config-json')
visuals = require('resin-cli-visuals')
capitano = Promise.promisifyAll(require('capitano'))
umount = Promise.promisifyAll(require('umount'))
capitanoRunAsync = Promise.promisify(require('capitano').run)
umountAsync = Promise.promisify(require('umount').umount)
Promise.try ->
return options.drive or visuals.drive('Select the device drive')
.tap(umount.umountAsync)
.tap(umountAsync)
.then (drive) ->
config.read(drive, options.type).get('uuid')
.tap ->
umount.umountAsync(drive)
umountAsync(drive)
.then (uuid) ->
configureCommand = "os configure #{drive} #{uuid}"
if options.advanced
configureCommand += ' --advanced'
return capitano.runAsync(configureCommand)
return capitanoRunAsync(configureCommand)
.then ->
console.info('Done')
.nodeify(done)
@ -241,7 +241,7 @@ exports.generate =
permission: 'user'
action: (params, options, done) ->
Promise = require('bluebird')
fs = Promise.promisifyAll(require('fs'))
writeFileAsync = Promise.promisify(require('fs').writeFile)
resin = require('resin-sdk-preconfigured')
_ = require('lodash')
form = require('resin-cli-form')
@ -271,7 +271,7 @@ exports.generate =
return deviceConfig.getByApplication(resource.app_name, answers)
.then (config) ->
if options.output?
return fs.writeFileAsync(options.output, JSON.stringify(config))
return writeFileAsync(options.output, JSON.stringify(config))
console.log(prettyjson.render(config))
.nodeify(done)

View File

@ -376,9 +376,10 @@ exports.init =
permission: 'user'
action: (params, options, done) ->
Promise = require('bluebird')
capitano = Promise.promisifyAll(require('capitano'))
capitanoRunAsync = Promise.promisify(require('capitano').run)
rimraf = Promise.promisify(require('rimraf'))
tmp = Promise.promisifyAll(require('tmp'))
tmp = require('tmp')
tmpNameAsync = Promise.promisify(tmp.tmpName)
tmp.setGracefulCleanup()
resin = require('resin-sdk-preconfigured')
@ -392,27 +393,23 @@ exports.init =
.then (application) ->
download = ->
tmp.tmpNameAsync().then (temporalPath) ->
tmpNameAsync().then (tempPath) ->
# TODO: allow version selection
capitano.runAsync("os download #{application.device_type} --output #{temporalPath} --version default")
.disposer (temporalPath) ->
return rimraf(temporalPath)
capitanoRunAsync("os download #{application.device_type} --output '#{tempPath}' --version default")
.disposer (tempPath) ->
return rimraf(tempPath)
Promise.using download(), (temporalPath) ->
capitano.runAsync("device register #{application.app_name}")
Promise.using download(), (tempPath) ->
capitanoRunAsync("device register #{application.app_name}")
.then(resin.models.device.get)
.tap (device) ->
configure = "os configure #{temporalPath} #{device.uuid}"
configure += ' --advanced' if options.advanced
capitano.runAsync(configure).then ->
message = '''
Initializing a device requires administrative permissions
given that we need to access raw devices directly.
'''
helpers.sudo([ 'os', 'initialize', temporalPath, '--type', application.device_type ], message)
configureCommand = "os configure '#{tempPath}' #{device.uuid}"
if options.advanced
configureCommand += ' --advanced'
capitanoRunAsync(configureCommand)
.then ->
osInitCommand = "os initialize '#{tempPath}' --type #{application.device_type}"
capitanoRunAsync(osInitCommand)
# Make sure the device resource is removed if there is an
# error when configuring or initializing a device image
.catch (error) ->

View File

@ -54,12 +54,12 @@ general = (params, options, done) ->
# We do not want the wildcard command
# to be printed in the help screen.
commands = _.reject capitano.state.commands, (command) ->
return command.isWildcard()
return command.hidden or command.isWildcard()
groupedCommands = _.groupBy commands, (command) ->
if command.plugin
return 'plugins'
else if command.primary
if command.primary
return 'primary'
return 'secondary'

View File

@ -31,3 +31,4 @@ module.exports =
config: require('./config')
sync: require('./sync')
ssh: require('./ssh')
internal: require('./internal')

View File

@ -0,0 +1,37 @@
###
Copyright 2016 Resin.io
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
###
# These are internal commands we want to be runnable from the outside
# One use-case for this is spawning the minimal operation with root priviledges
exports.osInit =
signature: 'internal osinit <image> <type> <config>'
description: 'do actual init of the device with the preconfigured os image'
help: '''
Don't use this command directly! Use `resin os initialize <image>` instead.
'''
hidden: true
root: true
action: (params, options, done) ->
Promise = require('bluebird')
init = require('resin-device-init')
helpers = require('../utils/helpers')
return Promise.try ->
config = JSON.parse(params.config)
init.initialize(params.image, params.type, config)
.then(helpers.osProgressHandler)
.nodeify(done)

View File

@ -107,13 +107,14 @@ exports.add =
action: (params, options, done) ->
_ = require('lodash')
Promise = require('bluebird')
fs = Promise.promisifyAll(require('fs'))
readFileAsync = Promise.promisify(require('fs').readFile)
capitano = require('capitano')
resin = require('resin-sdk-preconfigured')
Promise.try ->
return fs.readFileAsync(params.path, encoding: 'utf8') if params.path?
return readFileAsync(params.path, encoding: 'utf8') if params.path?
# TODO: should this be promisified for consistency?
Promise.fromNode (callback) ->
capitano.utils.getStdin (data) ->
return callback(null, data)

View File

@ -67,14 +67,16 @@ module.exports =
action: (params, options, done) ->
_ = require('lodash')
Promise = require('bluebird')
umount = Promise.promisifyAll(require('umount'))
umount = require('umount')
umountAsync = Promise.promisify(umount.umount)
isMountedAsync = Promise.promisify(umount.isMounted)
inquirer = require('inquirer')
reconfix = require('reconfix')
denymount = Promise.promisify(require('denymount'))
umount.isMountedAsync(params.target).then (isMounted) ->
isMountedAsync(params.target).then (isMounted) ->
return if not isMounted
umount.umountAsync(params.target)
umountAsync(params.target)
.then ->
denymount params.target, (cb) ->
reconfix.readConfiguration(CONFIGURATION_SCHEMA, params.target).then (data) ->

View File

@ -43,15 +43,15 @@ module.exports =
_ = require('lodash')
os = require('os')
Promise = require('bluebird')
umount = Promise.promisifyAll(require('umount'))
umountAsync = Promise.promisify(require('umount').umount)
fs = Promise.promisifyAll(require('fs'))
drivelist = Promise.promisifyAll(require('drivelist'))
driveListAsync = Promise.promisify(require('drivelist').list)
chalk = require('chalk')
visuals = require('resin-cli-visuals')
form = require('resin-cli-form')
# XXX: Find a better ES6 module loading story/contract between resin.io modules
# TODO: Find a better ES6 module loading story/contract between resin.io modules
require('babel-register')({
only: /etcher-image-write|bmapflash/
presets: ['es2015']
@ -79,12 +79,14 @@ module.exports =
# otherwise the question will not be asked because
# `false` is a defined value.
yes: options.yes || undefined
# TODO: dedupe with the resin-device-operations
.then (answers) ->
if answers.yes isnt true
console.log(chalk.red.bold('Aborted image flash'))
process.exit(0)
drivelist.listAsync().then (drives) ->
driveListAsync().then (drives) ->
selectedDrive = _.find(drives, device: answers.drive)
if not selectedDrive?
@ -96,7 +98,7 @@ module.exports =
write: new visuals.Progress('Flashing')
check: new visuals.Progress('Validating')
umount.umountAsync(selectedDrive.device).then ->
umountAsync(selectedDrive.device).then ->
Promise.props
imageSize: fs.statAsync(params.image).get('size'),
imageStream: Promise.resolve(fs.createReadStream(params.image))
@ -119,8 +121,8 @@ module.exports =
writer.on('done', resolve)
.then ->
if (os.platform() is 'win32') and selectedDrive.mountpoint?
removedrive = Promise.promisifyAll(require('removedrive'))
return removedrive.ejectAsync(selectedDrive.mountpoint)
ejectAsync = Promise.promisify(require('removedrive').eject)
return ejectAsync(selectedDrive.mountpoint)
return umount.umountAsync(selectedDrive.device)
return umountAsync(selectedDrive.device)
.asCallback(done)

View File

@ -133,25 +133,6 @@ exports.download =
console.info('The image was downloaded successfully')
.nodeify(done)
stepHandler = (step) ->
_ = require('lodash')
rindle = require('rindle')
visuals = require('resin-cli-visuals')
helpers = require('../utils/helpers')
step.on('stdout', _.bind(process.stdout.write, process.stdout))
step.on('stderr', _.bind(process.stderr.write, process.stderr))
step.on 'state', (state) ->
return if state.operation.command is 'burn'
console.log(helpers.stateToString(state))
bar = new visuals.Progress('Writing Device OS')
step.on('burn', _.bind(bar.update, bar))
return rindle.wait(step)
exports.configure =
signature: 'os configure <image> <uuid>'
description: 'configure an os image'
@ -192,19 +173,26 @@ exports.configure =
return form.run(questions, { override })
.then (answers) ->
init.configure(params.image, params.uuid, answers).then(stepHandler)
init.configure(params.image, params.uuid, answers).then(helpers.osProgressHandler)
.nodeify(done)
initWarningMessage = '''
Note: Initializing the device may ask for administrative permissions
because we need to access the raw devices directly.
'''
exports.initialize =
signature: 'os initialize <image>'
description: 'initialize an os image'
help: '''
Use this command to initialize a previously configured operating system image.
help: """
Use this command to initialize a device with previously configured operating system image.
#{initWarningMessage}
Examples:
$ resin os initialize ../path/rpi.img --type 'raspberry-pi'
'''
"""
permission: 'user'
options: [
commandOptions.yes
@ -222,16 +210,18 @@ exports.initialize =
alias: 'd'
}
]
root: true
action: (params, options, done) ->
Promise = require('bluebird')
umount = Promise.promisifyAll(require('umount'))
umountAsync = Promise.promisify(require('umount').umount)
form = require('resin-cli-form')
init = require('resin-device-init')
patterns = require('../utils/patterns')
helpers = require('../utils/helpers')
console.info('Initializing device')
console.info("""
Initializing device
#{initWarningMessage}
""")
helpers.getManifest(params.image, options.type)
.then (manifest) ->
return manifest.initialization?.options
@ -244,11 +234,33 @@ exports.initialize =
message = "This will erase #{answers.drive}. Are you sure?"
patterns.confirm(options.yes, message)
.return(answers.drive)
.then(umount.umountAsync)
.then(umountAsync)
.tap (answers) ->
return init.initialize(params.image, options.type, answers).then(stepHandler)
return helpers.sudo([
'internal'
'osinit'
params.image
options.type
JSON.stringify(answers)
])
.then (answers) ->
return if not answers.drive?
umount.umountAsync(answers.drive).tap ->
# TODO: resin local makes use of ejectAsync, see below
# DO we need this / should we do that here?
# getDrive = (drive) ->
# driveListAsync().then (drives) ->
# selectedDrive = _.find(drives, device: drive)
# if not selectedDrive?
# throw new Error("Drive not found: #{drive}")
# return selectedDrive
# if (os.platform() is 'win32') and selectedDrive.mountpoint?
# ejectAsync = Promise.promisify(require('removedrive').eject)
# return ejectAsync(selectedDrive.mountpoint)
umountAsync(answers.drive).tap ->
console.info("You can safely remove #{answers.drive} now")
.nodeify(done)

View File

@ -35,7 +35,7 @@ exports.wizard =
primary: true
action: (params, options, done) ->
Promise = require('bluebird')
capitano = Promise.promisifyAll(require('capitano'))
capitanoRunAsync = Promise.promisify(require('capitano').run)
resin = require('resin-sdk-preconfigured')
patterns = require('../utils/patterns')
@ -43,20 +43,20 @@ exports.wizard =
return if isLoggedIn
console.info('Looks like you\'re not logged in yet!')
console.info('Lets go through a quick wizard to get you started.\n')
return capitano.runAsync('login')
return capitanoRunAsync('login')
.then ->
return if params.name?
patterns.selectOrCreateApplication().tap (applicationName) ->
resin.models.application.has(applicationName).then (hasApplication) ->
return applicationName if hasApplication
capitano.runAsync("app create #{applicationName}")
capitanoRunAsync("app create #{applicationName}")
.then (applicationName) ->
params.name = applicationName
.then ->
return capitano.runAsync("device init --application #{params.name}")
return capitanoRunAsync("device init --application #{params.name}")
.tap(patterns.awaitDevice)
.then (uuid) ->
return capitano.runAsync("device #{uuid}")
return capitanoRunAsync("device #{uuid}")
.then ->
return resin.models.application.get(params.name)
.then (application) ->

View File

@ -24,7 +24,8 @@ Raven.config(
_ = require('lodash')
Promise = require('bluebird')
capitano = Promise.promisifyAll(require('capitano'))
capitano = require('capitano')
capitanoExecuteAsync = Promise.promisify(capitano.execute)
resin = require('resin-sdk-preconfigured')
actions = require('./actions')
errors = require('./errors')
@ -142,6 +143,9 @@ capitano.command(actions.local.ssh)
capitano.command(actions.local.scan)
capitano.command(actions.local.stop)
# ---------- Internal utils ----------
capitano.command(actions.internal.osInit)
update.notify()
plugins.register(/^resin-plugin-(.+)$/).then ->
@ -149,7 +153,7 @@ plugins.register(/^resin-plugin-(.+)$/).then ->
events.trackCommand(cli).then ->
if cli.global?.help
return capitano.executeAsync(command: "help #{cli.command ? ''}")
capitano.executeAsync(cli)
return capitanoExecuteAsync(command: "help #{cli.command ? ''}")
capitanoExecuteAsync(cli)
.catch(errors.handle)

View File

@ -2,19 +2,20 @@ _ = require('lodash')
Mixpanel = require('mixpanel')
Promise = require('bluebird')
resin = require('resin-sdk-preconfigured')
capitanoState = Promise.promisifyAll(require('capitano').state)
packageJSON = require('../package.json')
exports.getLoggerInstance = _.memoize ->
return resin.models.config.getMixpanelToken().then(Mixpanel.init)
exports.trackCommand = (capitanoCommand) ->
capitanoStateGetMatchCommandAsync = Promise.promisify(require('capitano').state.getMatchCommand)
return Promise.props
resinUrl: resin.settings.get('resinUrl')
username: resin.auth.whoami()
mixpanel: exports.getLoggerInstance()
.then (data) ->
return capitanoState.getMatchCommandAsync(capitanoCommand.command).then (command) ->
return capitanoStateGetMatchCommandAsync(capitanoCommand.command).then (command) ->
data.mixpanel.track "[CLI] #{command.signature.toString()}",
distinct_id: data.username
argv: process.argv.join(' ')

View File

@ -15,17 +15,10 @@ limitations under the License.
###
Promise = require('bluebird')
capitano = Promise.promisifyAll(require('capitano'))
_ = require('lodash')
_.str = require('underscore.string')
president = Promise.promisifyAll(require('president'))
resin = require('resin-sdk-preconfigured')
imagefs = require('resin-image-fs')
rindle = require('rindle')
os = require('os')
chalk = require('chalk')
exports.getGroupDefaults = (group) ->
_ = require('lodash')
return _.chain(group)
.get('options')
.map (question) ->
@ -34,7 +27,10 @@ exports.getGroupDefaults = (group) ->
.value()
exports.stateToString = (state) ->
percentage = _.str.lpad(state.percentage, 3, '0') + '%'
_str = require('underscore.string')
chalk = require('chalk')
percentage = _str.lpad(state.percentage, 3, '0') + '%'
result = "#{chalk.blue(percentage)} #{chalk.cyan(state.operation.command)}"
switch state.operation.command
@ -47,16 +43,21 @@ exports.stateToString = (state) ->
else
throw new Error("Unsupported operation: #{state.operation.type}")
exports.sudo = (command, message) ->
command = _.union(_.take(process.argv, 2), command)
console.log(message)
exports.sudo = (command) ->
_ = require('lodash')
os = require('os')
if os.platform() isnt 'win32'
console.log('Type your computer password to continue')
console.log('If asked please type your computer password to continue')
return president.executeAsync(command)
command = _.union(_.take(process.argv, 2), command)
presidentExecuteAsync = Promise.promisify(require('president').execute)
return presidentExecuteAsync(command)
exports.getManifest = (image, deviceType) ->
rindle = require('rindle')
imagefs = require('resin-image-fs')
resin = require('resin-sdk-preconfigured')
# Attempt to read manifest from the first
# partition, but fallback to the API if
@ -70,3 +71,23 @@ exports.getManifest = (image, deviceType) ->
.then(JSON.parse)
.catch ->
resin.models.device.getManifestBySlug(deviceType)
exports.osProgressHandler = (step) ->
rindle = require('rindle')
visuals = require('resin-cli-visuals')
step.on('stdout', process.stdout.write.bind(process.stdout))
step.on('stderr', process.stderr.write.bind(process.stderr))
step.on 'state', (state) ->
return if state.operation.command is 'burn'
console.log(exports.stateToString(state))
progressBars =
write: new visuals.Progress('Writing Device OS')
check: new visuals.Progress('Validating Device OS')
step.on 'burn', (state) ->
progressBars[state.type].update(state)
return rindle.wait(step)

View File

@ -61,7 +61,7 @@
"resin-cli-visuals": "^1.3.0",
"resin-config-json": "^1.0.0",
"resin-device-config": "^3.0.0",
"resin-device-init": "^2.1.0",
"resin-device-init": "^2.2.0",
"resin-image-fs": "^2.1.2",
"resin-image-manager": "^4.1.0",
"resin-sdk-preconfigured": "^0.1.1",
@ -75,5 +75,8 @@
"unzip2": "^0.2.5",
"update-notifier": "^0.6.1",
"valid-email": "^0.0.2"
},
"optionalDependencies": {
"removedrive": "^1.0.0"
}
}