From 356042557e0cdc1d7434bdb50fd4bbb8f42785b0 Mon Sep 17 00:00:00 2001 From: Kostas Lekkas Date: Wed, 8 Mar 2017 21:49:56 +0000 Subject: [PATCH] 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",