UNPKG

azure-cli

Version:

Microsoft Azure Cross Platform Command Line tool

518 lines (468 loc) 18.6 kB
// // Copyright (c) Microsoft and contributors. All rights reserved. // // 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. // /** * Common code to work OS or Disk image that implements vm disk * and vm image* commands */ var path = require('path'); var util = require('util'); var async = require('async'); var _ = require('underscore'); var utils = require('../../../util/utils'); var blobUtils = require('../../../util/blobUtils'); var deleteImage = require('./deleteImage'); var uploadVMImage = require('./upload/uploadVMImage'); var $ = utils.getLocaleString; var profile = require('../../../util/profile'); var DISK = exports.DISK = 0; /*jshint unused:false*/ var OSIMAGE = exports.OSIMAGE = 1; var whatAPI = ['Disk', 'OSImage']; var whatLog = ['disk image', 'VM image']; var whatLogs = ['disk images', 'VM images']; var VMClient = require('../vm/vmclient'); exports.show = function(what, cli) { return function(name, options, callback) { var computeManagementClient = createComputeManagementClient(cli, options); var logger = cli.output; var progress = cli.interaction.progress('Fetching ' + whatLog[what]); if (what === DISK) { // show Data Disk computeManagementClient.virtualMachineDisks.getDisk(name, function(error, response) { progress.end(); if (!error) { delete response['@']; // skip @ xmlns and @ xmlns:i delete response['requestId']; // skip requestId delete response['statusCode']; // statusCode if (logger.format().json) { logger.json(response); } else { utils.logLineFormat(response, logger.data); } } return callback(error); }); } else { // This approach has been inspired from what's being done in the "Get-AzureVMImage -ImageName" // Azure PowerShell command. See: http://git.io/03o3ag var lookupError = null; async.parallel([ function(callback) { computeManagementClient.virtualMachineVMImages.list(function(error, response) { var theImage = null; if (!error) { theImage = _.find(response.vMImages, function(img) { return name.toUpperCase() === img.name.toUpperCase(); }); } callback(error, theImage); }); }, function(callback) { // show OS Image computeManagementClient.virtualMachineOSImages.get(name, function(error, response) { var theImage = null; if (!error) { theImage = response; } else { // if this is a ResourceNotFound error then we save the error // info in "lookupError" and continue execution if (error.code === 'ResourceNotFound') { lookupError = error; error = null; } } callback(error, theImage); }); } ], function(error, results) { progress.end(); if (!error) { // we expect "results" to be an array with 2 elements; if both are // undefined then the image name supplied is invalid if (results.length !== 2 || (!results[0] && !results[1])) { error = lookupError; } } if (!error) { results.filter(function(theImage) { return !!theImage; }).forEach(function(theImage) { if (theImage['location']) { theImage['location'] = theImage['location'].split(';'); } delete theImage['@']; // skip @ xmlns and @ xmlns:i delete theImage['requestId']; // skip requestId delete theImage['statusCode']; // statusCode if (logger.format().json) { logger.json(theImage); } else { utils.logLineFormat(theImage, logger.data); } }); } return callback(error); }); } }; }; exports.list = function(what, cli) { function list(name, options, callback) { var logger = cli.output; var computeManagementClient = createComputeManagementClient(cli, options); var progress = cli.interaction.progress('Fetching ' + whatLogs[what]); var images = []; if (what === DISK) { if (name || options.dnsName) { // list data disks for a specific VM name and/or dns name listDisks(cli, { subscription: options.subscription, name: name, dnsPrefix: utils.getDnsPrefix(options.dnsName, true) }, callback); return; } else { // list all Data Disks computeManagementClient.virtualMachineDisks.listDisks(function(error, response) { progress.end(); if (!error) { if (response.disks.length > 0) { logger.table(response.disks, function(row, item) { row.cell('Name', item.name); if (what === DISK) { row.cell('OS', item.operatingSystemType || ''); } else { if (item.category) { row.cell('Category', item.category); } row.cell('OS', item.operatingSystemType || ''); } }); } else { if (logger.format().json) { logger.json([]); } else { logger.info('No ' + whatLogs[what] + ' found'); } } } callback(error); }); } } else { // list all Images async.parallel([ function(callback) { computeManagementClient.virtualMachineOSImages.list(function(error, response) { callback(error, (error) ? null : response.images); }); }, function(callback) { computeManagementClient.virtualMachineVMImages.list(function(error, response) { callback(error, (error) ? null : response.vMImages); }); } ], function(error, results) { if (!error) { progress.end(); // splice all images into "images" results.forEach(function(result) { images = images.concat(result); }); if (images.length > 0) { logger.table(images, function(row, item) { row.cell('Name', item.name); if (what === DISK) { row.cell('OS', item.operatingSystemType || ''); } else { if (item.category) { row.cell('Category', item.category); } row.cell('OS', item.operatingSystemType || item.oSDiskConfiguration.operatingSystem); row.cell('Publisher', item.publisherName); } }); } else { if (logger.format().json) { logger.json([]); } else { logger.info('No ' + whatLogs[what] + ' found'); } } } callback(error); }); } } // return 2 or 3-arg version of the function return what === DISK ? list : function(options, callback) { return list(undefined, options, callback); }; }; exports.delete = function(what, cli) { return function(diskName, deleteOptions, callback) { var computeManagementClient = createComputeManagementClient(cli, deleteOptions); var storageClient = createStorageClient(cli, deleteOptions); var logger = cli.output; deleteImage.deleteImage(whatAPI[what], ['Disk', 'Image'][what], logger, computeManagementClient, storageClient, diskName, deleteOptions, cli.interaction.progress, callback); }; }; exports.create = function(what, cli) { return function(name, sourcePath, options, callback) { var computeManagementClient = createComputeManagementClient(cli, options); var managementClient = createManagementClient(cli, options); var storageClient = createStorageClient(cli, options); var logger = cli.output; var os = (undefined); if (typeof options.os === 'string') { var los = options.os.trim().toLowerCase(); os = los[0].toUpperCase() + los.slice(1); // start with capital letter } if (os && os !== 'Windows' && os !== 'Linux' && os !== 'None') { callback('--os [type] must specify linux, windows or none'); } if (what === DISK) { // @"^[a-zA-Z_][^\\\/\:\*\?\""\<\>\|\`\'\^%\#]*(?<![\.\s])$" in C# syntax if (!name.match(/^[a-zA-Z_][^\\\/\:\*\?\"<\>\|\`\'\^%\#]*$/) || name.slice(-1).match(/[\.\s]/)) { callback('Invalid image name. Disk image name should start with a Latin letter or underscore (_), cannot contain any of the following characters:\n\\/:*?\"<>|`\'%#^\nIt cannot end with a period (.) or space character.'); } } else { if (os !== 'Windows' && os !== 'Linux') { callback('--os <type> must specify linux or windows'); } // @"^[A-Za-z0-9\-\.]{1,512}(?<!\-)$" in C# syntax if (!name.match(/^[A-Za-z0-9\-\.]{0,511}[A-Za-z0-9\.]$/)) { callback('Invalid image name. OS image name can only contain Latin letters, digits, \'.\' and \'-\'. It cannot end with \'-\' or be longer than 512 chars.'); } } var genBlobUrl = ''; var force = options.forceOverwrite; var blobUrl = options.blobUrl; var location = options.location; var affinityGroup = options.affinityGroup; if (sourcePath) { if (!blobUrl && !location && !affinityGroup) { logger.error('--blob-url, --location, or --affinity-group must be specified'); logger.help('following commands show available locations and affinity groups:'); logger.help(' azure vm location list'); logger.help(' azure account affinity-group list'); callback(' '); } } else { // When source-path is not specified, the user is attempting to register an // already uploaded disk or OS blob. In that case we need the blob-url. if (!blobUrl) { callback('--blob-url must be specified if sourcePath is not specified'); } } if (blobUrl) { if (blobUrl[0] === '/') { // With partial urls, we need to know location/affinity group of the storage account. if (!location && !affinityGroup) { logger.error('location or affinity group is required if no full URL is specified'); logger.help('following commands show available locations and affinity groups:'); logger.help(' azure vm location list'); logger.help(' azure account affinity-group list'); callback('--location, or --affinity-group must be specified'); } } else { if (location) { logger.warn('--location option will be ignored'); } if (affinityGroup) { logger.warn('--affinity-group option will be ignored'); } } } var parameters = { imageOptions: { name: name, label: options.label || name, isPremium: false, operatingSystemType: '', showInGui: true }, verbose: cli.verbose || logger.format().level === 'verbose' || logger.format().level === 'silly', skipMd5: options.md5Skip, force: force, vhd: true, threads: options.parallel, parentBlob: options.baseVhd, exitWithError: callback, logger: logger }; if (options.os) { var hasOS = os !== 'None'; parameters.imageOptions.hasOperatingSystem = hasOS; if (hasOS && os) { parameters.imageOptions.operatingSystemType = os; } } logger.silly('Options: ', parameters.imageOptions); if (location) { logger.verbose('Resolving the location \'' + location + '\''); utils.resolveLocationName(managementClient, location, function(error, resolvedLocation) { if (!error) { location = resolvedLocation.name; logger.verbose('Location resolved to \'' + location + '\''); getBlobNameAndUpload(); } else { callback(error); } }); } else { getBlobNameAndUpload(); } function getBlobNameAndUpload() { blobUtils.getBlobName(cli, storageClient, location, affinityGroup, path.basename(sourcePath), blobUrl, ['/disks/', '/vm-images/'][what], sourcePath, '', function(error, url) { if (error) { logger.error('Unable to retrieve storage account.'); callback(error); } else { genBlobUrl = url; logger.verbose('Blob url: ' + genBlobUrl); upload(); } }); } function upload() { // uploading if (sourcePath) { if (/^https?\:\/\//i.test(sourcePath)) { logger.verbose('Copying blob from ' + sourcePath + ' to ' + genBlobUrl); if (options.md5Skip || options.parallel !== 96 || options.baseVhd) { logger.warn('--md5-skip, --parallel and/or --base-vhd options will be ignored'); } if (!options.forceOverwrite) { logger.warn('Any existing blob will be overwritten' + (blobUrl ? ' at ' + blobUrl : '')); } var progress = cli.interaction.progress(''); uploadVMImage.copyBlobFromIaasClient(storageClient, sourcePath, options.sourceKey, genBlobUrl, parameters, function(error, blob, response, newDestUri) { progress.end(); logger.silly(util.inspect(response, null, null, true)); if (!error) { var status = blob.copy.status; (status === 'success' ? logger.silly : logger.warn)('Status : ' + status); createImage(newDestUri); } else { logger.error('Couldn\'t copy blob ' + sourcePath + ' to ' + newDestUri); callback(error); } }); return; } uploadVMImage.uploadPageBlobFromIaasClient(genBlobUrl, storageClient, sourcePath, parameters, function(error, finalBlobUrl, alreadyExisted) { if (error && !error.isSuccessful) { // do not delete incomplete blob logger.error('Couldn\'t upload blob ' + genBlobUrl); callback(error); } if (!error) { // final callback logger.info(finalBlobUrl + (alreadyExisted ? ' was already uploaded' : ' was uploaded successfully')); createImage(finalBlobUrl); } }); } else { // not uploading createImage(); } } function createImage(finalBlobUrl) { finalBlobUrl = finalBlobUrl || genBlobUrl; var normUrl = blobUtils.normalizeBlobUri(finalBlobUrl, true); if (normUrl !== genBlobUrl) { logger.verbose('Creating image from ' + normUrl); } parameters.imageOptions.mediaLinkUri = normUrl; // example: http://example.blob.core.windows.net/disks/mydisk.vhd if (what === DISK) { computeManagementClient.virtualMachineDisks.createDisk(parameters.imageOptions, function(error) { callback(error); }); } else { computeManagementClient.virtualMachineOSImages.create(parameters.imageOptions, function(error) { callback(error); }); } } }; }; function listDisks(cli, options, callback) { var logger = cli.output; var computeManagementClient = createComputeManagementClient(cli, options); var vmClient = new VMClient(cli, profile.current.getSubscription(options.subscription).Id); vmClient.getDeployments(options, function(error, deployments) { if (error) { return callback(error); } else { var found = null; var foundDisks = null; for (var i = 0; i < deployments.length; i++) { var roles = deployments[i].deploy.roles; if (roles) { for (var j = 0; j < roles.length; j++) { if (roles[j].roleType === 'PersistentVMRole' && (!options.name || roles[j].roleName === options.name)) { if (found) { // found duplicates, emit error callback(new Error($('VM name is not unique'))); } found = deployments[i]; foundDisks = [roles[j].oSVirtualHardDisk]; if (roles[j].dataVirtualHardDisks) { foundDisks = foundDisks.concat(roles[j].dataVirtualHardDisks); } } } } } // got unique role under a deployment and service, list the disks if (found) { var osDiskName = foundDisks[0].name; logger.verbose('Getting info for OS disk ' + osDiskName); var progress = cli.interaction.progress($('Getting VM disks')); computeManagementClient.virtualMachineDisks.getDisk(osDiskName, function(error, response) { progress.end(); foundDisks[0].logicalDiskSizeInGB = error ? 'Error' : response.logicalSizeInGB; logger.table(foundDisks, function(row, item) { row.cell('Lun', (item === foundDisks[0]) ? '' : (item.logicalUnitNumber || 0)); row.cell('Size(GB)', item.logicalDiskSizeInGB); var mediaLink = item.mediaLink.split('/'); row.cell('Blob-Name', mediaLink[mediaLink.length - 1]); row.cell('OS', item.operatingSystem || ''); }); callback(error); }); } else { logger.warn($('No VMs found')); callback(); } } }); } function createComputeManagementClient(cli, options) { return utils.createComputeClient(profile.current.getSubscription(options.subscription)); } function createManagementClient(cli, options) { return utils.createManagementClient(profile.current.getSubscription(options.subscription)); } function createStorageClient(cli, options) { return utils.createStorageClient(profile.current.getSubscription(options.subscription)); }