mirror of
https://github.com/balena-io/etcher.git
synced 2025-04-24 07:17:18 +00:00
Revert "refactor: remove extended archives extra functionality (#1055)"
This reverts commit b78473ea0ebd76233217fb4f236bb34be635da90.
This commit is contained in:
parent
b78473ea0e
commit
fd9d3ce749
@ -77,6 +77,7 @@ exports.writeImage = (imagePath, drive, options, onProgress) => {
|
||||
}, {
|
||||
check: options.validateWriteOnSuccess,
|
||||
transform: image.transform,
|
||||
bmap: image.bmap,
|
||||
bytesToZeroOutFromTheBeginning: image.bytesToZeroOutFromTheBeginning
|
||||
});
|
||||
}).then((writer) => {
|
||||
|
@ -213,7 +213,9 @@ app.controller('HeaderController', function(SelectionStateModel, OSOpenExternalS
|
||||
* HeaderController.openHelpPage();
|
||||
*/
|
||||
this.openHelpPage = () => {
|
||||
OSOpenExternalService.open('https://github.com/resin-io/etcher/blob/master/SUPPORT.md');
|
||||
const DEFAULT_SUPPORT_URL = 'https://github.com/resin-io/etcher/blob/master/SUPPORT.md';
|
||||
const supportUrl = SelectionStateModel.getImageSupportUrl() || DEFAULT_SUPPORT_URL;
|
||||
OSOpenExternalService.open(supportUrl);
|
||||
};
|
||||
|
||||
});
|
||||
|
@ -142,6 +142,76 @@ SelectionStateModel.service('SelectionStateModel', function(DrivesModel) {
|
||||
return _.get(Store.getState().toJS(), 'selection.image.size');
|
||||
};
|
||||
|
||||
/**
|
||||
* @summary Get image url
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @returns {String} image url
|
||||
*
|
||||
* @example
|
||||
* const imageUrl = SelectionStateModel.getImageUrl();
|
||||
*/
|
||||
this.getImageUrl = () => {
|
||||
return _.get(Store.getState().toJS(), 'selection.image.url');
|
||||
};
|
||||
|
||||
/**
|
||||
* @summary Get image name
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @returns {String} image name
|
||||
*
|
||||
* @example
|
||||
* const imageName = SelectionStateModel.getImageName();
|
||||
*/
|
||||
this.getImageName = () => {
|
||||
return _.get(Store.getState().toJS(), 'selection.image.name');
|
||||
};
|
||||
|
||||
/**
|
||||
* @summary Get image logo
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @returns {String} image logo
|
||||
*
|
||||
* @example
|
||||
* const imageLogo = SelectionStateModel.getImageLogo();
|
||||
*/
|
||||
this.getImageLogo = () => {
|
||||
return _.get(Store.getState().toJS(), 'selection.image.logo');
|
||||
};
|
||||
|
||||
/**
|
||||
* @summary Get image support url
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @returns {String} image support url
|
||||
*
|
||||
* @example
|
||||
* const imageSupportUrl = SelectionStateModel.getImageSupportUrl();
|
||||
*/
|
||||
this.getImageSupportUrl = () => {
|
||||
return _.get(Store.getState().toJS(), 'selection.image.supportUrl');
|
||||
};
|
||||
|
||||
/**
|
||||
* @summary Get image recommended drive size
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @returns {String} image recommended drive size
|
||||
*
|
||||
* @example
|
||||
* const imageRecommendedDriveSize = SelectionStateModel.getImageRecommendedDriveSize();
|
||||
*/
|
||||
this.getImageRecommendedDriveSize = () => {
|
||||
return _.get(Store.getState().toJS(), 'selection.image.recommendedDriveSize');
|
||||
};
|
||||
|
||||
/**
|
||||
* @summary Check if there is a selected drive
|
||||
* @function
|
||||
|
@ -249,13 +249,28 @@ const storeReducer = (state, action) => {
|
||||
throw new Error(`Invalid image size: ${action.data.size}`);
|
||||
}
|
||||
|
||||
if (action.data.url && !_.isString(action.data.url)) {
|
||||
throw new Error(`Invalid image url: ${action.data.url}`);
|
||||
}
|
||||
|
||||
if (action.data.name && !_.isString(action.data.name)) {
|
||||
throw new Error(`Invalid image name: ${action.data.name}`);
|
||||
}
|
||||
|
||||
if (action.data.logo && !_.isString(action.data.logo)) {
|
||||
throw new Error(`Invalid image logo: ${action.data.logo}`);
|
||||
}
|
||||
|
||||
const selectedDevice = state.getIn([ 'selection', 'drive' ]);
|
||||
const selectedDrive = state.get('availableDrives').find((drive) => {
|
||||
return drive.get('device') === selectedDevice;
|
||||
});
|
||||
|
||||
return _.attempt(() => {
|
||||
if (selectedDrive && !constraints.isDriveLargeEnough(selectedDrive.toJS(), action.data)) {
|
||||
if (selectedDrive && !_.every([
|
||||
constraints.isDriveLargeEnough(selectedDrive.toJS(), action.data),
|
||||
constraints.isDriveSizeRecommended(selectedDrive.toJS(), action.data)
|
||||
])) {
|
||||
return storeReducer(state, {
|
||||
type: ACTIONS.REMOVE_DRIVE
|
||||
});
|
||||
|
@ -66,6 +66,12 @@ module.exports = function(SupportedFormatsModel, SelectionStateModel, AnalyticsS
|
||||
}
|
||||
|
||||
SelectionStateModel.setImage(image);
|
||||
|
||||
// An easy way so we can quickly identify if we're making use of
|
||||
// certain features without printing pages of text to DevTools.
|
||||
image.logo = Boolean(image.logo);
|
||||
image.bmap = Boolean(image.bmap);
|
||||
|
||||
AnalyticsService.logEvent('Select image', image);
|
||||
};
|
||||
|
||||
|
@ -43,7 +43,7 @@
|
||||
|
||||
<svg-icon
|
||||
class="center-block"
|
||||
path="../../../assets/image.svg"
|
||||
path="{{ main.selection.getImageLogo() || '../../../assets/image.svg' }}"
|
||||
ng-disabled="main.shouldImageStepBeDisabled()"></svg-icon>
|
||||
<span
|
||||
class="icon-caption"
|
||||
@ -67,8 +67,9 @@
|
||||
</div>
|
||||
<div ng-if="main.selection.hasImage()">
|
||||
<div class="step-selection-text">
|
||||
<span ng-class="{ 'text-disabled': main.shouldImageStepBeDisabled() }"
|
||||
ng-bind="main.selection.getImagePath() | basename | middleEllipses:20"
|
||||
<span ng-click="main.external.open(main.selection.getImageUrl())"
|
||||
ng-class="{ 'text-disabled': main.shouldImageStepBeDisabled() }"
|
||||
ng-bind="main.selection.getImageName() || main.selection.getImagePath() | basename | middleEllipses:20"
|
||||
uib-tooltip="{{ main.selection.getImagePath() | basename }}"></span>
|
||||
|
||||
<span class="glyphicon glyphicon-info-sign"
|
||||
|
@ -17,10 +17,20 @@
|
||||
'use strict';
|
||||
|
||||
const path = require('path');
|
||||
const Bluebird = require('bluebird');
|
||||
const rindle = require('rindle');
|
||||
const _ = require('lodash');
|
||||
const PassThroughStream = require('stream').PassThrough;
|
||||
const supportedFileTypes = require('./supported');
|
||||
|
||||
/**
|
||||
* @summary Archive metadata base path
|
||||
* @constant
|
||||
* @private
|
||||
* @type {String}
|
||||
*/
|
||||
const ARCHIVE_METADATA_BASE_PATH = '.meta';
|
||||
|
||||
/**
|
||||
* @summary Image extensions
|
||||
* @constant
|
||||
@ -35,6 +45,103 @@ const IMAGE_EXTENSIONS = _.reduce(supportedFileTypes, (accumulator, file) => {
|
||||
return accumulator;
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* @summary Extract entry by path
|
||||
* @function
|
||||
* @private
|
||||
*
|
||||
* @param {String} archive - archive
|
||||
* @param {String} filePath - entry file path
|
||||
* @param {Object} options - options
|
||||
* @param {Object} options.hooks - archive hooks
|
||||
* @param {Object[]} options.entries - archive entries
|
||||
* @param {*} [options.default] - entry default value
|
||||
* @fulfil {*} contents
|
||||
* @returns {Promise}
|
||||
*
|
||||
* extractEntryByPath('my/archive.zip', '_info/logo.svg', {
|
||||
* hooks: { ... },
|
||||
* entries: [ ... ],
|
||||
* default: ''
|
||||
* }).then((contents) => {
|
||||
* console.log(contents);
|
||||
* });
|
||||
*/
|
||||
const extractEntryByPath = (archive, filePath, options) => {
|
||||
const fileEntry = _.find(options.entries, (entry) => {
|
||||
return _.chain(entry.name)
|
||||
.split('/')
|
||||
.tail()
|
||||
.join('/')
|
||||
.value() === filePath;
|
||||
});
|
||||
|
||||
if (!fileEntry) {
|
||||
return Bluebird.resolve(options.default);
|
||||
}
|
||||
|
||||
return options.hooks.extractFile(archive, options.entries, fileEntry.name)
|
||||
.then(rindle.extract);
|
||||
};
|
||||
|
||||
/**
|
||||
* @summary Extract archive metadata
|
||||
* @function
|
||||
* @private
|
||||
*
|
||||
* @param {String} archive - archive
|
||||
* @param {String} basePath - metadata base path
|
||||
* @param {Object} options - options
|
||||
* @param {Object[]} options.entries - archive entries
|
||||
* @param {Object} options.hooks - archive hooks
|
||||
* @fulfil {Object} - metadata
|
||||
* @returns {Promise}
|
||||
*
|
||||
* extractArchiveMetadata('my/archive.zip', '.meta', {
|
||||
* hooks: { ... },
|
||||
* entries: [ ... ]
|
||||
* }).then((metadata) => {
|
||||
* console.log(metadata);
|
||||
* });
|
||||
*/
|
||||
const extractArchiveMetadata = (archive, basePath, options) => {
|
||||
return Bluebird.props({
|
||||
logo: extractEntryByPath(archive, `${basePath}/logo.svg`, options),
|
||||
instructions: extractEntryByPath(archive, `${basePath}/instructions.markdown`, options),
|
||||
bmap: extractEntryByPath(archive, `${basePath}/image.bmap`, options),
|
||||
manifest: _.attempt(() => {
|
||||
return extractEntryByPath(archive, `${basePath}/manifest.json`, {
|
||||
hooks: options.hooks,
|
||||
entries: options.entries,
|
||||
default: '{}'
|
||||
}).then((manifest) => {
|
||||
try {
|
||||
return JSON.parse(manifest);
|
||||
} catch (parseError) {
|
||||
const error = new Error('Invalid archive manifest.json');
|
||||
error.description = 'The archive manifest.json file is not valid JSON.';
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
})
|
||||
}).then((results) => {
|
||||
return {
|
||||
name: results.manifest.name,
|
||||
version: results.manifest.version,
|
||||
url: results.manifest.url,
|
||||
supportUrl: results.manifest.supportUrl,
|
||||
releaseNotesUrl: results.manifest.releaseNotesUrl,
|
||||
checksumType: results.manifest.checksumType,
|
||||
checksum: results.manifest.checksum,
|
||||
bytesToZeroOutFromTheBeginning: results.manifest.bytesToZeroOutFromTheBeginning,
|
||||
recommendedDriveSize: results.manifest.recommendedDriveSize,
|
||||
logo: results.logo,
|
||||
bmap: results.bmap,
|
||||
instructions: results.instructions
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @summary Extract image from archive
|
||||
* @function
|
||||
@ -75,18 +182,25 @@ exports.extractImage = (archive, hooks) => {
|
||||
|
||||
const imageEntry = _.first(imageEntries);
|
||||
|
||||
return hooks.extractFile(archive, entries, imageEntry.name).then((imageStream) => {
|
||||
return {
|
||||
stream: imageStream,
|
||||
transform: new PassThroughStream(),
|
||||
size: {
|
||||
original: imageEntry.size,
|
||||
final: {
|
||||
estimation: false,
|
||||
value: imageEntry.size
|
||||
}
|
||||
return Bluebird.props({
|
||||
imageStream: hooks.extractFile(archive, entries, imageEntry.name),
|
||||
metadata: extractArchiveMetadata(archive, ARCHIVE_METADATA_BASE_PATH, {
|
||||
entries,
|
||||
hooks
|
||||
})
|
||||
}).then((results) => {
|
||||
results.metadata.stream = results.imageStream;
|
||||
results.metadata.transform = new PassThroughStream();
|
||||
|
||||
results.metadata.size = {
|
||||
original: imageEntry.size,
|
||||
final: {
|
||||
estimation: false,
|
||||
value: imageEntry.size
|
||||
}
|
||||
};
|
||||
|
||||
return results.metadata;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
@ -99,7 +99,10 @@ exports.getFromFilePath = (file) => {
|
||||
* const imageStream = require('./lib/image-stream');
|
||||
*
|
||||
* imageStream.getImageMetadata('path/to/rpi.img.xz').then((metadata) => {
|
||||
* console.log(`The image original size is: ${metada.size.original}`);
|
||||
* console.log(`The image display name is: ${metada.name}`);
|
||||
* console.log(`The image url is: ${metada.url}`);
|
||||
* console.log(`The image support url is: ${metada.supportUrl}`);
|
||||
* console.log(`The image logo is: ${metada.logo}`);
|
||||
* });
|
||||
*/
|
||||
exports.getImageMetadata = (file) => {
|
||||
|
32
npm-shrinkwrap.json
generated
32
npm-shrinkwrap.json
generated
@ -512,16 +512,16 @@
|
||||
"from": "isarray@0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz"
|
||||
},
|
||||
"isexe": {
|
||||
"version": "1.1.2",
|
||||
"from": "isexe@>=1.1.1 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-1.1.2.tgz"
|
||||
},
|
||||
"isstream": {
|
||||
"version": "0.1.2",
|
||||
"from": "isstream@>=0.1.1 <0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz"
|
||||
},
|
||||
"isexe": {
|
||||
"version": "1.1.2",
|
||||
"from": "isexe@>=1.1.1 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-1.1.2.tgz"
|
||||
},
|
||||
"js-message": {
|
||||
"version": "1.0.5",
|
||||
"from": "js-message@>=1.0.5",
|
||||
@ -1451,6 +1451,23 @@
|
||||
"from": "restore-cursor@>=1.0.1 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz"
|
||||
},
|
||||
"rindle": {
|
||||
"version": "1.3.0",
|
||||
"from": "rindle@>=1.3.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/rindle/-/rindle-1.3.0.tgz",
|
||||
"dependencies": {
|
||||
"bluebird": {
|
||||
"version": "2.11.0",
|
||||
"from": "bluebird@>=2.10.2 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.11.0.tgz"
|
||||
},
|
||||
"lodash": {
|
||||
"version": "3.10.1",
|
||||
"from": "lodash@>=3.10.1 <4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz"
|
||||
}
|
||||
}
|
||||
},
|
||||
"run-async": {
|
||||
"version": "0.1.0",
|
||||
"from": "run-async@>=0.1.0 <0.2.0",
|
||||
@ -1575,6 +1592,11 @@
|
||||
"from": "string-template@>=0.2.1 <0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/string-template/-/string-template-0.2.1.tgz"
|
||||
},
|
||||
"string-to-stream": {
|
||||
"version": "1.1.0",
|
||||
"from": "string-to-stream@>=1.0.1 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/string-to-stream/-/string-to-stream-1.1.0.tgz"
|
||||
},
|
||||
"string-width": {
|
||||
"version": "1.0.1",
|
||||
"from": "string-width@>=1.0.1 <2.0.0",
|
||||
|
@ -90,6 +90,7 @@
|
||||
"redux-localstorage": "^0.4.1",
|
||||
"resin-cli-form": "^1.4.1",
|
||||
"resin-cli-visuals": "^1.2.8",
|
||||
"rindle": "^1.3.0",
|
||||
"rx": "^4.1.0",
|
||||
"semver": "^5.1.0",
|
||||
"sudo-prompt": "^6.1.0",
|
||||
@ -113,7 +114,6 @@
|
||||
"jsonfile": "^2.3.1",
|
||||
"mochainon": "^1.0.0",
|
||||
"node-sass": "^3.8.0",
|
||||
"rindle": "^1.3.0",
|
||||
"tmp": "0.0.31",
|
||||
"versionist": "^2.1.0"
|
||||
},
|
||||
|
@ -47,6 +47,26 @@ describe('Browser: SelectionState', function() {
|
||||
m.chai.expect(SelectionStateModel.getImageSize()).to.be.undefined;
|
||||
});
|
||||
|
||||
it('getImageUrl() should return undefined', function() {
|
||||
m.chai.expect(SelectionStateModel.getImageUrl()).to.be.undefined;
|
||||
});
|
||||
|
||||
it('getImageName() should return undefined', function() {
|
||||
m.chai.expect(SelectionStateModel.getImageName()).to.be.undefined;
|
||||
});
|
||||
|
||||
it('getImageLogo() should return undefined', function() {
|
||||
m.chai.expect(SelectionStateModel.getImageLogo()).to.be.undefined;
|
||||
});
|
||||
|
||||
it('getImageSupportUrl() should return undefined', function() {
|
||||
m.chai.expect(SelectionStateModel.getImageSupportUrl()).to.be.undefined;
|
||||
});
|
||||
|
||||
it('getImageRecommendedDriveSize() should return undefined', function() {
|
||||
m.chai.expect(SelectionStateModel.getImageRecommendedDriveSize()).to.be.undefined;
|
||||
});
|
||||
|
||||
it('hasDrive() should return false', function() {
|
||||
const hasDrive = SelectionStateModel.hasDrive();
|
||||
m.chai.expect(hasDrive).to.be.false;
|
||||
@ -199,7 +219,12 @@ describe('Browser: SelectionState', function() {
|
||||
beforeEach(function() {
|
||||
this.image = {
|
||||
path: 'foo.img',
|
||||
size: 999999999
|
||||
size: 999999999,
|
||||
recommendedDriveSize: 1000000000,
|
||||
url: 'https://www.raspbian.org',
|
||||
supportUrl: 'https://www.raspbian.org/forums/',
|
||||
name: 'Raspbian',
|
||||
logo: '<svg><text fill="red">Raspbian</text></svg>'
|
||||
};
|
||||
|
||||
SelectionStateModel.setImage(this.image);
|
||||
@ -250,6 +275,51 @@ describe('Browser: SelectionState', function() {
|
||||
|
||||
});
|
||||
|
||||
describe('.getImageUrl()', function() {
|
||||
|
||||
it('should return the image url', function() {
|
||||
const imageUrl = SelectionStateModel.getImageUrl();
|
||||
m.chai.expect(imageUrl).to.equal('https://www.raspbian.org');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('.getImageName()', function() {
|
||||
|
||||
it('should return the image name', function() {
|
||||
const imageName = SelectionStateModel.getImageName();
|
||||
m.chai.expect(imageName).to.equal('Raspbian');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('.getImageLogo()', function() {
|
||||
|
||||
it('should return the image logo', function() {
|
||||
const imageLogo = SelectionStateModel.getImageLogo();
|
||||
m.chai.expect(imageLogo).to.equal('<svg><text fill="red">Raspbian</text></svg>');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('.getImageSupportUrl()', function() {
|
||||
|
||||
it('should return the image support url', function() {
|
||||
const imageSupportUrl = SelectionStateModel.getImageSupportUrl();
|
||||
m.chai.expect(imageSupportUrl).to.equal('https://www.raspbian.org/forums/');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('.getImageRecommendedDriveSize()', function() {
|
||||
|
||||
it('should return the image recommended drive size', function() {
|
||||
const imageRecommendedDriveSize = SelectionStateModel.getImageRecommendedDriveSize();
|
||||
m.chai.expect(imageRecommendedDriveSize).to.equal(1000000000);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('.hasImage()', function() {
|
||||
|
||||
it('should return true', function() {
|
||||
@ -340,6 +410,36 @@ describe('Browser: SelectionState', function() {
|
||||
}).to.throw('Invalid image size: 999999999');
|
||||
});
|
||||
|
||||
it('should throw if url is defined but its not a string', function() {
|
||||
m.chai.expect(function() {
|
||||
SelectionStateModel.setImage({
|
||||
path: 'foo.img',
|
||||
size: 999999999,
|
||||
url: 1234
|
||||
});
|
||||
}).to.throw('Invalid image url: 1234');
|
||||
});
|
||||
|
||||
it('should throw if name is defined but its not a string', function() {
|
||||
m.chai.expect(function() {
|
||||
SelectionStateModel.setImage({
|
||||
path: 'foo.img',
|
||||
size: 999999999,
|
||||
name: 1234
|
||||
});
|
||||
}).to.throw('Invalid image name: 1234');
|
||||
});
|
||||
|
||||
it('should throw if logo is defined but its not a string', function() {
|
||||
m.chai.expect(function() {
|
||||
SelectionStateModel.setImage({
|
||||
path: 'foo.img',
|
||||
size: 999999999,
|
||||
logo: 1234
|
||||
});
|
||||
}).to.throw('Invalid image logo: 1234');
|
||||
});
|
||||
|
||||
it('should de-select a previously selected not-large-enough drive', function() {
|
||||
DrivesModel.setDrives([
|
||||
{
|
||||
@ -362,6 +462,29 @@ describe('Browser: SelectionState', function() {
|
||||
SelectionStateModel.removeImage();
|
||||
});
|
||||
|
||||
it('should de-select a previously selected not-recommended drive', function() {
|
||||
DrivesModel.setDrives([
|
||||
{
|
||||
device: '/dev/disk1',
|
||||
name: 'USB Drive',
|
||||
size: 1200000000,
|
||||
protected: false
|
||||
}
|
||||
]);
|
||||
|
||||
SelectionStateModel.setDrive('/dev/disk1');
|
||||
m.chai.expect(SelectionStateModel.hasDrive()).to.be.true;
|
||||
|
||||
SelectionStateModel.setImage({
|
||||
path: 'foo.img',
|
||||
size: 999999999,
|
||||
recommendedDriveSize: 1500000000
|
||||
});
|
||||
|
||||
m.chai.expect(SelectionStateModel.hasDrive()).to.be.false;
|
||||
SelectionStateModel.removeImage();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
BIN
tests/image-stream/data/metadata/zip/rpi-invalid-manifest.zip
Normal file
BIN
tests/image-stream/data/metadata/zip/rpi-invalid-manifest.zip
Normal file
Binary file not shown.
BIN
tests/image-stream/data/metadata/zip/rpi-with-bmap.zip
Normal file
BIN
tests/image-stream/data/metadata/zip/rpi-with-bmap.zip
Normal file
Binary file not shown.
BIN
tests/image-stream/data/metadata/zip/rpi-with-instructions.zip
Normal file
BIN
tests/image-stream/data/metadata/zip/rpi-with-instructions.zip
Normal file
Binary file not shown.
BIN
tests/image-stream/data/metadata/zip/rpi-with-logo.zip
Normal file
BIN
tests/image-stream/data/metadata/zip/rpi-with-logo.zip
Normal file
Binary file not shown.
BIN
tests/image-stream/data/metadata/zip/rpi-with-manifest.zip
Normal file
BIN
tests/image-stream/data/metadata/zip/rpi-with-manifest.zip
Normal file
Binary file not shown.
171
tests/image-stream/metadata/zip.spec.js
Normal file
171
tests/image-stream/metadata/zip.spec.js
Normal file
@ -0,0 +1,171 @@
|
||||
/*
|
||||
* 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 path = require('path');
|
||||
const DATA_PATH = path.join(__dirname, '..', 'data');
|
||||
const IMAGES_PATH = path.join(DATA_PATH, 'images');
|
||||
const ZIP_PATH = path.join(DATA_PATH, 'metadata', 'zip');
|
||||
const tester = require('../tester');
|
||||
const imageStream = require('../../../lib/image-stream/index');
|
||||
|
||||
const testMetadataProperty = (archivePath, propertyName, expectedValue) => {
|
||||
return imageStream.getFromFilePath(archivePath).then((image) => {
|
||||
m.chai.expect(image[propertyName]).to.deep.equal(expectedValue);
|
||||
|
||||
return imageStream.getImageMetadata(archivePath).then((metadata) => {
|
||||
m.chai.expect(metadata[propertyName]).to.deep.equal(expectedValue);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
describe('ImageStream: Metadata ZIP', function() {
|
||||
|
||||
this.timeout(10000);
|
||||
|
||||
describe('given an archive with an invalid `manifest.json`', function() {
|
||||
|
||||
tester.expectError(
|
||||
path.join(ZIP_PATH, 'rpi-invalid-manifest.zip'),
|
||||
'Invalid archive manifest.json');
|
||||
|
||||
describe('.getImageMetadata()', function() {
|
||||
|
||||
it('should be rejected with an error', function(done) {
|
||||
const image = path.join(ZIP_PATH, 'rpi-invalid-manifest.zip');
|
||||
|
||||
imageStream.getImageMetadata(image).catch((error) => {
|
||||
m.chai.expect(error).to.be.an.instanceof(Error);
|
||||
m.chai.expect(error.message).to.equal('Invalid archive manifest.json');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('given an archive with a `manifest.json`', function() {
|
||||
|
||||
const archive = path.join(ZIP_PATH, 'rpi-with-manifest.zip');
|
||||
|
||||
tester.extractFromFilePath(
|
||||
archive,
|
||||
path.join(IMAGES_PATH, 'raspberrypi.img'));
|
||||
|
||||
it('should read the manifest name property', function(done) {
|
||||
testMetadataProperty(archive, 'name', 'Raspberry Pi').asCallback(done);
|
||||
});
|
||||
|
||||
it('should read the manifest version property', function(done) {
|
||||
testMetadataProperty(archive, 'version', '1.0.0').asCallback(done);
|
||||
});
|
||||
|
||||
it('should read the manifest url property', function(done) {
|
||||
testMetadataProperty(archive, 'url', 'https://www.raspberrypi.org').asCallback(done);
|
||||
});
|
||||
|
||||
it('should read the manifest supportUrl property', function(done) {
|
||||
const expectedValue = 'https://www.raspberrypi.org/forums/';
|
||||
testMetadataProperty(archive, 'supportUrl', expectedValue).asCallback(done);
|
||||
});
|
||||
|
||||
it('should read the manifest releaseNotesUrl property', function(done) {
|
||||
const expectedValue = 'http://downloads.raspberrypi.org/raspbian/release_notes.txt';
|
||||
testMetadataProperty(archive, 'releaseNotesUrl', expectedValue).asCallback(done);
|
||||
});
|
||||
|
||||
it('should read the manifest checksumType property', function(done) {
|
||||
testMetadataProperty(archive, 'checksumType', 'md5').asCallback(done);
|
||||
});
|
||||
|
||||
it('should read the manifest checksum property', function(done) {
|
||||
testMetadataProperty(archive, 'checksum', 'add060b285d512f56c175b76b7ef1bee').asCallback(done);
|
||||
});
|
||||
|
||||
it('should read the manifest bytesToZeroOutFromTheBeginning property', function(done) {
|
||||
testMetadataProperty(archive, 'bytesToZeroOutFromTheBeginning', 512).asCallback(done);
|
||||
});
|
||||
|
||||
it('should read the manifest recommendedDriveSize property', function(done) {
|
||||
testMetadataProperty(archive, 'recommendedDriveSize', 4294967296).asCallback(done);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('given an archive with a `logo.svg`', function() {
|
||||
|
||||
const archive = path.join(ZIP_PATH, 'rpi-with-logo.zip');
|
||||
|
||||
const logo = [
|
||||
'<svg xmlns="http://www.w3.org/2000/svg">',
|
||||
' <text>Hello World</text>',
|
||||
'</svg>',
|
||||
''
|
||||
].join('\n');
|
||||
|
||||
it('should read the logo contents', function(done) {
|
||||
testMetadataProperty(archive, 'logo', logo).asCallback(done);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('given an archive with a bmap file', function() {
|
||||
|
||||
const archive = path.join(ZIP_PATH, 'rpi-with-bmap.zip');
|
||||
|
||||
const bmap = [
|
||||
'<?xml version="1.0" ?>',
|
||||
'<bmap version="1.3">',
|
||||
' <ImageSize> 36864 </ImageSize>',
|
||||
' <BlockSize> 4096 </BlockSize>',
|
||||
' <BlocksCount> 9 </BlocksCount>',
|
||||
' <MappedBlocksCount> 4 </MappedBlocksCount>',
|
||||
' <BmapFileSHA1> d90f372215cbbef8801caca7b1dd7e587b2142cc </BmapFileSHA1>',
|
||||
' <BlockMap>',
|
||||
' <Range sha1="193edb53bde599f58369f4e83a6c5d54b96819ce"> 0-1 </Range>',
|
||||
' <Range sha1="193edb53bde599f58369f4e83a6c5d54b96819ce"> 7-8 </Range>',
|
||||
' </BlockMap>',
|
||||
'</bmap>',
|
||||
''
|
||||
].join('\n');
|
||||
|
||||
it('should read the bmap contents', function(done) {
|
||||
testMetadataProperty(archive, 'bmap', bmap).asCallback(done);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('given an archive with instructions', function() {
|
||||
|
||||
const archive = path.join(ZIP_PATH, 'rpi-with-instructions.zip');
|
||||
|
||||
const instructions = [
|
||||
'# Raspberry Pi Next Steps',
|
||||
'',
|
||||
'Lorem ipsum dolor sit amet.',
|
||||
''
|
||||
].join('\n');
|
||||
|
||||
it('should read the instruction contents', function(done) {
|
||||
testMetadataProperty(archive, 'instructions', instructions).asCallback(done);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
Loading…
x
Reference in New Issue
Block a user