balena-cli/lib/actions/device.coffee

377 lines
10 KiB
CoffeeScript
Raw Normal View History

Promise = require('bluebird')
capitano = require('capitano')
2015-08-12 12:17:46 +00:00
_ = require('lodash')
2014-12-19 14:05:54 +00:00
async = require('async')
2015-01-08 12:04:37 +00:00
resin = require('resin-sdk')
visuals = require('resin-cli-visuals')
vcs = require('resin-vcs')
2015-05-18 13:34:40 +00:00
manager = require('resin-image-manager')
image = require('resin-image')
inject = require('resin-config-inject')
2015-06-04 12:55:32 +00:00
registerDevice = require('resin-register-device')
pine = require('resin-pine')
deviceConfig = require('resin-device-config')
2015-07-27 12:08:55 +00:00
form = require('resin-cli-form')
2015-05-07 15:40:12 +00:00
htmlToText = require('html-to-text')
helpers = require('../utils/helpers')
commandOptions = require('./command-options')
2014-11-19 17:38:15 +00:00
exports.list =
signature: 'devices'
description: 'list all devices'
help: '''
Use this command to list all devices that belong to you.
You can filter the devices by application by using the `--application` option.
Examples:
$ resin devices
$ resin devices --application MyApp
$ resin devices --app MyApp
$ resin devices -a MyApp
'''
options: [ commandOptions.optionalApplication ]
permission: 'user'
action: (params, options, done) ->
Promise.try ->
if options.application?
return resin.models.device.getAllByApplication(options.application)
return resin.models.device.getAll()
.tap (devices) ->
console.log visuals.table.horizontal devices, [
2015-01-22 17:06:02 +00:00
'id'
'name'
2015-01-22 17:20:20 +00:00
'device_type'
2015-01-22 17:06:02 +00:00
'is_online'
'application_name'
'status'
'last_seen'
]
.nodeify(done)
exports.info =
signature: 'device <uuid>'
description: 'list a single device'
help: '''
Use this command to show information about a single device.
Examples:
$ resin device 7cf02a62a3a84440b1bb5579a3d57469148943278630b17e7fc6c4f7b465c9
'''
permission: 'user'
action: (params, options, done) ->
resin.models.device.get(params.uuid).then (device) ->
# TODO: We should outsource this logic and probably
# other last_seen edge cases to either Resin CLI Visuals
# or have it parsed appropriately in the SDK.
device.last_seen ?= 'Not seen'
console.log visuals.table.vertical device, [
"$#{device.name}$"
2015-01-22 17:06:02 +00:00
'id'
2015-01-22 17:20:20 +00:00
'device_type'
2015-01-22 17:06:02 +00:00
'is_online'
'ip_address'
'application_name'
'status'
'last_seen'
'uuid'
'commit'
'supervisor_version'
'is_web_accessible'
'note'
]
.nodeify(done)
exports.remove =
signature: 'device rm <uuid>'
description: 'remove a device'
help: '''
Use this command to remove a device from resin.io.
Notice this command asks for confirmation interactively.
You can avoid this by passing the `--yes` boolean option.
Examples:
$ resin device rm 7cf02a62a3a84440b1bb5579a3d57469148943278630b17e7fc6c4f7b465c9
$ resin device rm 7cf02a62a3a84440b1bb5579a3d57469148943278630b17e7fc6c4f7b465c9 --yes
'''
options: [ commandOptions.yes ]
permission: 'user'
action: (params, options, done) ->
helpers.confirm(options.yes, 'Are you sure you want to delete the device?').then (confirmed) ->
return if not confirmed
resin.models.device.remove(params.uuid)
.nodeify(done)
exports.identify =
signature: 'device identify <uuid>'
description: 'identify a device with a UUID'
help: '''
Use this command to identify a device.
In the Raspberry Pi, the ACT led is blinked several times.
Examples:
$ resin device identify 23c73a12e3527df55c60b9ce647640c1b7da1b32d71e6a39849ac0f00db828
'''
permission: 'user'
action: (params, options, done) ->
resin.models.device.identify(params.uuid).nodeify(done)
exports.rename =
signature: 'device rename <uuid> [newName]'
description: 'rename a resin device'
help: '''
Use this command to rename a device.
If you omit the name, you'll get asked for it interactively.
Examples:
$ resin device rename 7cf02a62a3a84440b1bb5579a3d57469148943278630b17e7fc6c4f7b465c9 MyPi
$ resin device rename 7cf02a62a3a84440b1bb5579a3d57469148943278630b17e7fc6c4f7b465c9
'''
permission: 'user'
action: (params, options, done) ->
Promise.try ->
return params.newName if not _.isEmpty(params.newName)
form.ask
message: 'How do you want to name this device?'
type: 'input'
.then(_.partial(resin.models.device.rename, params.uuid))
.nodeify(done)
2015-05-05 05:08:11 +00:00
exports.await =
signature: 'device await <uuid>'
2015-05-05 05:08:11 +00:00
description: 'await for a device to become online'
help: '''
Use this command to await for a device to become online.
The process will exit when the device becomes online.
Notice that there is no time limit for this command, so it might run forever.
You can configure the poll interval with the --interval option (defaults to 3000ms).
Examples:
$ resin device await 7cf02a62a3a84440b1bb5579a3d57469148943278630b17e7fc6c4f7b465c9
$ resin device await 7cf02a62a3a84440b1bb5579a3d57469148943278630b17e7fc6c4f7b465c9 --interval 1000
2015-05-05 05:08:11 +00:00
'''
options: [
signature: 'interval'
parameter: 'interval'
description: 'poll interval'
alias: 'i'
]
permission: 'user'
action: (params, options, done) ->
options.interval ?= 3000
spinner = new visuals.Spinner("Awaiting device: #{params.uuid}")
2015-05-05 05:08:11 +00:00
poll = ->
resin.models.device.isOnline(params.uuid).then (isOnline) ->
2015-05-05 05:08:11 +00:00
if isOnline
spinner.stop()
console.info("Device became online: #{params.uuid}")
return
2015-05-05 05:08:11 +00:00
else
# Spinner implementation is smart enough to
# not start again if it was already started
spinner.start()
return Promise.delay(options.interval).then(poll)
poll().nodeify(done)
2015-05-05 05:08:11 +00:00
2015-02-04 18:13:28 +00:00
exports.init =
signature: 'device init [device]'
description: 'initialise a device with resin os'
help: '''
2015-02-05 13:56:22 +00:00
Use this command to download the OS image of a certain application and write it to an SD Card.
2015-02-04 18:13:28 +00:00
2015-02-05 13:56:22 +00:00
Note that this command requires admin privileges.
If `device` is omitted, you will be prompted to select a device interactively.
Notice this command asks for confirmation interactively.
You can avoid this by passing the `--yes` boolean option.
2015-05-18 13:34:40 +00:00
You can quiet the progress bar and other logging information by passing the `--quiet` boolean option.
2015-02-05 13:56:22 +00:00
You need to configure the network type and other settings:
Ethernet:
You can setup the device OS to use ethernet by setting the `--network` option to "ethernet".
2015-02-05 13:56:22 +00:00
Wifi:
You can setup the device OS to use wifi by setting the `--network` option to "wifi".
If you set "network" to "wifi", you will need to specify the `--ssid` and `--key` option as well.
2015-02-05 13:56:22 +00:00
2015-03-11 13:05:20 +00:00
You can omit network related options to be asked about them interactively.
2015-02-05 13:56:22 +00:00
Examples:
2015-03-23 12:25:45 +00:00
$ resin device init
$ resin device init --application MyApp
$ resin device init --application MyApp --network ethernet
$ resin device init /dev/disk2 --application MyApp --network wifi --ssid MyNetwork --key secret
2015-02-04 18:13:28 +00:00
'''
options: [
commandOptions.optionalApplication
2015-02-04 18:13:28 +00:00
commandOptions.network
commandOptions.wifiSsid
commandOptions.wifiKey
]
permission: 'user'
2015-05-18 13:34:40 +00:00
root: true
2015-02-04 18:13:28 +00:00
action: (params, options, done) ->
2015-05-18 13:34:40 +00:00
networkOptions =
network: options.network
wifiSsid: options.ssid
wifiKey: options.key
2015-07-27 12:08:55 +00:00
async.waterfall [
2015-02-04 18:13:28 +00:00
(callback) ->
return callback(null, options.application) if options.application?
2015-07-10 17:04:17 +00:00
vcs.getApplicationName(process.cwd()).nodeify(callback)
(applicationName, callback) ->
2015-05-18 13:34:40 +00:00
options.application = applicationName
resin.models.application.has(options.application).nodeify(callback)
(hasApplication, callback) ->
if not hasApplication
return callback(new Error("Invalid application: #{options.application}"))
2015-02-04 18:13:28 +00:00
return callback(null, params.device) if params.device?
visuals.drive().nodeify(callback)
2015-02-04 18:13:28 +00:00
(device, callback) ->
params.device = device
2015-03-23 22:32:18 +00:00
message = "This will completely erase #{params.device}. Are you sure you want to continue?"
2015-07-27 12:08:55 +00:00
if options.yes
return callback(null, true)
else
form.ask
message: message
type: 'confirm'
default: false
.nodeify(callback)
2015-02-04 18:13:28 +00:00
(confirmed, callback) ->
return done() if not confirmed
2015-05-18 13:34:40 +00:00
return callback() if networkOptions.network?
2015-07-27 12:08:55 +00:00
form.run [
message: 'Network Type'
name: 'network'
type: 'list'
choices: [ 'ethernet', 'wifi' ]
,
message: 'Wifi Ssid'
name: 'wifiSsid'
type: 'input'
when:
network: 'wifi'
,
message: 'Wifi Key'
name: 'wifiKey'
type: 'input'
when:
network: 'wifi'
]
.then (parameters) ->
2015-05-18 13:34:40 +00:00
_.extend(networkOptions, parameters)
2015-07-27 12:08:55 +00:00
.nodeify(callback)
2015-02-04 18:13:28 +00:00
2015-05-18 13:34:40 +00:00
(callback) ->
console.info("Checking application: #{options.application}")
resin.models.application.get(options.application).nodeify(callback)
2015-05-18 13:34:40 +00:00
(application, callback) ->
2015-06-04 12:55:32 +00:00
async.parallel
2015-06-04 12:55:32 +00:00
manifest: (callback) ->
console.info('Getting device manifest for the application')
resin.models.device.getManifestBySlug(application.device_type).nodeify(callback)
2015-06-04 12:55:32 +00:00
config: (callback) ->
console.info('Fetching application configuration')
deviceConfig.get(options.application, networkOptions).nodeify(callback)
2015-06-04 12:55:32 +00:00
, callback
2015-05-18 13:34:40 +00:00
2015-06-04 12:55:32 +00:00
(results, callback) ->
2015-05-07 15:40:12 +00:00
params.manifest = results.manifest
2015-06-04 12:55:32 +00:00
console.info('Associating the device')
2015-05-18 13:34:40 +00:00
2015-06-04 12:55:32 +00:00
registerDevice.register pine, results.config, (error, device) ->
return callback(error) if error?
2015-06-04 12:55:32 +00:00
# Associate a device
results.config.deviceId = device.id
results.config.uuid = device.uuid
2015-07-14 12:22:44 +00:00
results.config.registered_at = Math.floor(Date.now() / 1000)
2015-05-18 13:34:40 +00:00
2015-06-04 12:55:32 +00:00
params.uuid = results.config.uuid
return callback(null, results)
(results, callback) ->
console.info('Initializing device operating system image')
2015-06-04 12:55:32 +00:00
if process.env.DEBUG
console.log(results.config)
bar = new visuals.Progress('Downloading Device OS')
spinner = new visuals.Spinner('Downloading Device OS (size unknown)')
2015-06-04 12:55:32 +00:00
2015-05-07 15:40:12 +00:00
manager.configure params.manifest, results.config, (error, imagePath, removeCallback) ->
2015-06-04 12:55:32 +00:00
spinner.stop()
return callback(error, imagePath, removeCallback)
, (state) ->
if state?
bar.update(state)
else
spinner.start()
2015-05-18 13:34:40 +00:00
(configuredImagePath, removeCallback, callback) ->
2015-05-07 15:40:12 +00:00
console.info('The base image was cached to improve initialization time of similar devices')
2015-05-18 13:34:40 +00:00
console.info('Attempting to write operating system image to drive')
bar = new visuals.Progress('Writing Device OS')
2015-05-18 13:34:40 +00:00
image.write
device: params.device
image: configuredImagePath
progress: _.bind(bar.update, bar)
, (error) ->
return callback(error) if error?
2015-05-18 13:34:40 +00:00
return callback(null, configuredImagePath, removeCallback)
(temporalImagePath, removeCallback, callback) ->
console.info('Image written successfully')
removeCallback(callback)
2015-02-04 18:13:28 +00:00
2015-06-04 12:55:32 +00:00
(callback) ->
resin.models.device.get(params.uuid).nodeify(callback)
2015-06-04 12:55:32 +00:00
(device, callback) ->
console.info("Device created: #{device.name}")
2015-05-07 15:40:12 +00:00
return callback(null, params.uuid)
2015-07-27 12:08:55 +00:00
], done