UNPKG

xo-vmdk-to-vhd

Version:
303 lines (302 loc) 11 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ensureArray = exports.ParsableFile = void 0; exports.parseOVAFile = parseOVAFile; exports.parseOVF = parseOVF; var _assert = _interopRequireDefault(require("assert")); var _find = _interopRequireDefault(require("lodash/find")); var _forEach = _interopRequireDefault(require("lodash/forEach")); var _pako = _interopRequireDefault(require("pako")); var _sum = _interopRequireDefault(require("lodash/sum")); var _xml2js = _interopRequireWildcard(require("xml2js")); var _ = require("."); var _util = require("./util"); function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); } function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; } function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } const MEMORY_UNIT_TO_FACTOR = { k: 1024, m: 1048576, g: 1073741824, t: 1099511627776 }; const RESOURCE_TYPE_TO_HANDLER = { 3: (data, { VirtualQuantity: nCpus }) => { data.nCpus = +nCpus; }, 4: (data, { AllocationUnits: unit, VirtualQuantity: quantity }) => { data.memory = quantity * allocationUnitsToFactor(unit); }, 10: ({ networks }, { AutomaticAllocation: enabled, Connection: name }) => { if (enabled) { networks.push(name); } }, 17: ({ disks }, { Address: address, AddressOnParent: position, Description: description = 'No description', ElementName: name, Caption: caption = name, HostResource: resource }) => { const diskId = resource.match(/^(?:ovf:)?\/disk\/(.+)$/); const disk = diskId && disks[diskId[1]]; if (position === undefined && address !== undefined) { let parsed = address.replace(/\s+/g, ''); if (parsed[0] === '{' && parsed[parsed.length - 1] === '}') { parsed = parsed.substring(1, parsed.length - 1); parsed = parsed.split(','); parsed = Object.fromEntries(parsed.map(couple => couple.split('='))); if ('target' in parsed) { position = +parsed.target; } } } if (position === undefined) { position = 0; } if (disk) { disk.descriptionLabel = description; disk.nameLabel = caption; disk.position = +position; } else { console.error(`No disk found: '${diskId}'.`); } } }; function parseTarHeader(header, stringDeserializer) { const fileName = stringDeserializer(header.slice(0, 100), 'ascii').split('\0')[0]; if (fileName.length === 0) { return null; } const sizeBuffer = header.slice(124, 124 + 12); let fileSize = 0; if (new Uint8Array(sizeBuffer)[0] === 128) { for (const byte of new Uint8Array(sizeBuffer.slice(1))) { fileSize *= 256; fileSize += byte; } } else { fileSize = parseInt(stringDeserializer(sizeBuffer.slice(0, 11), 'ascii'), 8); } return { fileName, fileSize }; } class ParsableFile { slice(start, end) {} async read() {} } exports.ParsableFile = ParsableFile; const ensureArray = value => { if (value === undefined) { return []; } return Array.isArray(value) ? value : [value]; }; exports.ensureArray = ensureArray; const allocationUnitsToFactor = unit => { const intValue = unit.match(/\^([0-9]+)$/); return intValue != null ? Math.pow(2, intValue[1]) : MEMORY_UNIT_TO_FACTOR[unit.charAt(0).toLowerCase()]; }; const cleanDisks = disks => { const usedPositions = new Set(); let nextPosition = Object.keys(disks).length; for (const diskId in disks) { let position = disks[diskId].position; if (position == null) { console.error(`No position specified for '${diskId}'.`); delete disks[diskId]; } else { if (usedPositions.has(position)) { console.warn(`There is at least two disks with position ${position}, we're changing the second one to ${nextPosition}`); disks[diskId].position = position = nextPosition; nextPosition++; } usedPositions.add(position); } } }; async function parseOVF(fileFragment, stringDeserializer) { const xmlString = stringDeserializer(await fileFragment.read(), 'utf-8'); return new Promise((resolve, reject) => _xml2js.default.parseString(xmlString, { mergeAttrs: true, explicitArray: false, tagNameProcessors: [_xml2js.processors.stripPrefix], attrNameProcessors: [_xml2js.processors.stripPrefix] }, (err, res) => { if (err) { reject(err); return; } const { Envelope: { DiskSection: { Disk: disks }, References: { File: files }, VirtualSystem: system } } = res; const data = { disks: {}, networks: [] }; const hardware = system.VirtualHardwareSection; data.nameLabel = hardware.System.VirtualSystemIdentifier; data.descriptionLabel = system.AnnotationSection && system.AnnotationSection.Annotation || system.OperatingSystemSection && system.OperatingSystemSection.Description; (0, _forEach.default)(ensureArray(disks), disk => { const file = (0, _find.default)(ensureArray(files), file => file.id === disk.fileRef); const unit = disk.capacityAllocationUnits; data.disks[disk.diskId] = { capacity: disk.capacity * (unit && allocationUnitsToFactor(unit) || 1), path: file && file.href, compression: file && file.compression }; }); const handleItem = item => { const handler = RESOURCE_TYPE_TO_HANDLER[item.ResourceType]; if (!handler) { return; } handler(data, item); }; (0, _forEach.default)(ensureArray(hardware.Item), handleItem); (0, _forEach.default)(ensureArray(hardware.StorageItem), handleItem); (0, _forEach.default)(ensureArray(hardware.EthernetPortItem), handleItem); cleanDisks(data.disks); resolve(data); })); } const GZIP_CHUNK_SIZE = 4 * 1024 * 1024; async function parseGzipFromEnd(start, end, fileSlice, header) { const l = end - start; const chunks = []; let savedSize = 0; let currentDeflatedPos = 0; const inflate = new _pako.default.Inflate(); while (currentDeflatedPos < header.fileSize) { const slice = fileSlice.slice(currentDeflatedPos, currentDeflatedPos + GZIP_CHUNK_SIZE); const compressed = await slice.read(); inflate.push(compressed, _pako.default.Z_SYNC_FLUSH); const chunk = inflate.result.slice(); chunks.push({ pos: currentDeflatedPos, buffer: chunk }); savedSize += chunk.length; if (savedSize - chunks[0].buffer.length >= l) { savedSize -= chunks[0].buffer.length; chunks.shift(); } currentDeflatedPos += GZIP_CHUNK_SIZE; } let resultBuffer = new Uint8Array((0, _sum.default)(chunks.map(c => c.buffer.length))); let index = 0; chunks.forEach(c => { resultBuffer.set(c.buffer, index); index += c.buffer.length; }); resultBuffer = resultBuffer.slice(start, end); return resultBuffer.buffer; } async function parseOVAFile(parsableFile, stringDeserializer, skipVmdk = false) { let offset = 0; const HEADER_SIZE = 512; let data = { tables: {} }; while (true) { const header = parseTarHeader(await parsableFile.slice(offset, offset + HEADER_SIZE).read(), stringDeserializer); offset += HEADER_SIZE; if (header === null) { break; } const fileSlice = parsableFile.slice(offset, offset + header.fileSize); fileSlice.fileName = header.fileName; if (!(header.fileName.startsWith('PaxHeader/') || header.fileName.startsWith('.'))) { if (header.fileName.toLowerCase().endsWith('.ovf')) { const res = await parseOVF(fileSlice, stringDeserializer); data = { ...data, ...res }; } if (!skipVmdk && header.fileName.toLowerCase().endsWith('.vmdk')) { const readFile = async (start, end) => fileSlice.slice(start, end).read(); readFile.fileName = header.fileName; data.tables[header.fileName] = (0, _util.suppressUnhandledRejection)((0, _.readVmdkGrainTable)(readFile)); } } if (!skipVmdk && header.fileName.toLowerCase().endsWith('.vmdk.gz')) { let forwardsInflater = new _pako.default.Inflate(); const readFile = async (start, end) => { async function parseGzipFromStart(start, end, fileSlice) { const chunks = []; const resultStart = () => forwardsInflater.strm.total_out - forwardsInflater.result.length; if (forwardsInflater.result != null && start < resultStart()) { forwardsInflater = new _pako.default.Inflate(); } let isLast = false; while (true) { if (forwardsInflater.strm.total_out > start) { let chunk = forwardsInflater.result; if (resultStart() < start) { chunk = chunk.slice(start - resultStart()); } if (forwardsInflater.strm.total_out > end) { chunk = chunk.slice(0, -(forwardsInflater.strm.total_out - end)); isLast = true; } chunks.push(chunk); } if (isLast) { break; } const slice = fileSlice.slice(forwardsInflater.strm.total_in, forwardsInflater.strm.total_in + GZIP_CHUNK_SIZE); forwardsInflater.push(await slice.read(), _pako.default.Z_SYNC_FLUSH); } const resultBuffer = new Uint8Array((0, _sum.default)(chunks.map(c => c.length))); let index = 0; chunks.forEach(c => { resultBuffer.set(c, index); index += c.length; }); _assert.default.strictEqual(resultBuffer.buffer.byteLength, end - start); return resultBuffer.buffer; } if (start === end) { return new Uint8Array(0); } if (start >= 0 && end >= 0) { return parseGzipFromStart(start, end, fileSlice); } else if (start < 0 && end < 0) { return parseGzipFromEnd(start, end, fileSlice, header); } }; readFile.fileName = header.fileName; data.tables[header.fileName] = (0, _util.suppressUnhandledRejection)((0, _.readVmdkGrainTable)(readFile)); } offset += Math.ceil(header.fileSize / 512) * 512; } return data; } //# sourceMappingURL=ova-read.js.map