From 00753a577622a2b53aa4fa8031fe46c053358d3e Mon Sep 17 00:00:00 2001 From: Kostas Lekkas Date: Wed, 8 Mar 2017 18:43:34 +0000 Subject: [PATCH 01/10] Implement 'resin local configure' --- lib/actions/local/configure.coffee | 128 +++++++++++++++++++++++++++++ lib/actions/local/index.coffee | 17 ++++ package.json | 5 +- 3 files changed, 149 insertions(+), 1 deletion(-) create mode 100644 lib/actions/local/configure.coffee create mode 100644 lib/actions/local/index.coffee diff --git a/lib/actions/local/configure.coffee b/lib/actions/local/configure.coffee new file mode 100644 index 00000000..c543f4f6 --- /dev/null +++ b/lib/actions/local/configure.coffee @@ -0,0 +1,128 @@ +### +Copyright 2017 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. +### + +CONFIGURATION_SCHEMA = + mapper: [ + { + template: + hostname: '{{hostname}}' + persistentLogging: '{{persistentLogging}}' + domain: [ + [ 'config_json', 'hostname' ] + [ 'config_json', 'persistentLogging' ] + ] + } + { + template: + wifi: + ssid: '{{networkSsid}}' + 'wifi-security': + psk: '{{networkKey}}' + domain: [ + [ 'system_connections', 'resin-sample', 'wifi' ] + [ 'system_connections', 'resin-sample', 'wifi-security' ] + ] + } + ] + files: + system_connections: + fileset: true + type: 'ini' + location: + path: 'system-connections' + partition: + primary: 1 + config_json: + type: 'json' + location: + path: 'config.json' + partition: + primary: 1 + +module.exports = + signature: 'local configure ' + description: '(Re)configure a resinOS drive or image' + help: ''' + Use this command to configure or reconfigure a resinOS drive or image. + + Examples: + + $ resin local configure /dev/sdc + $ resin local configure path/to/image.img + ''' + root: true + action: (params, options, done) -> + _ = require('lodash') + Promise = require('bluebird') + umount = Promise.promisifyAll(require('umount')) + inquirer = require('inquirer') + reconfix = require('reconfix') + denymount = Promise.promisify(require('denymount')) + + umount.isMountedAsync(params.target).then (isMounted) -> + return if not isMounted + umount.umountAsync(params.target) + .then -> + denymount params.target, (cb) -> + reconfix.readConfiguration(CONFIGURATION_SCHEMA, params.target).then (data) -> + + # `persistentLogging` can be `undefined`, so we want + # to make sure that case defaults to `false` + data.persistentLogging = data.persistentLogging or false + + inquirer.prompt([ + { + message: 'Network SSID' + type: 'input' + name: 'networkSsid' + default: data.networkSsid + } + { + message: 'Network Key' + type: 'input' + name: 'networkKey' + default: data.networkKey + } + { + message: 'Do you want to set advanced settings?' + type: 'confirm' + name: 'advancedSettings' + default: false + } + { + message: 'Device Hostname' + type: 'input' + name: 'hostname' + default: data.hostname, + when: (answers) -> + answers.advancedSettings + } + { + message: 'Do you want to enable persistent logging?' + type: 'confirm' + name: 'persistentLogging' + default: data.persistentLogging + when: (answers) -> + answers.advancedSettings + } + ]).then (answers) -> + return _.merge(data, answers) + .then (answers) -> + reconfix.writeConfiguration(CONFIGURATION_SCHEMA, answers, params.target) + .asCallback(cb) + .then -> + console.log('Done!') + .asCallback(done) diff --git a/lib/actions/local/index.coffee b/lib/actions/local/index.coffee new file mode 100644 index 00000000..6c1a6a7a --- /dev/null +++ b/lib/actions/local/index.coffee @@ -0,0 +1,17 @@ +### +Copyright 2017 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.configure = require('./configure') diff --git a/package.json b/package.json index 915b4737..312d598d 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,8 @@ "chalk": "^1.1.1", "coffee-script": "~1.12.2", "columnify": "^1.5.2", + "denymount": "^2.2.0", + "inquirer": "^3.0.6", "is-root": "^1.0.0", "js-yaml": "^3.7.0", "lodash": "^3.10.0", @@ -44,6 +46,7 @@ "nplugm": "^3.0.0", "president": "^2.0.1", "prettyjson": "^1.1.3", + "reconfix": "0.0.3", "resin-cli-auth": "^1.0.0", "resin-cli-errors": "^1.2.0", "resin-cli-form": "^1.4.0", @@ -59,7 +62,7 @@ "rimraf": "^2.4.3", "rindle": "^1.0.0", "tmp": "^0.0.31", - "umount": "^1.1.1", + "umount": "^1.1.5", "underscore.string": "^3.1.1", "unzip2": "^0.2.5", "update-notifier": "^0.6.1", From 356042557e0cdc1d7434bdb50fd4bbb8f42785b0 Mon Sep 17 00:00:00 2001 From: Kostas Lekkas Date: Wed, 8 Mar 2017 21:49:56 +0000 Subject: [PATCH 02/10] Implement 'resin local flash' --- lib/actions/local/flash.coffee | 126 +++++++++++++++++++++++++++++++++ lib/actions/local/index.coffee | 1 + package.json | 10 ++- 3 files changed, 134 insertions(+), 3 deletions(-) create mode 100644 lib/actions/local/flash.coffee diff --git a/lib/actions/local/flash.coffee b/lib/actions/local/flash.coffee new file mode 100644 index 00000000..8670ff7a --- /dev/null +++ b/lib/actions/local/flash.coffee @@ -0,0 +1,126 @@ +### +Copyright 2017 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. +### + +module.exports = + signature: 'local flash ' + description: 'Flash an image to a drive' + help: ''' + Use this command to flash a resinOS image to a drive. + + Examples: + + $ resin local flash path/to/resinos.img + $ resin local flash path/to/resinos.img --drive /dev/disk2 + $ resin local flash path/to/resinos.img --drive /dev/disk2 --yes + ''' + options: [ + signature: 'yes' + boolean: true + description: 'confirm non-interactively' + alias: 'y' + , + signature: 'drive' + parameter: 'drive' + description: 'drive' + alias: 'd' + ] + root: true + action: (params, options, done) -> + + _ = require('lodash') + os = require('os') + Promise = require('bluebird') + umount = Promise.promisifyAll(require('umount')) + fs = Promise.promisifyAll(require('fs')) + drivelist = Promise.promisifyAll(require('drivelist')) + 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 + require('babel-register')({ + only: /etcher-image-write|bmapflash/ + presets: ['es2015'] + compact: true + }) + imageWrite = require('etcher-image-write') + + form.run [ + { + message: 'Select drive' + type: 'drive' + name: 'drive' + }, + { + message: 'This will erase the selected drive. Are you sure?' + type: 'confirm' + name: 'yes' + default: false + } + ], + override: + drive: options.drive + + # If `options.yes` is `false`, pass `undefined`, + # otherwise the question will not be asked because + # `false` is a defined value. + yes: options.yes || undefined + .then (answers) -> + if answers.yes isnt true + console.log(chalk.red.bold('Aborted image flash')) + process.exit(0) + + drivelist.listAsync().then (drives) -> + selectedDrive = _.find(drives, device: answers.drive) + + if not selectedDrive? + throw new Error("Drive not found: #{answers.drive}") + + return selectedDrive + .then (selectedDrive) -> + progressBars = + write: new visuals.Progress('Flashing') + check: new visuals.Progress('Validating') + + umount.umountAsync(selectedDrive.device).then -> + Promise.props + imageSize: fs.statAsync(params.image).get('size'), + imageStream: Promise.resolve(fs.createReadStream(params.image)) + driveFileDescriptor: fs.openAsync(selectedDrive.raw, 'rs+') + .then (results) -> + imageWrite.write + fd: results.driveFileDescriptor + device: selectedDrive.raw + size: selectedDrive.size + , + stream: results.imageStream, + size: results.imageSize + , + check: true + .then (writer) -> + new Promise (resolve, reject) -> + writer.on 'progress', (state) -> + progressBars[state.type].update(state) + writer.on('error', reject) + writer.on('done', resolve) + .then -> + if (os.platform() is 'win32') and selectedDrive.mountpoint? + removedrive = Promise.promisifyAll(require('removedrive')) + return removedrive.ejectAsync(selectedDrive.mountpoint) + + return umount.umountAsync(selectedDrive.device) + .asCallback(done) diff --git a/lib/actions/local/index.coffee b/lib/actions/local/index.coffee index 6c1a6a7a..f1e51d78 100644 --- a/lib/actions/local/index.coffee +++ b/lib/actions/local/index.coffee @@ -15,3 +15,4 @@ limitations under the License. ### exports.configure = require('./configure') +exports.flash = require('./flash') diff --git a/package.json b/package.json index 312d598d..91cc9d58 100644 --- a/package.json +++ b/package.json @@ -31,12 +31,16 @@ "gulp-shell": "^0.5.2" }, "dependencies": { + "babel-preset-es2015": "^6.16.0", + "babel-register": "^6.16.3", "bluebird": "^3.3.3", "capitano": "~1.7.0", - "chalk": "^1.1.1", + "chalk": "^1.1.3", "coffee-script": "~1.12.2", "columnify": "^1.5.2", "denymount": "^2.2.0", + "drivelist": "^5.0.16", + "etcher-image-write": "^9.0.1", "inquirer": "^3.0.6", "is-root": "^1.0.0", "js-yaml": "^3.7.0", @@ -49,8 +53,8 @@ "reconfix": "0.0.3", "resin-cli-auth": "^1.0.0", "resin-cli-errors": "^1.2.0", - "resin-cli-form": "^1.4.0", - "resin-cli-visuals": "^1.2.2", + "resin-cli-form": "^1.4.1", + "resin-cli-visuals": "^1.3.0", "resin-config-json": "^1.0.0", "resin-device-config": "^3.0.0", "resin-device-init": "^2.1.0", From 3b4c8f2a01b1eb490021a474f7c1f36bb3fbbe0d Mon Sep 17 00:00:00 2001 From: Kostas Lekkas Date: Wed, 8 Mar 2017 18:45:36 +0000 Subject: [PATCH 03/10] Implement 'resin local logs' --- lib/actions/local/common.coffee | 69 +++++++++++++++++++++++++++++++++ lib/actions/local/index.coffee | 1 + lib/actions/local/logs.coffee | 65 +++++++++++++++++++++++++++++++ package.json | 1 + 4 files changed, 136 insertions(+) create mode 100644 lib/actions/local/common.coffee create mode 100644 lib/actions/local/logs.coffee diff --git a/lib/actions/local/common.coffee b/lib/actions/local/common.coffee new file mode 100644 index 00000000..897e7a26 --- /dev/null +++ b/lib/actions/local/common.coffee @@ -0,0 +1,69 @@ +Promise = require('bluebird') +_ = require('lodash') +Docker = require('docker-toolbelt') +form = require('resin-cli-form') +chalk = require('chalk') + +module.exports = + + selectContainerFromDevice: Promise.method (deviceIp) -> + docker = new Docker(host: deviceIp, port: 2375) + + # List all containers, including those not running + docker.listContainersAsync(all: true) + .then (containers) -> + if _.isEmpty(containers) + throw new Error("No containers found in #{deviceIp}") + + return form.ask + message: 'Select a container' + type: 'list' + choices: _.map containers, (container) -> + containerName = container.Names[0] or 'Untitled' + shortContainerId = ('' + container.Id).substr(0, 11) + containerStatus = container.Status + + return { + name: "#{containerName} (#{shortContainerId}) - #{containerStatus}" + value: container.Id + } + + pipeContainerStream: Promise.method ({ deviceIp, name, outStream, follow = false }) -> + docker = new Docker(host: deviceIp, port: 2375) + + container = docker.getContainer(name) + container.inspectAsync() + .then (containerInfo) -> + return containerInfo?.State?.Running + .then (isRunning) -> + container.attachAsync + logs: not follow or not isRunning + stream: follow and isRunning + stdout: true + stderr: true + .then (containerStream) -> + containerStream.pipe(outStream) + .catch (err) -> + err = '' + err.statusCode + if err is '404' + return console.log(chalk.red.bold("Container '#{name}' not found.")) + throw err + + # A function to reliably execute a command + # in all supported operating systems, including + # different Windows environments like `cmd.exe` + # and `Cygwin` should be encapsulated in a + # re-usable package. + getSubShellCommand: (command) -> + os = require('os') + + if os.platform() is 'win32' + return { + program: 'cmd.exe' + args: [ '/s', '/c', command ] + } + else + return { + program: '/bin/sh' + args: [ '-c', command ] + } diff --git a/lib/actions/local/index.coffee b/lib/actions/local/index.coffee index f1e51d78..dc9c25e8 100644 --- a/lib/actions/local/index.coffee +++ b/lib/actions/local/index.coffee @@ -16,3 +16,4 @@ limitations under the License. exports.configure = require('./configure') exports.flash = require('./flash') +exports.logs = require('./logs') diff --git a/lib/actions/local/logs.coffee b/lib/actions/local/logs.coffee new file mode 100644 index 00000000..79cfc789 --- /dev/null +++ b/lib/actions/local/logs.coffee @@ -0,0 +1,65 @@ +### +Copyright 2017 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. +### + +# A function to reliably execute a command +# in all supported operating systems, including +# different Windows environments like `cmd.exe` +# and `Cygwin` should be encapsulated in a +# re-usable package. +# +module.exports = + signature: 'local logs [deviceIp]' + description: 'Get or attach to logs of a running container on a resinOS device' + help: ''' + + Examples: + + $ resin local logs + $ resin local logs -f + $ resin local logs 192.168.1.10 + $ resin local logs 192.168.1.10 -f + $ resin local logs 192.168.1.10 -f --app-name myapp + ''' + options: [ + signature: 'follow' + boolean: true + description: 'follow log' + alias: 'f' + , + signature: 'app-name' + parameter: 'name' + description: 'name of container to get logs from' + alias: 'a' + ] + action: (params, options, done) -> + Promise = require('bluebird') + { forms } = require('resin-sync') + { selectContainerFromDevice, pipeContainerStream } = require('./common') + + Promise.try -> + if not params.deviceIp? + return forms.selectLocalResinOsDevice() + return params.deviceIp + .then (@deviceIp) => + if not options['app-name']? + return selectContainerFromDevice(@deviceIp) + return options['app-name'] + .then (appName) => + pipeContainerStream + deviceIp: @deviceIp + name: appName + outStream: process.stdout + follow: options['follow'] diff --git a/package.json b/package.json index 91cc9d58..a30f981c 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "coffee-script": "~1.12.2", "columnify": "^1.5.2", "denymount": "^2.2.0", + "docker-toolbelt": "^1.3.3", "drivelist": "^5.0.16", "etcher-image-write": "^9.0.1", "inquirer": "^3.0.6", From f5cd3375f26f302cfdad95324a0966a824d24f4d Mon Sep 17 00:00:00 2001 From: Kostas Lekkas Date: Wed, 8 Mar 2017 18:46:28 +0000 Subject: [PATCH 04/10] Implement 'resin local promote' --- lib/actions/local/index.coffee | 1 + lib/actions/local/promote.coffee | 78 ++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+) create mode 100644 lib/actions/local/promote.coffee diff --git a/lib/actions/local/index.coffee b/lib/actions/local/index.coffee index dc9c25e8..992b0f8a 100644 --- a/lib/actions/local/index.coffee +++ b/lib/actions/local/index.coffee @@ -17,3 +17,4 @@ limitations under the License. exports.configure = require('./configure') exports.flash = require('./flash') exports.logs = require('./logs') +exports.promote = require('./promote') diff --git a/lib/actions/local/promote.coffee b/lib/actions/local/promote.coffee new file mode 100644 index 00000000..f182ca8f --- /dev/null +++ b/lib/actions/local/promote.coffee @@ -0,0 +1,78 @@ +### +Copyright 2017 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. +### + +module.exports = + signature: 'local promote [deviceIp]' + description: 'Promote a resinOS device' + help: ''' + Warning: 'resin promote' requires an openssh-compatible client to be correctly + installed in your shell environment. For more information (including Windows + support) please check the README here: https://github.com/resin-io/resin-cli + + Use this command to promote your device. + + Promoting a device will provision it onto the Resin platform, + converting it from an unmanaged device to a managed device. + + Examples: + + $ resin local promote + $ resin local promote --port 22222 + $ resin local promote --verbose + ''' + options: [ + signature: 'verbose' + boolean: true + description: 'increase verbosity' + alias: 'v' + , + signature: 'port' + parameter: 'port' + description: 'ssh port number (default: 22222)' + alias: 'p' + ] + action: (params, options, done) -> + child_process = require('child_process') + Promise = require 'bluebird' + _ = require('lodash') + + { forms } = require('resin-sync') + { getSubShellCommand } = require('./common') + + options.port ?= 22222 + + verbose = if options.verbose then '-vvv' else '' + + Promise.try -> + return params.deviceIp ?= forms.selectLocalResinOsDevice() + .then (deviceIp) -> + _.assign(options, { deviceIp }) + + command = "ssh \ + #{verbose} \ + -t \ + -p #{options.port} \ + -o LogLevel=ERROR \ + -o StrictHostKeyChecking=no \ + -o UserKnownHostsFile=/dev/null \ + -o ControlMaster=no \ + root@#{options.deviceIp} \ + -- \"resin-provision interactive\"" + + subShellCommand = getSubShellCommand(command) + child_process.spawn subShellCommand.program, subShellCommand.args, + stdio: 'inherit' + .nodeify(done) From c5df32f952c085c2a37e2f03eaa68787185f720e Mon Sep 17 00:00:00 2001 From: Kostas Lekkas Date: Wed, 8 Mar 2017 18:47:47 +0000 Subject: [PATCH 05/10] Implement 'resin local scan' --- lib/actions/local/index.coffee | 1 + lib/actions/local/scan.coffee | 92 ++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 lib/actions/local/scan.coffee diff --git a/lib/actions/local/index.coffee b/lib/actions/local/index.coffee index 992b0f8a..5708065f 100644 --- a/lib/actions/local/index.coffee +++ b/lib/actions/local/index.coffee @@ -18,3 +18,4 @@ exports.configure = require('./configure') exports.flash = require('./flash') exports.logs = require('./logs') exports.promote = require('./promote') +exports.scan = require('./scan') diff --git a/lib/actions/local/scan.coffee b/lib/actions/local/scan.coffee new file mode 100644 index 00000000..d119e988 --- /dev/null +++ b/lib/actions/local/scan.coffee @@ -0,0 +1,92 @@ +### +Copyright 2017 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. +### + + +dockerInfoProperties = [ + 'Containers' + 'ContainersRunning' + 'ContainersPaused' + 'ContainersStopped' + 'Images' + 'Driver' + 'SystemTime' + 'KernelVersion' + 'OperatingSystem' + 'Architecture' +] + +dockerVersionProperties = [ + 'Version' + 'ApiVersion' +] + +module.exports = + signature: 'local scan' + description: 'Scan for resinOS devices in your local network' + help: ''' + + Examples: + + $ resin local scan + $ resin local scan --timeout 120 + $ resin local scan --verbose + ''' + options: [ + signature: 'verbose' + boolean: true + description: 'Display full info' + alias: 'v' + , + signature: 'timeout' + parameter: 'timeout' + description: 'Scan timeout in seconds' + alias: 't' + ] + primary: true + action: (params, options, done) -> + Promise = require('bluebird') + _ = require('lodash') + prettyjson = require('prettyjson') + Docker = require('docker-toolbelt') + { discover } = require('resin-sync') + { SpinnerPromise } = require('resin-cli-visuals') + + if options.timeout? + options.timeout *= 1000 + + Promise.try -> + new SpinnerPromise + promise: discover.discoverLocalResinOsDevices(options.timeout) + startMessage: 'Scanning for local resinOS devices..' + stopMessage: 'Reporting scan results' + .tap (devices) -> + if _.isEmpty(devices) + throw new Error('Could not find any resinOS devices in the local network') + .map ({ host, address }) -> + docker = new Docker(host: address, port: 2375) + Promise.props + dockerInfo: docker.infoAsync().catchReturn('Could not get Docker info') + dockerVersion: docker.versionAsync().catchReturn('Could not get Docker version') + .then ({ dockerInfo, dockerVersion }) -> + + if not options.verbose + dockerInfo = _.pick(dockerInfo, dockerInfoProperties) if _.isObject(dockerInfo) + dockerVersion = _.pick(dockerVersion, dockerVersionProperties) if _.isObject(dockerVersion) + + return { host, address, dockerInfo, dockerVersion } + .then (devicesInfo) -> + console.log(prettyjson.render(devicesInfo, noColor: true)) + .nodeify(done) From 977e3fb0ff49edfa52d605b4c6a509559be8d398 Mon Sep 17 00:00:00 2001 From: Kostas Lekkas Date: Wed, 8 Mar 2017 18:49:20 +0000 Subject: [PATCH 06/10] Implement 'resin local ssh' --- lib/actions/local/index.coffee | 1 + lib/actions/local/ssh.coffee | 109 +++++++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+) create mode 100644 lib/actions/local/ssh.coffee diff --git a/lib/actions/local/index.coffee b/lib/actions/local/index.coffee index 5708065f..2a34e607 100644 --- a/lib/actions/local/index.coffee +++ b/lib/actions/local/index.coffee @@ -19,3 +19,4 @@ exports.flash = require('./flash') exports.logs = require('./logs') exports.promote = require('./promote') exports.scan = require('./scan') +exports.ssh = require('./ssh') diff --git a/lib/actions/local/ssh.coffee b/lib/actions/local/ssh.coffee new file mode 100644 index 00000000..746d52de --- /dev/null +++ b/lib/actions/local/ssh.coffee @@ -0,0 +1,109 @@ +### +Copyright 2017 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. +### + +module.exports = + signature: 'local ssh [deviceIp]' + description: 'Get a shell into a resinOS device' + help: ''' + Warning: 'resin local ssh' requires an openssh-compatible client to be correctly + installed in your shell environment. For more information (including Windows + support) please check the README here: https://github.com/resin-io/resin-cli + + Use this command to get a shell into the running application container of + your device. + + The '--host' option will get you a shell into the Host OS of the resinOS device. + No option will return a list of containers to enter or you can explicitly select + one by passing its name to the --container option + + Examples: + + $ resin local ssh + $ resin local ssh --host + $ resin local ssh --container chaotic_water + $ resin local ssh --container chaotic_water --port 22222 + $ resin local ssh --verbose + ''' + options: [ + signature: 'verbose' + boolean: true + description: 'increase verbosity' + alias: 'v' + , + signature: 'host' + boolean: true + description: 'get a shell into the host OS' + alias: 's' + , + signature: 'container' + parameter: 'container' + default: null + description: 'name of container to access' + alias: 'c' + , + signature: 'port' + parameter: 'port' + description: 'ssh port number (default: 22222)' + alias: 'p' + ] + action: (params, options, done) -> + child_process = require('child_process') + Promise = require 'bluebird' + _ = require('lodash') + { forms } = require('resin-sync') + { selectContainerFromDevice, getSubShellCommand } = require('./common') + + if (options.host is true and options.container?) + throw new Error('Please pass either --host or --container option') + + if not options.port? + options.port = 22222 + + verbose = if options.verbose then '-vvv' else '' + + Promise.try -> + if not params.deviceIp? + return forms.selectLocalResinOsDevice() + return params.deviceIp + .then (deviceIp) -> + _.assign(options, { deviceIp }) + + return if options.host + + if not options.container? + return selectContainerFromDevice(deviceIp) + + return options.container + .then (container) -> + + command = "ssh \ + #{verbose} \ + -t \ + -p #{options.port} \ + -o LogLevel=ERROR \ + -o StrictHostKeyChecking=no \ + -o UserKnownHostsFile=/dev/null \ + -o ControlMaster=no \ + root@#{options.deviceIp}" + + if not options.host + shellCmd = '''/bin/sh -c $"'if [ -e /bin/bash ]; then exec /bin/bash; else exec /bin/sh; fi'"''' + command += " docker exec -ti #{container} #{shellCmd}" + + subShellCommand = getSubShellCommand(command) + child_process.spawn subShellCommand.program, subShellCommand.args, + stdio: 'inherit' + .nodeify(done) From 20ed8c9169dea66032c6c9545681ae84d5392646 Mon Sep 17 00:00:00 2001 From: Kostas Lekkas Date: Wed, 8 Mar 2017 19:08:52 +0000 Subject: [PATCH 07/10] Implement 'resin local push' --- lib/actions/local/index.coffee | 1 + lib/actions/local/push.coffee | 69 ++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 lib/actions/local/push.coffee diff --git a/lib/actions/local/index.coffee b/lib/actions/local/index.coffee index 2a34e607..628dbb1e 100644 --- a/lib/actions/local/index.coffee +++ b/lib/actions/local/index.coffee @@ -20,3 +20,4 @@ exports.logs = require('./logs') exports.promote = require('./promote') exports.scan = require('./scan') exports.ssh = require('./ssh') +exports.push = require('./push') diff --git a/lib/actions/local/push.coffee b/lib/actions/local/push.coffee new file mode 100644 index 00000000..29724b6c --- /dev/null +++ b/lib/actions/local/push.coffee @@ -0,0 +1,69 @@ +### +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. +### + +# Loads '.resin-sync.yml' configuration from 'source' directory. +# Returns the configuration object on success +# + +resinPush = require('resin-sync').capitano('resin-toolbox') + +# TODO: This is a temporary workaround to reuse the existing `rdt push` +# capitano frontend in `resin local push`. +resinPush.signature = 'local push [deviceIp]' +resinPush.help = + help: ''' + Warning: 'resin local push' requires an openssh-compatible client and 'rsync' to + be correctly installed in your shell environment. For more information (including + Windows support) please check the README here: https://github.com/resin-io/resin-cli + + Use this command to push your local changes to a container on a LAN-accessible resinOS device on the fly. + + If `Dockerfile` or any file in the 'build-triggers' list is changed, a new container will be built and run on your device. + If not, changes will simply be synced with `rsync` into the application container. + + After every 'resin local push' the updated settings will be saved in + '/.resin-sync.yml' and will be used in later invocations. You can + also change any option by editing '.resin-sync.yml' directly. + + Here is an example '.resin-sync.yml' : + + $ cat $PWD/.resin-sync.yml + destination: '/usr/src/app' + before: 'echo Hello' + after: 'echo Done' + ignore: + - .git + - node_modules/ + + 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 when using rsync to update the container. You can choose to change this default behavior with the + '--skip-gitignore' option. + + Examples: + + $ resin local push + $ resin local push --app-name test-server --build-triggers package.json,requirements.txt + $ resin local push --force-build + $ resin local push --force-build --skip-logs + $ resin local push --ignore lib/ + $ resin local push --verbose false + $ resin local push 192.168.2.10 --source . --destination /usr/src/app + $ resin local push 192.168.2.10 -s /home/user/myResinProject -d /usr/src/app --before 'echo Hello' --after 'echo Done' + ''' +resinPush.primary = true +module.exports = resinPush From 1ae1a152591f1a7533fca23fd4269754fd21f32d Mon Sep 17 00:00:00 2001 From: Kostas Lekkas Date: Wed, 8 Mar 2017 19:26:08 +0000 Subject: [PATCH 08/10] Implement 'resin local' --- lib/actions/index.coffee | 1 + lib/app.coffee | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/lib/actions/index.coffee b/lib/actions/index.coffee index 0ec98c17..31c391d1 100644 --- a/lib/actions/index.coffee +++ b/lib/actions/index.coffee @@ -23,6 +23,7 @@ module.exports = env: require('./environment-variables') keys: require('./keys') logs: require('./logs') + local: require('./local') notes: require('./notes') help: require('./help') os: require('./os') diff --git a/lib/app.coffee b/lib/app.coffee index d1ff2e52..ac98d490 100644 --- a/lib/app.coffee +++ b/lib/app.coffee @@ -124,6 +124,15 @@ capitano.command(actions.sync) # ---------- SSH Module ---------- capitano.command(actions.ssh) +# ---------- Local ResinOS Module ---------- +capitano.command(actions.local.configure) +capitano.command(actions.local.flash) +capitano.command(actions.local.logs) +capitano.command(actions.local.promote) +capitano.command(actions.local.push) +capitano.command(actions.local.ssh) +capitano.command(actions.local.scan) + update.notify() plugins.register(/^resin-plugin-(.+)$/).then -> From 53bf3148205dd126919d18cace9cf195a1357b82 Mon Sep 17 00:00:00 2001 From: Kostas Lekkas Date: Wed, 8 Mar 2017 21:11:33 +0000 Subject: [PATCH 09/10] Remove app create from primary commands --- lib/actions/app.coffee | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/actions/app.coffee b/lib/actions/app.coffee index dd10071e..aae7f713 100644 --- a/lib/actions/app.coffee +++ b/lib/actions/app.coffee @@ -43,7 +43,6 @@ exports.create = } ] permission: 'user' - primary: true action: (params, options, done) -> resin = require('resin-sdk-preconfigured') patterns = require('../utils/patterns') From 2d09c18d6b1949764a23a654398ca3539424bbd8 Mon Sep 17 00:00:00 2001 From: Kostas Lekkas Date: Wed, 8 Mar 2017 23:30:24 +0000 Subject: [PATCH 10/10] Build JS --- build/actions/app.js | 3 +- build/actions/auth.js | 2 +- build/actions/command-options.js | 2 +- build/actions/config.js | 2 +- build/actions/device.js | 2 +- build/actions/environment-variables.js | 2 +- build/actions/help.js | 2 +- build/actions/index.js | 3 +- build/actions/info.js | 2 +- build/actions/keys.js | 2 +- build/actions/local/common.js | 89 +++++++++++++++++ build/actions/local/configure.js | 133 +++++++++++++++++++++++++ build/actions/local/flash.js | 129 ++++++++++++++++++++++++ build/actions/local/index.js | 34 +++++++ build/actions/local/logs.js | 68 +++++++++++++ build/actions/local/promote.js | 64 ++++++++++++ build/actions/local/push.js | 34 +++++++ build/actions/local/scan.js | 101 +++++++++++++++++++ build/actions/local/ssh.js | 93 +++++++++++++++++ build/actions/logs.js | 2 +- build/actions/notes.js | 2 +- build/actions/os.js | 2 +- build/actions/settings.js | 2 +- build/actions/sync.js | 2 +- build/actions/wizard.js | 2 +- build/app.js | 16 ++- build/errors.js | 2 +- build/events.js | 2 +- build/utils/helpers.js | 2 +- build/utils/messages.js | 2 +- build/utils/patterns.js | 2 +- build/utils/plugins.js | 2 +- build/utils/update.js | 2 +- build/utils/validation.js | 2 +- 34 files changed, 785 insertions(+), 26 deletions(-) create mode 100644 build/actions/local/common.js create mode 100644 build/actions/local/configure.js create mode 100644 build/actions/local/flash.js create mode 100644 build/actions/local/index.js create mode 100644 build/actions/local/logs.js create mode 100644 build/actions/local/promote.js create mode 100644 build/actions/local/push.js create mode 100644 build/actions/local/scan.js create mode 100644 build/actions/local/ssh.js diff --git a/build/actions/app.js b/build/actions/app.js index 9f53017d..143091bc 100644 --- a/build/actions/app.js +++ b/build/actions/app.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.12.2 +// Generated by CoffeeScript 1.12.4 /* Copyright 2016 Resin.io @@ -34,7 +34,6 @@ limitations under the License. } ], permission: 'user', - primary: true, action: function(params, options, done) { var patterns, resin; resin = require('resin-sdk-preconfigured'); diff --git a/build/actions/auth.js b/build/actions/auth.js index 6b16c86f..051d1205 100644 --- a/build/actions/auth.js +++ b/build/actions/auth.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.12.2 +// Generated by CoffeeScript 1.12.4 /* Copyright 2016 Resin.io diff --git a/build/actions/command-options.js b/build/actions/command-options.js index e26ac0c9..4c175364 100644 --- a/build/actions/command-options.js +++ b/build/actions/command-options.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.12.2 +// Generated by CoffeeScript 1.12.4 /* Copyright 2016 Resin.io diff --git a/build/actions/config.js b/build/actions/config.js index b3a47141..4bd4e329 100644 --- a/build/actions/config.js +++ b/build/actions/config.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.12.2 +// Generated by CoffeeScript 1.12.4 /* Copyright 2016 Resin.io diff --git a/build/actions/device.js b/build/actions/device.js index 5d502189..39eea974 100644 --- a/build/actions/device.js +++ b/build/actions/device.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.12.2 +// Generated by CoffeeScript 1.12.4 /* Copyright 2016 Resin.io diff --git a/build/actions/environment-variables.js b/build/actions/environment-variables.js index e3e304ac..e9f807ca 100644 --- a/build/actions/environment-variables.js +++ b/build/actions/environment-variables.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.12.2 +// Generated by CoffeeScript 1.12.4 /* Copyright 2016 Resin.io diff --git a/build/actions/help.js b/build/actions/help.js index 5a8c8833..5e9745a0 100644 --- a/build/actions/help.js +++ b/build/actions/help.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.12.2 +// Generated by CoffeeScript 1.12.4 /* Copyright 2016 Resin.io diff --git a/build/actions/index.js b/build/actions/index.js index e4570848..bdf1d20f 100644 --- a/build/actions/index.js +++ b/build/actions/index.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.12.2 +// Generated by CoffeeScript 1.12.4 /* Copyright 2016 Resin.io @@ -26,6 +26,7 @@ limitations under the License. env: require('./environment-variables'), keys: require('./keys'), logs: require('./logs'), + local: require('./local'), notes: require('./notes'), help: require('./help'), os: require('./os'), diff --git a/build/actions/info.js b/build/actions/info.js index ab0256ca..49b8eafc 100644 --- a/build/actions/info.js +++ b/build/actions/info.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.12.2 +// Generated by CoffeeScript 1.12.4 /* Copyright 2016 Resin.io diff --git a/build/actions/keys.js b/build/actions/keys.js index 591fbc15..aa11737a 100644 --- a/build/actions/keys.js +++ b/build/actions/keys.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.12.2 +// Generated by CoffeeScript 1.12.4 /* Copyright 2016 Resin.io diff --git a/build/actions/local/common.js b/build/actions/local/common.js new file mode 100644 index 00000000..a6801592 --- /dev/null +++ b/build/actions/local/common.js @@ -0,0 +1,89 @@ +// Generated by CoffeeScript 1.12.4 +(function() { + var Docker, Promise, _, chalk, form; + + Promise = require('bluebird'); + + _ = require('lodash'); + + Docker = require('docker-toolbelt'); + + form = require('resin-cli-form'); + + chalk = require('chalk'); + + module.exports = { + selectContainerFromDevice: Promise.method(function(deviceIp) { + var docker; + docker = new Docker({ + host: deviceIp, + port: 2375 + }); + return docker.listContainersAsync({ + all: true + }).then(function(containers) { + if (_.isEmpty(containers)) { + throw new Error("No containers found in " + deviceIp); + } + return form.ask({ + message: 'Select a container', + type: 'list', + choices: _.map(containers, function(container) { + var containerName, containerStatus, shortContainerId; + containerName = container.Names[0] || 'Untitled'; + shortContainerId = ('' + container.Id).substr(0, 11); + containerStatus = container.Status; + return { + name: containerName + " (" + shortContainerId + ") - " + containerStatus, + value: container.Id + }; + }) + }); + }); + }), + pipeContainerStream: Promise.method(function(arg) { + var container, deviceIp, docker, follow, name, outStream, ref; + deviceIp = arg.deviceIp, name = arg.name, outStream = arg.outStream, follow = (ref = arg.follow) != null ? ref : false; + docker = new Docker({ + host: deviceIp, + port: 2375 + }); + container = docker.getContainer(name); + return container.inspectAsync().then(function(containerInfo) { + var ref1; + return containerInfo != null ? (ref1 = containerInfo.State) != null ? ref1.Running : void 0 : void 0; + }).then(function(isRunning) { + return container.attachAsync({ + logs: !follow || !isRunning, + stream: follow && isRunning, + stdout: true, + stderr: true + }); + }).then(function(containerStream) { + return containerStream.pipe(outStream); + })["catch"](function(err) { + err = '' + err.statusCode; + if (err === '404') { + return console.log(chalk.red.bold("Container '" + name + "' not found.")); + } + throw err; + }); + }), + getSubShellCommand: function(command) { + var os; + os = require('os'); + if (os.platform() === 'win32') { + return { + program: 'cmd.exe', + args: ['/s', '/c', command] + }; + } else { + return { + program: '/bin/sh', + args: ['-c', command] + }; + } + } + }; + +}).call(this); diff --git a/build/actions/local/configure.js b/build/actions/local/configure.js new file mode 100644 index 00000000..af928706 --- /dev/null +++ b/build/actions/local/configure.js @@ -0,0 +1,133 @@ +// Generated by CoffeeScript 1.12.4 + +/* +Copyright 2017 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. + */ + +(function() { + var CONFIGURATION_SCHEMA; + + CONFIGURATION_SCHEMA = { + mapper: [ + { + template: { + hostname: '{{hostname}}', + persistentLogging: '{{persistentLogging}}' + }, + domain: [['config_json', 'hostname'], ['config_json', 'persistentLogging']] + }, { + template: { + wifi: { + ssid: '{{networkSsid}}' + }, + 'wifi-security': { + psk: '{{networkKey}}' + } + }, + domain: [['system_connections', 'resin-sample', 'wifi'], ['system_connections', 'resin-sample', 'wifi-security']] + } + ], + files: { + system_connections: { + fileset: true, + type: 'ini', + location: { + path: 'system-connections', + partition: { + primary: 1 + } + } + }, + config_json: { + type: 'json', + location: { + path: 'config.json', + partition: { + primary: 1 + } + } + } + } + }; + + module.exports = { + signature: 'local configure ', + description: '(Re)configure a resinOS drive or image', + 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; + _ = require('lodash'); + Promise = require('bluebird'); + umount = Promise.promisifyAll(require('umount')); + inquirer = require('inquirer'); + reconfix = require('reconfix'); + denymount = Promise.promisify(require('denymount')); + return umount.isMountedAsync(params.target).then(function(isMounted) { + if (!isMounted) { + return; + } + return umount.umountAsync(params.target); + }).then(function() { + return denymount(params.target, function(cb) { + return reconfix.readConfiguration(CONFIGURATION_SCHEMA, params.target).then(function(data) { + data.persistentLogging = data.persistentLogging || false; + return inquirer.prompt([ + { + message: 'Network SSID', + type: 'input', + name: 'networkSsid', + "default": data.networkSsid + }, { + message: 'Network Key', + type: 'input', + name: 'networkKey', + "default": data.networkKey + }, { + message: 'Do you want to set advanced settings?', + type: 'confirm', + name: 'advancedSettings', + "default": false + }, { + message: 'Device Hostname', + type: 'input', + name: 'hostname', + "default": data.hostname, + when: function(answers) { + return answers.advancedSettings; + } + }, { + message: 'Do you want to enable persistent logging?', + type: 'confirm', + name: 'persistentLogging', + "default": data.persistentLogging, + when: function(answers) { + return answers.advancedSettings; + } + } + ]).then(function(answers) { + return _.merge(data, answers); + }); + }).then(function(answers) { + return reconfix.writeConfiguration(CONFIGURATION_SCHEMA, answers, params.target); + }).asCallback(cb); + }); + }).then(function() { + return console.log('Done!'); + }).asCallback(done); + } + }; + +}).call(this); diff --git a/build/actions/local/flash.js b/build/actions/local/flash.js new file mode 100644 index 00000000..e05ea86a --- /dev/null +++ b/build/actions/local/flash.js @@ -0,0 +1,129 @@ +// Generated by CoffeeScript 1.12.4 + +/* +Copyright 2017 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. + */ + +(function() { + module.exports = { + signature: 'local flash ', + description: 'Flash an image to a drive', + help: 'Use this command to flash a resinOS image to a drive.\n\nExamples:\n\n $ resin local flash path/to/resinos.img\n $ resin local flash path/to/resinos.img --drive /dev/disk2\n $ resin local flash path/to/resinos.img --drive /dev/disk2 --yes', + options: [ + { + signature: 'yes', + boolean: true, + description: 'confirm non-interactively', + alias: 'y' + }, { + signature: 'drive', + parameter: 'drive', + description: 'drive', + alias: 'd' + } + ], + root: true, + action: function(params, options, done) { + var Promise, _, chalk, drivelist, form, fs, imageWrite, os, umount, visuals; + _ = require('lodash'); + os = require('os'); + Promise = require('bluebird'); + umount = Promise.promisifyAll(require('umount')); + fs = Promise.promisifyAll(require('fs')); + drivelist = Promise.promisifyAll(require('drivelist')); + chalk = require('chalk'); + visuals = require('resin-cli-visuals'); + form = require('resin-cli-form'); + require('babel-register')({ + only: /etcher-image-write|bmapflash/, + presets: ['es2015'], + compact: true + }); + imageWrite = require('etcher-image-write'); + return form.run([ + { + message: 'Select drive', + type: 'drive', + name: 'drive' + }, { + message: 'This will erase the selected drive. Are you sure?', + type: 'confirm', + name: 'yes', + "default": false + } + ], { + override: { + drive: options.drive, + yes: options.yes || void 0 + } + }).then(function(answers) { + if (answers.yes !== true) { + console.log(chalk.red.bold('Aborted image flash')); + process.exit(0); + } + return drivelist.listAsync().then(function(drives) { + var selectedDrive; + selectedDrive = _.find(drives, { + device: answers.drive + }); + if (selectedDrive == null) { + throw new Error("Drive not found: " + answers.drive); + } + return selectedDrive; + }); + }).then(function(selectedDrive) { + var progressBars; + progressBars = { + write: new visuals.Progress('Flashing'), + check: new visuals.Progress('Validating') + }; + return umount.umountAsync(selectedDrive.device).then(function() { + return Promise.props({ + imageSize: fs.statAsync(params.image).get('size'), + imageStream: Promise.resolve(fs.createReadStream(params.image)), + driveFileDescriptor: fs.openAsync(selectedDrive.raw, 'rs+') + }); + }).then(function(results) { + return imageWrite.write({ + fd: results.driveFileDescriptor, + device: selectedDrive.raw, + size: selectedDrive.size + }, { + stream: results.imageStream, + size: results.imageSize + }, { + check: true + }); + }).then(function(writer) { + return new Promise(function(resolve, reject) { + writer.on('progress', function(state) { + return progressBars[state.type].update(state); + }); + writer.on('error', reject); + return writer.on('done', resolve); + }); + }).then(function() { + var removedrive; + if ((os.platform() === 'win32') && (selectedDrive.mountpoint != null)) { + removedrive = Promise.promisifyAll(require('removedrive')); + return removedrive.ejectAsync(selectedDrive.mountpoint); + } + return umount.umountAsync(selectedDrive.device); + }); + }).asCallback(done); + } + }; + +}).call(this); diff --git a/build/actions/local/index.js b/build/actions/local/index.js new file mode 100644 index 00000000..e40643cd --- /dev/null +++ b/build/actions/local/index.js @@ -0,0 +1,34 @@ +// Generated by CoffeeScript 1.12.4 + +/* +Copyright 2017 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. + */ + +(function() { + exports.configure = require('./configure'); + + exports.flash = require('./flash'); + + exports.logs = require('./logs'); + + exports.promote = require('./promote'); + + exports.scan = require('./scan'); + + exports.ssh = require('./ssh'); + + exports.push = require('./push'); + +}).call(this); diff --git a/build/actions/local/logs.js b/build/actions/local/logs.js new file mode 100644 index 00000000..2f0c688c --- /dev/null +++ b/build/actions/local/logs.js @@ -0,0 +1,68 @@ +// Generated by CoffeeScript 1.12.4 + +/* +Copyright 2017 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. + */ + +(function() { + module.exports = { + signature: 'local logs [deviceIp]', + description: 'Get or attach to logs of a running container on a resinOS device', + help: '\nExamples:\n\n $ resin local logs\n $ resin local logs -f\n $ resin local logs 192.168.1.10\n $ resin local logs 192.168.1.10 -f\n $ resin local logs 192.168.1.10 -f --app-name myapp', + options: [ + { + signature: 'follow', + boolean: true, + description: 'follow log', + alias: 'f' + }, { + signature: 'app-name', + parameter: 'name', + description: 'name of container to get logs from', + alias: 'a' + } + ], + action: function(params, options, done) { + var Promise, forms, pipeContainerStream, ref, selectContainerFromDevice; + Promise = require('bluebird'); + forms = require('resin-sync').forms; + ref = require('./common'), selectContainerFromDevice = ref.selectContainerFromDevice, pipeContainerStream = ref.pipeContainerStream; + return Promise["try"](function() { + if (params.deviceIp == null) { + return forms.selectLocalResinOsDevice(); + } + return params.deviceIp; + }).then((function(_this) { + return function(deviceIp) { + _this.deviceIp = deviceIp; + if (options['app-name'] == null) { + return selectContainerFromDevice(_this.deviceIp); + } + return options['app-name']; + }; + })(this)).then((function(_this) { + return function(appName) { + return pipeContainerStream({ + deviceIp: _this.deviceIp, + name: appName, + outStream: process.stdout, + follow: options['follow'] + }); + }; + })(this)); + } + }; + +}).call(this); diff --git a/build/actions/local/promote.js b/build/actions/local/promote.js new file mode 100644 index 00000000..3c79c7a0 --- /dev/null +++ b/build/actions/local/promote.js @@ -0,0 +1,64 @@ +// Generated by CoffeeScript 1.12.4 + +/* +Copyright 2017 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. + */ + +(function() { + module.exports = { + signature: 'local promote [deviceIp]', + description: 'Promote a resinOS device', + help: 'Warning: \'resin promote\' requires an openssh-compatible client to be correctly\ninstalled in your shell environment. For more information (including Windows\nsupport) please check the README here: https://github.com/resin-io/resin-cli\n\nUse this command to promote your device.\n\nPromoting a device will provision it onto the Resin platform,\nconverting it from an unmanaged device to a managed device.\n\nExamples:\n\n $ resin local promote\n $ resin local promote --port 22222\n $ resin local promote --verbose', + options: [ + { + signature: 'verbose', + boolean: true, + description: 'increase verbosity', + alias: 'v' + }, { + signature: 'port', + parameter: 'port', + description: 'ssh port number (default: 22222)', + alias: 'p' + } + ], + action: function(params, options, done) { + var Promise, _, child_process, forms, getSubShellCommand, verbose; + child_process = require('child_process'); + Promise = require('bluebird'); + _ = require('lodash'); + forms = require('resin-sync').forms; + getSubShellCommand = require('./common').getSubShellCommand; + if (options.port == null) { + options.port = 22222; + } + verbose = options.verbose ? '-vvv' : ''; + return Promise["try"](function() { + return params.deviceIp != null ? params.deviceIp : params.deviceIp = forms.selectLocalResinOsDevice(); + }).then(function(deviceIp) { + var command, subShellCommand; + _.assign(options, { + deviceIp: deviceIp + }); + command = "ssh " + verbose + " -t -p " + options.port + " -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ControlMaster=no root@" + options.deviceIp + " -- \"resin-provision interactive\""; + subShellCommand = getSubShellCommand(command); + return child_process.spawn(subShellCommand.program, subShellCommand.args, { + stdio: 'inherit' + }); + }).nodeify(done); + } + }; + +}).call(this); diff --git a/build/actions/local/push.js b/build/actions/local/push.js new file mode 100644 index 00000000..44f0c869 --- /dev/null +++ b/build/actions/local/push.js @@ -0,0 +1,34 @@ +// 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. + */ + +(function() { + var resinPush; + + resinPush = require('resin-sync').capitano('resin-toolbox'); + + resinPush.signature = 'local push [deviceIp]'; + + resinPush.help = { + help: 'Warning: \'resin local push\' requires an openssh-compatible client and \'rsync\' to\nbe correctly installed in your shell environment. For more information (including\nWindows support) please check the README here: https://github.com/resin-io/resin-cli\n\nUse this command to push your local changes to a container on a LAN-accessible resinOS device on the fly.\n\nIf `Dockerfile` or any file in the \'build-triggers\' list is changed, a new container will be built and run on your device.\nIf not, changes will simply be synced with `rsync` into the application container.\n\nAfter every \'resin local push\' the updated settings will be saved in\n\'/.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 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 when using rsync to update the container. You can choose to change this default behavior with the\n\'--skip-gitignore\' option.\n\nExamples:\n\n $ resin local push\n $ resin local push --app-name test-server --build-triggers package.json,requirements.txt\n $ resin local push --force-build\n $ resin local push --force-build --skip-logs\n $ resin local push --ignore lib/\n $ resin local push --verbose false\n $ resin local push 192.168.2.10 --source . --destination /usr/src/app\n $ resin local push 192.168.2.10 -s /home/user/myResinProject -d /usr/src/app --before \'echo Hello\' --after \'echo Done\'' + }; + + resinPush.primary = true; + + module.exports = resinPush; + +}).call(this); diff --git a/build/actions/local/scan.js b/build/actions/local/scan.js new file mode 100644 index 00000000..141057b4 --- /dev/null +++ b/build/actions/local/scan.js @@ -0,0 +1,101 @@ +// Generated by CoffeeScript 1.12.4 + +/* +Copyright 2017 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. + */ + +(function() { + var dockerInfoProperties, dockerVersionProperties; + + dockerInfoProperties = ['Containers', 'ContainersRunning', 'ContainersPaused', 'ContainersStopped', 'Images', 'Driver', 'SystemTime', 'KernelVersion', 'OperatingSystem', 'Architecture']; + + dockerVersionProperties = ['Version', 'ApiVersion']; + + module.exports = { + signature: 'local scan', + description: 'Scan for resinOS devices in your local network', + help: '\nExamples:\n\n $ resin local scan\n $ resin local scan --timeout 120\n $ resin local scan --verbose', + options: [ + { + signature: 'verbose', + boolean: true, + description: 'Display full info', + alias: 'v' + }, { + signature: 'timeout', + parameter: 'timeout', + description: 'Scan timeout in seconds', + alias: 't' + } + ], + primary: true, + action: function(params, options, done) { + var Docker, Promise, SpinnerPromise, _, discover, prettyjson; + Promise = require('bluebird'); + _ = require('lodash'); + prettyjson = require('prettyjson'); + Docker = require('docker-toolbelt'); + discover = require('resin-sync').discover; + SpinnerPromise = require('resin-cli-visuals').SpinnerPromise; + if (options.timeout != null) { + options.timeout *= 1000; + } + return Promise["try"](function() { + return new SpinnerPromise({ + promise: discover.discoverLocalResinOsDevices(options.timeout), + startMessage: 'Scanning for local resinOS devices..', + stopMessage: 'Reporting scan results' + }); + }).tap(function(devices) { + if (_.isEmpty(devices)) { + throw new Error('Could not find any resinOS devices in the local network'); + } + }).map(function(arg) { + var address, docker, host; + host = arg.host, address = arg.address; + docker = new Docker({ + host: address, + port: 2375 + }); + return Promise.props({ + dockerInfo: docker.infoAsync().catchReturn('Could not get Docker info'), + dockerVersion: docker.versionAsync().catchReturn('Could not get Docker version') + }).then(function(arg1) { + var dockerInfo, dockerVersion; + dockerInfo = arg1.dockerInfo, dockerVersion = arg1.dockerVersion; + if (!options.verbose) { + if (_.isObject(dockerInfo)) { + dockerInfo = _.pick(dockerInfo, dockerInfoProperties); + } + if (_.isObject(dockerVersion)) { + dockerVersion = _.pick(dockerVersion, dockerVersionProperties); + } + } + return { + host: host, + address: address, + dockerInfo: dockerInfo, + dockerVersion: dockerVersion + }; + }); + }).then(function(devicesInfo) { + return console.log(prettyjson.render(devicesInfo, { + noColor: true + })); + }).nodeify(done); + } + }; + +}).call(this); diff --git a/build/actions/local/ssh.js b/build/actions/local/ssh.js new file mode 100644 index 00000000..d34d231a --- /dev/null +++ b/build/actions/local/ssh.js @@ -0,0 +1,93 @@ +// Generated by CoffeeScript 1.12.4 + +/* +Copyright 2017 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. + */ + +(function() { + module.exports = { + signature: 'local ssh [deviceIp]', + description: 'Get a shell into a resinOS device', + help: 'Warning: \'resin local ssh\' requires an openssh-compatible client to be correctly\ninstalled in your shell environment. For more information (including Windows\nsupport) please check the README here: https://github.com/resin-io/resin-cli\n\nUse this command to get a shell into the running application container of\nyour device.\n\nThe \'--host\' option will get you a shell into the Host OS of the resinOS device.\nNo option will return a list of containers to enter or you can explicitly select\none by passing its name to the --container option\n\nExamples:\n\n $ resin local ssh\n $ resin local ssh --host\n $ resin local ssh --container chaotic_water\n $ resin local ssh --container chaotic_water --port 22222\n $ resin local ssh --verbose', + options: [ + { + signature: 'verbose', + boolean: true, + description: 'increase verbosity', + alias: 'v' + }, { + signature: 'host', + boolean: true, + description: 'get a shell into the host OS', + alias: 's' + }, { + signature: 'container', + parameter: 'container', + "default": null, + description: 'name of container to access', + alias: 'c' + }, { + signature: 'port', + parameter: 'port', + description: 'ssh port number (default: 22222)', + alias: 'p' + } + ], + action: function(params, options, done) { + var Promise, _, child_process, forms, getSubShellCommand, ref, selectContainerFromDevice, verbose; + child_process = require('child_process'); + Promise = require('bluebird'); + _ = require('lodash'); + forms = require('resin-sync').forms; + ref = require('./common'), selectContainerFromDevice = ref.selectContainerFromDevice, getSubShellCommand = ref.getSubShellCommand; + if (options.host === true && (options.container != null)) { + throw new Error('Please pass either --host or --container option'); + } + if (options.port == null) { + options.port = 22222; + } + verbose = options.verbose ? '-vvv' : ''; + return Promise["try"](function() { + if (params.deviceIp == null) { + return forms.selectLocalResinOsDevice(); + } + return params.deviceIp; + }).then(function(deviceIp) { + _.assign(options, { + deviceIp: deviceIp + }); + if (options.host) { + return; + } + if (options.container == null) { + return selectContainerFromDevice(deviceIp); + } + return options.container; + }).then(function(container) { + var command, shellCmd, subShellCommand; + command = "ssh " + verbose + " -t -p " + options.port + " -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ControlMaster=no root@" + options.deviceIp; + if (!options.host) { + shellCmd = '/bin/sh -c $"\'if [ -e /bin/bash ]; then exec /bin/bash; else exec /bin/sh; fi\'"'; + command += " docker exec -ti " + container + " " + shellCmd; + } + subShellCommand = getSubShellCommand(command); + return child_process.spawn(subShellCommand.program, subShellCommand.args, { + stdio: 'inherit' + }); + }).nodeify(done); + } + }; + +}).call(this); diff --git a/build/actions/logs.js b/build/actions/logs.js index 115ad887..fde5a831 100644 --- a/build/actions/logs.js +++ b/build/actions/logs.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.12.2 +// Generated by CoffeeScript 1.12.4 /* Copyright 2016 Resin.io diff --git a/build/actions/notes.js b/build/actions/notes.js index f6ab34b6..b43d82e7 100644 --- a/build/actions/notes.js +++ b/build/actions/notes.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.12.2 +// Generated by CoffeeScript 1.12.4 /* Copyright 2016 Resin.io diff --git a/build/actions/os.js b/build/actions/os.js index dbe8633a..9b8e1d5a 100644 --- a/build/actions/os.js +++ b/build/actions/os.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.12.2 +// Generated by CoffeeScript 1.12.4 /* Copyright 2016 Resin.io diff --git a/build/actions/settings.js b/build/actions/settings.js index 55225ddc..e6e0c149 100644 --- a/build/actions/settings.js +++ b/build/actions/settings.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.12.2 +// Generated by CoffeeScript 1.12.4 /* Copyright 2016 Resin.io diff --git a/build/actions/sync.js b/build/actions/sync.js index 2f325bbb..7cd9f3ab 100644 --- a/build/actions/sync.js +++ b/build/actions/sync.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.12.2 +// Generated by CoffeeScript 1.12.4 /* Copyright 2016 Resin.io diff --git a/build/actions/wizard.js b/build/actions/wizard.js index 81d2a8e8..f91ed85c 100644 --- a/build/actions/wizard.js +++ b/build/actions/wizard.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.12.2 +// Generated by CoffeeScript 1.12.4 /* Copyright 2016 Resin.io diff --git a/build/app.js b/build/app.js index 36465ad6..9032df5b 100644 --- a/build/app.js +++ b/build/app.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.12.2 +// Generated by CoffeeScript 1.12.4 /* Copyright 2016 Resin.io @@ -156,6 +156,20 @@ limitations under the License. capitano.command(actions.ssh); + capitano.command(actions.local.configure); + + capitano.command(actions.local.flash); + + capitano.command(actions.local.logs); + + capitano.command(actions.local.promote); + + capitano.command(actions.local.push); + + capitano.command(actions.local.ssh); + + capitano.command(actions.local.scan); + update.notify(); plugins.register(/^resin-plugin-(.+)$/).then(function() { diff --git a/build/errors.js b/build/errors.js index bb06ba0f..9f27848b 100644 --- a/build/errors.js +++ b/build/errors.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.12.2 +// Generated by CoffeeScript 1.12.4 /* Copyright 2016 Resin.io diff --git a/build/events.js b/build/events.js index 548f02d5..d0348001 100644 --- a/build/events.js +++ b/build/events.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.12.2 +// Generated by CoffeeScript 1.12.4 (function() { var Mixpanel, Promise, _, capitanoState, packageJSON, resin; diff --git a/build/utils/helpers.js b/build/utils/helpers.js index 811439e0..125deb20 100644 --- a/build/utils/helpers.js +++ b/build/utils/helpers.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.12.2 +// Generated by CoffeeScript 1.12.4 /* Copyright 2016 Resin.io diff --git a/build/utils/messages.js b/build/utils/messages.js index 0be33432..6e5bbebd 100644 --- a/build/utils/messages.js +++ b/build/utils/messages.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.12.2 +// Generated by CoffeeScript 1.12.4 (function() { exports.gettingStarted = 'Run the following command to get a device started with Resin.io\n\n $ resin quickstart'; diff --git a/build/utils/patterns.js b/build/utils/patterns.js index e023fe3e..8ff87d5c 100644 --- a/build/utils/patterns.js +++ b/build/utils/patterns.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.12.2 +// Generated by CoffeeScript 1.12.4 /* Copyright 2016 Resin.io diff --git a/build/utils/plugins.js b/build/utils/plugins.js index c6bfff0f..5b6cb625 100644 --- a/build/utils/plugins.js +++ b/build/utils/plugins.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.12.2 +// Generated by CoffeeScript 1.12.4 /* Copyright 2016 Resin.io diff --git a/build/utils/update.js b/build/utils/update.js index 54a40b44..17df8248 100644 --- a/build/utils/update.js +++ b/build/utils/update.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.12.2 +// Generated by CoffeeScript 1.12.4 /* Copyright 2016 Resin.io diff --git a/build/utils/validation.js b/build/utils/validation.js index 486fbc58..a4ff0617 100644 --- a/build/utils/validation.js +++ b/build/utils/validation.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.12.2 +// Generated by CoffeeScript 1.12.4 /* Copyright 2016 Resin.io