isolate the sudo-runnable command

This commit is contained in:
Eugene Mirotin 2017-03-24 12:48:14 +03:00
parent 857c5204b9
commit 6cf32e445a
16 changed files with 244 additions and 121 deletions

View File

@ -291,23 +291,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 tmp.tmpNameAsync().then(function(tempPath) {
return capitano.runAsync("os download " + application.device_type + " --output '" + tempPath + "' --version default");
}).disposer(function(tempPath) {
return rimraf(tempPath);
});
};
return Promise.using(download(), function(temporalPath) {
return Promise.using(download(), function(tempPath) {
return capitano.runAsync("device register " + application.app_name).then(resin.models.device.get).tap(function(device) {
var configure;
configure = "os configure " + temporalPath + " " + device.uuid;
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 capitano.runAsync(configureCommand).then(function() {
var osInitCommand;
osInitCommand = "os initialize '" + tempPath + "' --type " + application.device_type;
return capitano.runAsync(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

@ -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, umount;
Promise = require('bluebird');
umount = Promise.promisifyAll(require('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;
@ -228,7 +209,7 @@ exports.initialize = {
message = "This will erase " + answers.drive + ". Are you sure?";
return patterns.confirm(options.yes, message)["return"](answers.drive).then(umount.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;

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

@ -179,6 +179,8 @@ capitano.command(actions.local.scan);
capitano.command(actions.local.stop);
capitano.command(actions.internal.osInit);
update.notify();
plugins.register(/^resin-plugin-(.+)$/).then(function() {

View File

@ -15,37 +15,27 @@ 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, capitano, president;
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 +49,22 @@ 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;
_ = 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');
}
command = _.union(_.take(process.argv, 2), command);
return president.executeAsync(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 +75,20 @@ exports.getManifest = function(image, deviceType) {
return resin.models.device.getManifestBySlug(deviceType);
});
};
exports.osProgressHandler = function(step) {
var bar, 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));
});
bar = new visuals.Progress('Writing Device OS');
step.on('burn', bar.update.bind(bar));
return rindle.wait(step);
};

View File

@ -830,7 +830,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

@ -392,27 +392,23 @@ exports.init =
.then (application) ->
download = ->
tmp.tmpNameAsync().then (temporalPath) ->
tmp.tmpNameAsync().then (tempPath) ->
# TODO: allow version selection
capitano.runAsync("os download #{application.device_type} --output #{temporalPath} --version default")
.disposer (temporalPath) ->
return rimraf(temporalPath)
capitano.runAsync("os download #{application.device_type} --output '#{tempPath}' --version default")
.disposer (tempPath) ->
return rimraf(tempPath)
Promise.using download(), (temporalPath) ->
Promise.using download(), (tempPath) ->
capitano.runAsync("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'
capitano.runAsync(configureCommand)
.then ->
osInitCommand = "os initialize '#{tempPath}' --type #{application.device_type}"
capitano.runAsync(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

@ -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'))
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
@ -246,7 +236,13 @@ exports.initialize =
.return(answers.drive)
.then(umount.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 ->

View File

@ -142,6 +142,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 ->

View File

@ -16,16 +16,11 @@ 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 +29,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 +45,20 @@ 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')
command = _.union(_.take(process.argv, 2), command)
return president.executeAsync(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 +72,20 @@ 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))
bar = new visuals.Progress('Writing Device OS')
step.on('burn', bar.update.bind(bar))
return rindle.wait(step)