Merge pull request #367 from resin-io/features/resin-sync-ssh-improvements

resin sync/ssh improvements
This commit is contained in:
Juan Cruz Viotti 2016-09-15 09:21:24 -07:00 committed by GitHub
commit baf367b276
8 changed files with 247 additions and 111 deletions

View File

@ -35,9 +35,9 @@ limitations under the License.
};
module.exports = {
signature: 'ssh [destination]',
signature: 'ssh [uuid]',
description: '(beta) get a shell into the running app container of a device',
help: 'WARNING: If you\'re running Windows, this command only supports `cmd.exe`.\n\nUse this command to get a shell into the running application container of\nyour device.\n\nThe `destination` argument can be either a device uuid or an application name.\n\nExamples:\n\n $ resin ssh MyApp\n $ resin ssh 7cf02a6\n $ resin ssh 7cf02a6 --port 8080\n $ resin ssh 7cf02a6 -v',
help: 'WARNING: If you\'re running Windows, this command only supports `cmd.exe`.\n\nUse this command to get a shell into the running application container of\nyour device.\n\nExamples:\n\n $ resin ssh MyApp\n $ resin ssh 7cf02a6\n $ resin ssh 7cf02a6 --port 8080\n $ resin ssh 7cf02a6 -v',
permission: 'user',
primary: true,
options: [
@ -45,7 +45,7 @@ limitations under the License.
signature: 'port',
parameter: 'port',
description: 'ssh gateway port',
alias: 't'
alias: 'p'
}, {
signature: 'verbose',
boolean: true,
@ -64,11 +64,11 @@ limitations under the License.
options.port = 22;
}
verbose = options.verbose ? '-vvv' : '';
return resin.models.device.has(params.destination).then(function(isValidUUID) {
return resin.models.device.has(params.uuid).then(function(isValidUUID) {
if (isValidUUID) {
return params.destination;
return params.uuid;
}
return patterns.inferOrSelectDevice(params.destination);
return patterns.inferOrSelectDevice();
}).then(function(uuid) {
console.info("Connecting with: " + uuid);
return resin.models.device.get(uuid);
@ -88,7 +88,7 @@ limitations under the License.
}
return Promise["try"](function() {
var command, spawn, subShellCommand;
command = "ssh " + verbose + " -t -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p " + options.port + " " + username + "@ssh." + (settings.get('proxyUrl')) + " enter " + uuid + " " + containerId;
command = "ssh " + verbose + " -t -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ControlMaster=no -p " + options.port + " " + username + "@ssh." + (settings.get('proxyUrl')) + " enter " + uuid + " " + containerId;
subShellCommand = getSubShellCommand(command);
return spawn = child_process.spawn(subShellCommand.program, subShellCommand.args, {
stdio: 'inherit'

View File

@ -16,38 +16,79 @@ limitations under the License.
*/
(function() {
var loadConfig;
loadConfig = function(source) {
var _, config, configPath, error, error1, fs, jsYaml, path, result;
fs = require('fs');
path = require('path');
_ = require('lodash');
jsYaml = require('js-yaml');
configPath = path.join(source, '.resin-sync.yml');
try {
config = fs.readFileSync(configPath, {
encoding: 'utf8'
});
result = jsYaml.safeLoad(config);
} catch (error1) {
error = error1;
if (error.code === 'ENOENT') {
return {};
}
throw error;
}
if (!_.isPlainObject(result)) {
throw new Error("Invalid configuration file: " + configPath);
}
return result;
};
module.exports = {
signature: 'sync [destination]',
description: '(beta) sync your changes with a device',
help: 'WARNING: If you\'re running Windows, this command only supports `cmd.exe`.\n\nUse this command to sync your local changes to a certain device on the fly.\n\nThe `destination` argument can be either a device uuid or an application name.\n\nYou can save all the options mentioned below in a `resin-sync.yml` file,\nby using the same option names as keys. For example:\n\n $ cat $PWD/resin-sync.yml\n source: src/\n before: \'echo Hello\'\n ignore:\n - .git\n - node_modules/\n progress: true\n verbose: false\n\nNotice that explicitly passed command options override the ones set in the configuration file.\n\nExamples:\n\n $ resin sync MyApp\n $ resin sync 7cf02a6\n $ resin sync 7cf02a6 --port 8080\n $ resin sync 7cf02a6 --ignore foo,bar\n $ resin sync 7cf02a6 -v',
signature: 'sync [uuid]',
description: '(beta) sync your changes to a device',
help: 'WARNING: If you\'re running Windows, this command only supports `cmd.exe`.\n\nUse this command to sync your local changes to a certain device on the fly.\n\nAfter every \'resin sync\' the updated settings will be saved in\n\'<source>/.resin-sync.yml\' and will be used in later invocations. You can\nalso change any option by editing \'.resin-sync.yml\' directly.\n\nHere is an example \'.resin-sync.yml\' :\n\n $ cat $PWD/.resin-sync.yml\n uuid: 7cf02a6\n destination: \'/usr/src/app\'\n before: \'echo Hello\'\n after: \'echo Done\'\n ignore:\n - .git\n - node_modules/\n\nCommand line options have precedence over the ones saved in \'.resin-sync.yml\'.\n\nIf \'.gitignore\' is found in the source directory then all explicitly listed files will be\nexcluded from the syncing process. You can choose to change this default behavior with the\n\'--skip-gitignore\' option.\n\nExamples:\n\n $ resin sync 7cf02a6 --source . --destination /usr/src/app\n $ resin sync 7cf02a6 -s /home/user/myResinProject -d /usr/src/app --before \'echo Hello\' --after \'echo Done\'\n $ resin sync --ignore lib/\n $ resin sync --verbose false\n $ resin sync',
permission: 'user',
primary: true,
options: [
{
signature: 'source',
parameter: 'path',
description: 'custom source path',
description: 'local directory path to synchronize to device',
alias: 's'
}, {
signature: 'destination',
parameter: 'path',
description: 'destination path on device',
alias: 'd'
}, {
signature: 'ignore',
parameter: 'paths',
description: 'comma delimited paths to ignore when syncing',
alias: 'i'
}, {
signature: 'skip-gitignore',
boolean: true,
description: 'do not parse excluded/included files from .gitignore'
}, {
signature: 'before',
parameter: 'command',
description: 'execute a command before syncing',
alias: 'b'
}, {
signature: 'progress',
boolean: true,
description: 'show progress',
alias: 'p'
signature: 'after',
parameter: 'command',
description: 'execute a command after syncing',
alias: 'a'
}, {
signature: 'port',
parameter: 'port',
description: 'ssh port',
alias: 't'
}, {
signature: 'progress',
boolean: true,
description: 'show progress',
alias: 'p'
}, {
signature: 'verbose',
boolean: true,
@ -56,20 +97,43 @@ limitations under the License.
}
],
action: function(params, options, done) {
var patterns, resin, resinSync;
var Promise, fs, path, patterns, resin, resinSync;
fs = require('fs');
path = require('path');
resin = require('resin-sdk');
Promise = require('bluebird');
resinSync = require('resin-sync');
patterns = require('../utils/patterns');
if (options.ignore != null) {
options.ignore = options.ignore.split(',');
}
return resin.models.device.has(params.destination).then(function(isValidUUID) {
if (isValidUUID) {
return params.destination;
return Promise["try"](function() {
var error1;
try {
fs.accessSync(path.join(process.cwd(), '.resin-sync.yml'));
} catch (error1) {
if (options.source == null) {
throw new Error('No --source option passed and no \'.resin-sync.yml\' file found in current directory.');
}
}
return patterns.inferOrSelectDevice(params.destination);
}).then(function(uuid) {
return resinSync.sync(uuid, options);
if (options.source == null) {
options.source = process.cwd();
}
if (options.ignore != null) {
options.ignore = options.ignore.split(',');
}
return Promise.resolve(params.uuid).then(function(uuid) {
var savedUuid;
if (uuid == null) {
savedUuid = loadConfig(options.source).uuid;
return patterns.inferOrSelectDevice(savedUuid);
}
return resin.models.device.has(uuid).then(function(hasDevice) {
if (!hasDevice) {
throw new Error("Device not found: " + uuid);
}
return uuid;
});
}).then(function(uuid) {
return resinSync.sync(uuid, options);
});
}).nodeify(done);
}
};

View File

@ -16,7 +16,8 @@ limitations under the License.
*/
(function() {
var Promise, _, chalk, form, messages, resin, validation, visuals;
var Promise, _, chalk, form, messages, resin, validation, visuals,
indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
_ = require('lodash');
@ -191,25 +192,18 @@ limitations under the License.
});
};
exports.inferOrSelectDevice = function(applicationName) {
return Promise["try"](function() {
if (applicationName != null) {
return resin.models.device.getAllByApplication(applicationName);
}
return resin.models.device.getAll();
}).filter(function(device) {
exports.inferOrSelectDevice = function(preferredUuid) {
return resin.models.device.getAll().filter(function(device) {
return device.is_online;
}).then(function(devices) {
if (_.isEmpty(devices)) {
throw new Error('You don\'t have any devices');
}
if (devices.length === 1) {
return _.first(devices).uuid;
}).then(function(onlineDevices) {
if (_.isEmpty(onlineDevices)) {
throw new Error('You don\'t have any devices online');
}
return form.ask({
message: 'Select a device',
type: 'list',
choices: _.map(devices, function(device) {
"default": indexOf.call(_.map(onlineDevices, 'uuid'), preferredUuid) >= 0 ? preferredUuid : onlineDevices[0].uuid,
choices: _.map(onlineDevices, function(device) {
return {
name: (device.name || 'Untitled') + " (" + (device.uuid.slice(0, 7)) + ")",
value: device.uuid

View File

@ -69,11 +69,11 @@ Now you have access to all the commands referenced below.
- Sync
- [sync [source]](#sync-source-)
- [sync [uuid]](#sync-uuid-)
- SSH
- [ssh &#60;uuid&#62;](#ssh-60-uuid-62-)
- [ssh [uuid]](#ssh-uuid-)
- Notes
@ -582,65 +582,82 @@ continuously stream output
# Sync
## sync [source]
## sync [uuid]
WARNING: If you're running Windows, this command only supports `cmd.exe`.
Use this command to sync your local changes to a certain device on the fly.
The `source` argument can be either a device uuid or an application name.
After every 'resin sync' the updated settings will be saved in
'<source>/.resin-sync.yml' and will be used in later invocations. You can
also change any option by editing '.resin-sync.yml' directly.
You can save all the options mentioned below in a `resin-sync.yml` file,
by using the same option names as keys. For example:
Here is an example '.resin-sync.yml' :
$ cat $PWD/resin-sync.yml
source: src/
$ cat $PWD/.resin-sync.yml
uuid: 7cf02a6
destination: '/usr/src/app'
before: 'echo Hello'
after: 'echo Done'
ignore:
- .git
- node_modules/
progress: true
verbose: false
Notice that explicitly passed command options override the ones set in the configuration file.
Command line options have precedence over the ones saved in '.resin-sync.yml'.
If '.gitignore' is found in the source directory then all explicitly listed files will be
excluded from the syncing process. You can choose to change this default behavior with the
'--skip-gitignore' option.
Examples:
$ resin sync MyApp
$ resin sync 7cf02a6
$ resin sync 7cf02a6 --port 8080
$ resin sync 7cf02a6 --ignore foo,bar
$ resin sync 7cf02a6 -v
$ resin sync 7cf02a6 --source . --destination /usr/src/app
$ resin sync 7cf02a6 -s /home/user/myResinProject -d /usr/src/app --before 'echo Hello' --after 'echo Done'
$ resin sync --ignore lib/
$ resin sync --verbose false
$ resin sync
### Options
#### --source, -s &#60;path&#62;
custom source path
local directory path to synchronize to device
#### --destination, -d &#60;path&#62;
destination path on device
#### --ignore, -i &#60;paths&#62;
comma delimited paths to ignore when syncing
#### --skip-gitignore
do not parse excluded/included files from .gitignore
#### --before, -b &#60;command&#62;
execute a command before syncing
#### --progress, -p
#### --after, -a &#60;command&#62;
show progress
execute a command after syncing
#### --port, -t &#60;port&#62;
ssh port
#### --progress, -p
show progress
#### --verbose, -v
increase verbosity
# SSH
## ssh &#60;uuid&#62;
## ssh [uuid]
WARNING: If you're running Windows, this command only supports `cmd.exe`.
@ -649,13 +666,14 @@ your device.
Examples:
$ resin ssh MyApp
$ resin ssh 7cf02a6
$ resin ssh 7cf02a6 --port 8080
$ resin ssh 7cf02a6 -v
### Options
#### --port, -t &#60;port&#62;
#### --port, -p &#60;port&#62;
ssh gateway port

View File

@ -36,7 +36,7 @@ getSubShellCommand = (command) ->
}
module.exports =
signature: 'ssh [destination]'
signature: 'ssh [uuid]'
description: '(beta) get a shell into the running app container of a device'
help: '''
WARNING: If you're running Windows, this command only supports `cmd.exe`.
@ -44,8 +44,6 @@ module.exports =
Use this command to get a shell into the running application container of
your device.
The `destination` argument can be either a device uuid or an application name.
Examples:
$ resin ssh MyApp
@ -59,7 +57,7 @@ module.exports =
signature: 'port'
parameter: 'port'
description: 'ssh gateway port'
alias: 't'
alias: 'p'
,
signature: 'verbose'
boolean: true
@ -78,11 +76,11 @@ module.exports =
verbose = if options.verbose then '-vvv' else ''
resin.models.device.has(params.destination).then (isValidUUID) ->
resin.models.device.has(params.uuid).then (isValidUUID) ->
if isValidUUID
return params.destination
return params.uuid
return patterns.inferOrSelectDevice(params.destination)
return patterns.inferOrSelectDevice()
.then (uuid) ->
console.info("Connecting with: #{uuid}")
resin.models.device.get(uuid)
@ -96,7 +94,11 @@ module.exports =
.then ({ username, uuid, containerId }) ->
throw new Error('Did not find running application container') if not containerId?
Promise.try ->
command = "ssh #{verbose} -t -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
command = "ssh #{verbose} -t \
-o LogLevel=ERROR \
-o StrictHostKeyChecking=no \
-o UserKnownHostsFile=/dev/null \
-o ControlMaster=no \
-p #{options.port} #{username}@ssh.#{settings.get('proxyUrl')} enter #{uuid} #{containerId}"
subShellCommand = getSubShellCommand(command)

View File

@ -14,65 +14,110 @@ See the License for the specific language governing permissions and
limitations under the License.
###
# Loads '.resin-sync.yml' configuration from 'source' directory.
# Returns the configuration object on success
#
# TODO: Use 'config.load()' method from `resin sync` when resin sync gets
# integrated into resin CLI
loadConfig = (source) ->
fs = require('fs')
path = require('path')
_ = require('lodash')
jsYaml = require('js-yaml')
configPath = path.join(source, '.resin-sync.yml')
try
config = fs.readFileSync(configPath, encoding: 'utf8')
result = jsYaml.safeLoad(config)
catch error
# return empty object if '.resin-sync.yml' is missing
if error.code is 'ENOENT'
return {}
throw error
if not _.isPlainObject(result)
throw new Error("Invalid configuration file: #{configPath}")
return result
module.exports =
signature: 'sync [destination]'
description: '(beta) sync your changes with a device'
signature: 'sync [uuid]'
description: '(beta) sync your changes to a device'
help: '''
WARNING: If you're running Windows, this command only supports `cmd.exe`.
Use this command to sync your local changes to a certain device on the fly.
The `destination` argument can be either a device uuid or an application name.
After every 'resin sync' the updated settings will be saved in
'<source>/.resin-sync.yml' and will be used in later invocations. You can
also change any option by editing '.resin-sync.yml' directly.
You can save all the options mentioned below in a `resin-sync.yml` file,
by using the same option names as keys. For example:
Here is an example '.resin-sync.yml' :
$ cat $PWD/resin-sync.yml
source: src/
$ cat $PWD/.resin-sync.yml
uuid: 7cf02a6
destination: '/usr/src/app'
before: 'echo Hello'
after: 'echo Done'
ignore:
- .git
- node_modules/
progress: true
verbose: false
Notice that explicitly passed command options override the ones set in the configuration file.
Command line options have precedence over the ones saved in '.resin-sync.yml'.
If '.gitignore' is found in the source directory then all explicitly listed files will be
excluded from the syncing process. You can choose to change this default behavior with the
'--skip-gitignore' option.
Examples:
$ resin sync MyApp
$ resin sync 7cf02a6
$ resin sync 7cf02a6 --port 8080
$ resin sync 7cf02a6 --ignore foo,bar
$ resin sync 7cf02a6 -v
$ resin sync 7cf02a6 --source . --destination /usr/src/app
$ resin sync 7cf02a6 -s /home/user/myResinProject -d /usr/src/app --before 'echo Hello' --after 'echo Done'
$ resin sync --ignore lib/
$ resin sync --verbose false
$ resin sync
'''
permission: 'user'
primary: true
options: [
signature: 'source'
parameter: 'path'
description: 'custom source path'
description: 'local directory path to synchronize to device'
alias: 's'
,
signature: 'destination'
parameter: 'path'
description: 'destination path on device'
alias: 'd'
,
signature: 'ignore'
parameter: 'paths'
description: 'comma delimited paths to ignore when syncing'
alias: 'i'
,
signature: 'skip-gitignore'
boolean: true
description: 'do not parse excluded/included files from .gitignore'
,
signature: 'before'
parameter: 'command'
description: 'execute a command before syncing'
alias: 'b'
,
signature: 'progress'
boolean: true
description: 'show progress'
alias: 'p'
signature: 'after'
parameter: 'command'
description: 'execute a command after syncing'
alias: 'a'
,
signature: 'port'
parameter: 'port'
description: 'ssh port'
alias: 't'
,
signature: 'progress'
boolean: true
description: 'show progress'
alias: 'p'
,
signature: 'verbose'
boolean: true
@ -81,19 +126,37 @@ module.exports =
,
]
action: (params, options, done) ->
fs = require('fs')
path = require('path')
resin = require('resin-sdk')
Promise = require('bluebird')
resinSync = require('resin-sync')
patterns = require('../utils/patterns')
# TODO: Add comma separated options to Capitano
if options.ignore?
options.ignore = options.ignore.split(',')
Promise.try ->
try
fs.accessSync(path.join(process.cwd(), '.resin-sync.yml'))
catch
if not options.source?
throw new Error('No --source option passed and no \'.resin-sync.yml\' file found in current directory.')
resin.models.device.has(params.destination).then (isValidUUID) ->
if isValidUUID
return params.destination
options.source ?= process.cwd()
return patterns.inferOrSelectDevice(params.destination)
.then (uuid) ->
resinSync.sync(uuid, options)
# TODO: Add comma separated options to Capitano
if options.ignore?
options.ignore = options.ignore.split(',')
Promise.resolve(params.uuid)
.then (uuid) ->
if not uuid?
savedUuid = loadConfig(options.source).uuid
return patterns.inferOrSelectDevice(savedUuid)
resin.models.device.has(uuid)
.then (hasDevice) ->
if not hasDevice
throw new Error("Device not found: #{uuid}")
return uuid
.then (uuid) ->
resinSync.sync(uuid, options)
.nodeify(done)

View File

@ -150,24 +150,19 @@ exports.awaitDevice = (uuid) ->
console.info("Waiting for #{deviceName} to connect to resin...")
poll().return(uuid)
exports.inferOrSelectDevice = (applicationName) ->
Promise.try ->
if applicationName?
return resin.models.device.getAllByApplication(applicationName)
return resin.models.device.getAll()
exports.inferOrSelectDevice = (preferredUuid) ->
resin.models.device.getAll()
.filter (device) ->
device.is_online
.then (devices) ->
if _.isEmpty(devices)
throw new Error('You don\'t have any devices')
if devices.length is 1
return _.first(devices).uuid
.then (onlineDevices) ->
if _.isEmpty(onlineDevices)
throw new Error('You don\'t have any devices online')
return form.ask
message: 'Select a device'
type: 'list'
choices: _.map devices, (device) ->
default: if preferredUuid in _.map(onlineDevices, 'uuid') then preferredUuid else onlineDevices[0].uuid
choices: _.map onlineDevices, (device) ->
return {
name: "#{device.name or 'Untitled'} (#{device.uuid.slice(0, 7)})"
value: device.uuid

View File

@ -55,7 +55,7 @@
"resin-pine": "^1.3.0",
"resin-sdk": "^5.3.5",
"resin-settings-client": "^3.5.0",
"resin-sync": "^2.0.2",
"resin-sync": "^3.0.0",
"resin-vcs": "^2.0.0",
"rimraf": "^2.4.3",
"rindle": "^1.0.0",