refactor(CLI): move UNIX umount functionality to repository (#866)

- Move UNIX unmount functionality to the CLI module
- Run `electron-mocha` for the CLI code
- Make unmount command templates that take a `drivelist` object as input

Signed-off-by: Juan Cruz Viotti <jviotti@openmailbox.org>
This commit is contained in:
Juan Cruz Viotti 2016-11-14 11:11:49 +02:00 committed by GitHub
parent f80feb9edf
commit 4e3bdb7e22
5 changed files with 181 additions and 26 deletions

105
lib/cli/unmount.js Normal file
View File

@ -0,0 +1,105 @@
/*
* 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.
*/
'use strict';
const _ = require('lodash');
const Bluebird = require('bluebird');
const childProcess = Bluebird.promisifyAll(require('child_process'));
const os = require('os');
/**
* @summary Unmount command templates
* @namespace COMMAND_TEMPLATES
* @private
*
* We make sure that the commands declared here exit
* successfully even if the drive is not mounted.
*/
const COMMAND_TEMPLATES = {
/**
* @property {String} darwin
* @memberof COMMAND_TEMPLATES
*/
darwin: '/usr/sbin/diskutil unmountDisk force <%= device %>',
/**
* @property {String} linux
* @memberof COMMAND_TEMPLATES
*
* @description
* If trying to unmount the raw device in Linux, we get:
* > umount: /dev/sdN: not mounted
* Therefore we use the ?* glob to make sure umount processes
* the partitions of sdN independently (even if they contain multiple digits)
* but not the raw device.
* We also redirect stderr to /dev/null to ignore warnings
* if a device is already unmounted.
* Finally, we also wrap the command in a boolean expression
* that always evaluates to true to ignore the return code,
* which is non zero when a device was already unmounted.
*/
linux: 'umount <%= device %>?* 2>/dev/null || /bin/true'
};
/**
* @summary Get UNIX unmount command
* @function
* @public
*
* @param {String} operatingSystem - operating system slug
* @param {Object} drive - drive object
* @returns {String} command
*
* @example
* const drivelist = require('drivelist');
* const os = require('os');
*
* drivelist.list((drives) => {
* const command = unmount.getUNIXUnmountCommand(os.platform(), drives[0]);
* });
*/
exports.getUNIXUnmountCommand = (operatingSystem, drive) => {
return _.template(COMMAND_TEMPLATES[operatingSystem])(drive);
};
/**
* @summary Unmount drive
* @function
* @public
*
* @param {Object} drive - drive object
* @returns {Promise}
*
* @example
* const Bluebird = require('bluebird');
* const drivelist = Bluebird.promisifyAll(require('drivelist'));
*
* drivelist.listAsync().each(unmount.unmountDrive);
*/
exports.unmountDrive = (drive) => {
const platform = os.platform();
if (platform === 'win32') {
const removedrive = Bluebird.promisifyAll(require('removedrive'));
return removedrive.ejectAsync(drive.mountpoint);
}
const command = exports.getUNIXUnmountCommand(platform, drive);
return childProcess.execAsync(command);
};

View File

@ -20,9 +20,8 @@ const imageWrite = require('etcher-image-write');
const imageStream = require('etcher-image-stream');
const Bluebird = require('bluebird');
const fs = Bluebird.promisifyAll(require('fs'));
const umount = Bluebird.promisifyAll(require('umount'));
const os = require('os');
const isWindows = os.platform() === 'win32';
const unmount = require('./unmount');
/**
* @summary Write an image to a disk drive
@ -56,7 +55,15 @@ const isWindows = os.platform() === 'win32';
* });
*/
exports.writeImage = (imagePath, drive, options, onProgress) => {
return umount.umountAsync(drive.device).then(() => {
return Bluebird.try(() => {
// Unmounting a drive in Windows means we can't write to it anymore
if (os.platform() === 'win32') {
return;
}
return unmount.unmountDrive(drive);
}).then(() => {
return Bluebird.props({
image: imageStream.getFromFilePath(imagePath),
driveFileDescriptor: fs.openAsync(drive.raw, 'rs+')
@ -83,14 +90,6 @@ exports.writeImage = (imagePath, drive, options, onProgress) => {
return;
}
if (isWindows && drive.mountpoint) {
// The `can-ignore` annotation is EncloseJS (http://enclosejs.com) specific.
const removedrive = Bluebird.promisifyAll(require('removedrive', 'can-ignore'));
return removedrive.ejectAsync(drive.mountpoint);
}
return umount.umountAsync(drive.device);
return unmount.unmountDrive(drive);
});
};

12
npm-shrinkwrap.json generated
View File

@ -5146,18 +5146,6 @@
"from": "umd@>=3.0.0 <4.0.0",
"resolved": "https://registry.npmjs.org/umd/-/umd-3.0.1.tgz"
},
"umount": {
"version": "1.1.5",
"from": "umount@1.1.5",
"resolved": "https://registry.npmjs.org/umount/-/umount-1.1.5.tgz",
"dependencies": {
"lodash": {
"version": "3.10.1",
"from": "lodash@>=3.7.0 <4.0.0",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz"
}
}
},
"unbzip2-stream": {
"version": "1.0.10",
"from": "unbzip2-stream@>=1.0.10 <2.0.0",

View File

@ -11,7 +11,7 @@
"url": "git@github.com:resin-io/etcher.git"
},
"scripts": {
"test": "npm run lint && electron-mocha --recursive --renderer tests/gui -R min",
"test": "npm run lint && electron-mocha --recursive --renderer tests/gui -R min && electron-mocha --recursive tests/cli -R min",
"sass": "node-sass ./lib/gui/scss/main.scss > build/css/main.css",
"jslint": "eslint lib tests scripts bin versionist.conf.js",
"scsslint": "scss-lint lib/gui/scss",
@ -88,7 +88,6 @@
"sudo-prompt": "^6.1.0",
"tail": "^1.1.0",
"trackjs": "^2.1.16",
"umount": "^1.1.5",
"username": "^2.1.0",
"yargs": "^4.6.0"
},

64
tests/cli/unmount.spec.js Normal file
View File

@ -0,0 +1,64 @@
/*
* 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.
*/
'use strict';
const m = require('mochainon');
const unmount = require('../../lib/cli/unmount');
describe('CLI: Unmount', function() {
describe('.getUNIXUnmountCommand()', function() {
it('should return the correct command for OS X', function() {
const command = unmount.getUNIXUnmountCommand('darwin', {
device: '/dev/disk2',
description: 'DataTraveler 2.0',
size: 7823458304,
mountpoints: [
{
path: '/Volumes/UNTITLED'
}
],
raw: '/dev/rdisk2',
protected: false,
system: false
});
m.chai.expect(command).to.equal('/usr/sbin/diskutil unmountDisk force /dev/disk2');
});
it('should return the correct command for GNU/Linux', function() {
const command = unmount.getUNIXUnmountCommand('linux', {
device: '/dev/sda',
description: 'DataTraveler 2.0',
size: 7823458304,
mountpoints: [
{
path: '/media/UNTITLED'
}
],
raw: '/dev/sda',
protected: false,
system: false
});
m.chai.expect(command).to.equal('umount /dev/sda?* 2>/dev/null || /bin/true');
});
});
});