mirror of
https://github.com/balena-io/etcher.git
synced 2025-07-23 11:16:39 +00:00
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:
parent
f80feb9edf
commit
4e3bdb7e22
105
lib/cli/unmount.js
Normal file
105
lib/cli/unmount.js
Normal 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);
|
||||||
|
};
|
@ -20,9 +20,8 @@ const imageWrite = require('etcher-image-write');
|
|||||||
const imageStream = require('etcher-image-stream');
|
const imageStream = require('etcher-image-stream');
|
||||||
const Bluebird = require('bluebird');
|
const Bluebird = require('bluebird');
|
||||||
const fs = Bluebird.promisifyAll(require('fs'));
|
const fs = Bluebird.promisifyAll(require('fs'));
|
||||||
const umount = Bluebird.promisifyAll(require('umount'));
|
|
||||||
const os = require('os');
|
const os = require('os');
|
||||||
const isWindows = os.platform() === 'win32';
|
const unmount = require('./unmount');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @summary Write an image to a disk drive
|
* @summary Write an image to a disk drive
|
||||||
@ -56,7 +55,15 @@ const isWindows = os.platform() === 'win32';
|
|||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
exports.writeImage = (imagePath, drive, options, onProgress) => {
|
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({
|
return Bluebird.props({
|
||||||
image: imageStream.getFromFilePath(imagePath),
|
image: imageStream.getFromFilePath(imagePath),
|
||||||
driveFileDescriptor: fs.openAsync(drive.raw, 'rs+')
|
driveFileDescriptor: fs.openAsync(drive.raw, 'rs+')
|
||||||
@ -83,14 +90,6 @@ exports.writeImage = (imagePath, drive, options, onProgress) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isWindows && drive.mountpoint) {
|
return unmount.unmountDrive(drive);
|
||||||
|
|
||||||
// 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);
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
12
npm-shrinkwrap.json
generated
12
npm-shrinkwrap.json
generated
@ -5146,18 +5146,6 @@
|
|||||||
"from": "umd@>=3.0.0 <4.0.0",
|
"from": "umd@>=3.0.0 <4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/umd/-/umd-3.0.1.tgz"
|
"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": {
|
"unbzip2-stream": {
|
||||||
"version": "1.0.10",
|
"version": "1.0.10",
|
||||||
"from": "unbzip2-stream@>=1.0.10 <2.0.0",
|
"from": "unbzip2-stream@>=1.0.10 <2.0.0",
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
"url": "git@github.com:resin-io/etcher.git"
|
"url": "git@github.com:resin-io/etcher.git"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"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",
|
"sass": "node-sass ./lib/gui/scss/main.scss > build/css/main.css",
|
||||||
"jslint": "eslint lib tests scripts bin versionist.conf.js",
|
"jslint": "eslint lib tests scripts bin versionist.conf.js",
|
||||||
"scsslint": "scss-lint lib/gui/scss",
|
"scsslint": "scss-lint lib/gui/scss",
|
||||||
@ -88,7 +88,6 @@
|
|||||||
"sudo-prompt": "^6.1.0",
|
"sudo-prompt": "^6.1.0",
|
||||||
"tail": "^1.1.0",
|
"tail": "^1.1.0",
|
||||||
"trackjs": "^2.1.16",
|
"trackjs": "^2.1.16",
|
||||||
"umount": "^1.1.5",
|
|
||||||
"username": "^2.1.0",
|
"username": "^2.1.0",
|
||||||
"yargs": "^4.6.0"
|
"yargs": "^4.6.0"
|
||||||
},
|
},
|
||||||
|
64
tests/cli/unmount.spec.js
Normal file
64
tests/cli/unmount.spec.js
Normal 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');
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
Loading…
x
Reference in New Issue
Block a user