UNPKG

cloud-blender

Version:

A high level library for cloud compute operations

1,058 lines (908 loc) 51.9 kB
/*jslint node: true */ 'use strict'; var request = require('request'), underscore = require('underscore'), xMsVersion = '2015-05-01-preview', rgMsVersion = '2016-02-01', templateMsVersion = '2016-02-01', deleteMsVersion = '2016-03-30', AzureError = require('./azure-error.js'), Authentication = {}, adal = require('adal-node'), tunnelingProxyURL, interval = 11000, azure = require('azure-storage'), CBError = require('./cb-error'), CBErrorCodes = require('./cb-error-codes'), foldersStructure = 'Microsoft.Compute/Images/images/'; module.exports = (function () { function checkReachedFinishedState(settings, code, maxNumOfIterations, callback) { if (maxNumOfIterations === 0) { return callback(new CBError('can\'t verify if operation finished')); } azureRequest('GET', settings, function (err, taskResult) { if (err) { return callback(new CBError(err.message, err, 1, true, CBErrorCodes.UNSPECIFIED_ERROR)); } if (taskResult.statuses[1].code === code) { return callback(null, taskResult); } else { setTimeout(checkReachedFinishedState, 4000, settings, code, maxNumOfIterations - 1, callback); } }); } function getAuthenticationToken(settings, callback) { if (Authentication.expiresOn && (new Date().valueOf()) + (1000 * 60 * 10) < Authentication.expiresOn) { callback(null, Authentication.token); return; } else { var AuthenticationContext = adal.AuthenticationContext, tenantID = settings.tenantId, clientID = settings.clientId, resource = "https://management.azure.com/", authURL = "https://login.windows.net/" + tenantID, secret = settings.secret, context = new AuthenticationContext(authURL); context.acquireTokenWithClientCredentials(resource, clientID, secret, function (err, tokenResponse) { if (err) { callback(new AzureError('err generate token-' + err)); return; } else { Authentication.token = tokenResponse.accessToken; Authentication.expiresOn = tokenResponse.expiresOn.valueOf(); callback(null, tokenResponse.accessToken); } }); } } function checkTemplateStatus(settings, templateResult, maxNumOfIterations, callback) { if (maxNumOfIterations === 0) { var correlationId = 0; if (templateResult && templateResult.properties && templateResult.properties.correlationId){ correlationId = templateResult.properties.correlationId; } return callback(new CBError('can\'t check if template id: '+ templateResult.id + ' status is finished. correlation id: '+ correlationId, 'finished number of iteratation, still template deployment is running', 1, false, CBErrorCodes.AZURE_ARM_DEPLOYMENT_ERROR)); } var getTemplateInfo = { regionContext: settings.regionContext, url: 'https://management.azure.com/' + templateResult.id + '?api-version=' + templateMsVersion, successCode: 200 }; azureRequest('GET', getTemplateInfo, function (err, templateInfo) { if (err) { return callback(new CBError(err.message, err, 1, true, CBErrorCodes.AZURE_ARM_DEPLOYMENT_ERROR)); } if (templateInfo.properties.provisioningState === 'Succeeded' || templateInfo.properties.provisioningState === 'Ready') { return callback(null, templateInfo); } else if (templateInfo.properties.provisioningState === 'Failed' || templateInfo.properties.provisioningState === 'Canceled') { return callback(new CBError(templateInfo.properties.error.message, templateInfo.properties.error, templateInfo.properties.error.code, false, CBErrorCodes.AZURE_ARM_DEPLOYMENT_ERROR)); } else { setTimeout(checkTemplateStatus, 4000, settings, templateResult, maxNumOfIterations - 1, callback); } }); } function checkDeleteStatus(settings, deleteResult, maxNumOfIterations, callback) { if (maxNumOfIterations === 0) { return callback(new CBError('can\'t check if vm is deleted', 'finished number of iteratation, the vm is not deleted still', 1, true)); } var getDeleteResult = { regionContext: settings.regionContext, url: 'https://management.azure.com/' + deleteResult.id + '?api-version=' + deleteMsVersion, successCode: 404 }; azureRequest('GET', getDeleteResult, function (err, deleteStatus) { if (err) { setTimeout(checkDeleteStatus, 4000, settings, deleteResult, maxNumOfIterations - 1, callback); return; } return callback(null, deleteStatus); }); } function azureRetryRequest(method, reqSettings, pollingCount, interval, callback) { var Settings, Authorization; getAuthenticationToken(reqSettings.regionContext, function (err, result) { if (err) { callback(new AzureError(err)); return; } Authorization = result; Settings = { method: method, url: reqSettings.url, headers: { Authorization: 'Bearer ' + Authorization, 'Content-Type': 'application/json' }, body: JSON.stringify(reqSettings.jsonBody) }; request(Settings, function (err, response, body) { if (err || !response) { callback(new AzureError('response is not valid - ' + err)); return; } // in case of retry code and there we didn't reached to max polling if (pollingCount > 0 && ((response.statusCode === 503) || (response.statusCode === 429) || (underscore.contains(reqSettings.retryCodes, response.statusCode) === true))) { setTimeout(azureRetryRequest, interval, method, reqSettings, pollingCount - 1, interval, callback); return; } if ((reqSettings.successCode === response.statusCode) || (underscore.contains(reqSettings.successCode, response.statusCode) === true)) { if (method.toUpperCase() === 'DELETE' || method.toUpperCase() === 'POST') { callback(null, response.statusCode); return; } else { callback(null, JSON.parse(response.body)); return; } } // in case we got an error code which is not success/retry callback(new AzureError('res.statusCode - ' + response.statusCode + ' ' + response.body)); }); }); } function azureRequest(method, reqSettings, callback) { var Settings, Authorization; getAuthenticationToken(reqSettings.regionContext, function (err, result) { if (err) { callback(new AzureError(err)); return; } Authorization = result; Settings = { method: method, url: reqSettings.url, headers: { Authorization: 'Bearer ' + Authorization, 'Content-Type': 'application/json' }, body: JSON.stringify(reqSettings.jsonBody) }; request(Settings, function (err, response, body) { if (!response) { callback(new AzureError('response is not valid-' + err)); return; } if (err || response.statusCode !== reqSettings.successCode) { callback(err || new AzureError('res.statusCode-' + response.statusCode + ' ' + response.body)); return; } if (method.toUpperCase() === 'DELETE' || method.toUpperCase() === 'POST') { callback(null, reqSettings.successCode); return; } else { callback(null, JSON.parse(response.body)); return; } }); }); } function getBlobsList(regionContext, container, imagesArray, foldersPrefix, callback) { var blobSvc = azure.createBlobService(regionContext.storageAccount, regionContext.storageAccessKey), blobFullName, images = []; if(imagesArray.length > 0) { underscore.each(imagesArray, function (image, index) { blobSvc.listBlobsSegmentedWithPrefix(container, foldersPrefix + image, null, function (error, result, response) { if (error) { return callback(error); } if (result && (result.entries.length === 1)) { result.entries[0].Name = result.entries[0].name; result.entries[0].Properties = {}; result.entries[0].Properties['Last-Modified'] = result.entries[0].lastModified; images.push(result.entries[0]); } if (index === imagesArray.length - 1) { callback(null, images); } }); }); } else{ blobSvc.listBlobsSegmentedWithPrefix(container, foldersPrefix, null, function(error, result) { if (error){ return callback(error); } if(result.continuationToken != null) { aggregateBlobs(result.entries, regionContext, container, foldersPrefix, result.continuationToken, function (error, results){ if (error){ return callback(error); } fillImagesList(results, images,callback); }); } else{ fillImagesList(result.entries, images,callback); } }); } } function aggregateBlobs(entries, regionContext, container, foldersPrefix, continuationToken, callback) { var blobSvc = azure.createBlobService(regionContext.storageAccount, regionContext.storageAccessKey); blobSvc.listBlobsSegmentedWithPrefix (container, foldersPrefix, continuationToken, function (error, result){ if(error){ callback(error, null); } entries = entries.concat(result.entries); if(result.continuationToken !== null) { aggregateBlobs(entries, regionContext, container, foldersPrefix, result.continuationToken, callback); } else{ callback(null, entries); } } ); } function fillImagesList(entries, images, callback){ underscore.each(entries, function(image){ image.Name = image.name; image.Properties = {}; image.Properties['Last-Modified'] = image.lastModified; images.push(image); }); callback(null, images); } function getBlobFullName(regionContext, container, BlobPrefix, foldersPrefix, numberOfRetry, callback) { var blobSvc = azure.createBlobService(regionContext.storageAccount, regionContext.storageAccessKey), blobFullName; blobSvc.listBlobsSegmentedWithPrefix(container, foldersPrefix + BlobPrefix, null, function (error, result, response) { if (error) { return callback(error); } if (result && (result.entries.length === 1)) { blobFullName = result.entries[0].name; return callback(null, blobFullName.substr(foldersPrefix.length)); } else if (result && (result.entries.length > 1)) { var newArr = result.entries.filter(function (entry) { return (entry.blobType === 'PageBlob'); }); if (newArr.length === 1) { blobFullName = newArr[0].name; return callback(null, blobFullName.substr(foldersPrefix.length)); } else { return callback(new AzureError('looking for: ' + foldersPrefix + BlobPrefix + ' in container: ' + container + '. found ' + result.entries.length + ' images by its prefix: ' + JSON.stringify(result.entries))); } } else { if (numberOfRetry === 0) { return callback(new AzureError('looking for: ' + foldersPrefix + BlobPrefix + ' in container: ' + container + '. can\'t find image by its prefix')); } else { setTimeout(getBlobFullName, 2000, regionContext, container, BlobPrefix, foldersPrefix, numberOfRetry - 1, callback); } } }); } function deleteBlob(regionContext, container, Blob, pollingCount, interval, callback) { var blobSvc = azure.createBlobService(regionContext.storageAccount, regionContext.storageAccessKey); blobSvc.deleteBlob(container, Blob, function (error, response) { if (!response) { callback(new AzureError('delete blob response is not valid-' + error)); return; } // in case of retry code and there we didn't reached to max polling if (pollingCount > 0 && response.statusCode === 412) { setTimeout(deleteBlob, interval, regionContext, container, Blob, pollingCount - 1, interval, callback); } // in case we got an error code which is not success/retry if ((pollingCount === 0 && response.statusCode === 412) || (response.statusCode !== 412 && response.statusCode !== 202)) { callback(error || new AzureError('error delete blob res.statusCode-' + response.statusCode + ' ' + response.body)); return; } if (response.statusCode === 202) { callback(null, response.statusCode); return; } }); } function addNodeIp(partition, id, ip, callback) { var tableService = azure.createTableService(); var entGen = azure.TableUtilities.entityGenerator, entity = {}; entity.PartitionKey = entGen.String(partition); entity.RowKey = entGen.String('_' + id); entity.ip = entGen.String(ip); tableService.insertEntity('nodesIps', entity, function (error, result, response) { if (error) { callback(error); return; } callback(null, true); }); } function getVmIp(settings, vmName, pollingCount, interval, callback) { that.listNodes(settings, function (err, res) { var ip, vm; if (underscore.isEmpty(res) || underscore.isEmpty(res.nodes)) { if (pollingCount > 0) { setTimeout(getVmIp, interval, settings, vmName, pollingCount - 1, interval, callback); } else { callback(new AzureError('failed to find public ip for ' + vmName)); } return; } vm = underscore.filter(res.nodes, function (node) { return node.id === vmName; }); if (vm && vm[0] && vm[0].addresses) { ip = vm[0].addresses[1]; } if (ip) { callback(null, ip); return; } if (pollingCount > 0) { setTimeout(getVmIp, interval, settings, vmName, pollingCount - 1, interval, callback); return; } callback(new AzureError('VM ip to-' + vmName + ' is missing')); }); } function deleteNetworkInterface(settings, callback) { var settingsNetwork = { regionContext: settings.regionContext, url: 'https://management.azure.com/subscriptions/' + settings.regionContext.subscriptionId + '/resourceGroups/' + settings.regionContext.groupId + '/providers/Microsoft.Network/networkInterfaces?api-version=' + xMsVersion, successCode: 200 }, settingsVM = { regionContext: settings.regionContext, url: 'https://management.azure.com/subscriptions/' + settings.regionContext.subscriptionId + '/resourceGroups/' + settings.regionContext.groupId + '/providers/Microsoft.Compute/virtualMachines?api-version=' + xMsVersion, successCode: 200 }, vmNetworks, deleteINterval = (1000 * 60 * 10), settingsDelNetwork, errors = [], index = 0; azureRequest('GET', settingsNetwork, function (err, resNetwork) { if (err) { callback([err]); return; } azureRequest('GET', settingsVM, function (err, resVm) { if (err) { callback([err]); return; } vmNetworks = underscore.map(resVm.value, function (vm) { return vm.properties.networkProfile.networkInterfaces[0].id; }); if (resNetwork.value.length === 0) { callback([]); return; } underscore.each(resNetwork.value, function (network) { // all the network interface without VM and which were not created in the last 10 minutes if (vmNetworks.indexOf(network.id) === -1 && (!network.tags || !network.tags.createTime || (parseInt(network.tags.createTime) + deleteINterval) < new Date().valueOf())) { console.log('network interface-' + network.id + 'on region-' + settings.regionContext.cloudRegion + ' will be deleted'); settingsDelNetwork = { regionContext: settings.regionContext, url: 'https://management.azure.com/subscriptions/' + settings.regionContext.subscriptionId + '/resourceGroups/' + settings.regionContext.groupId + '/providers/Microsoft.Network/networkInterfaces/' + network.name + '?api-version=' + xMsVersion, successCode: 202 }; azureRequest('DELETE', settingsDelNetwork, function (err, resVm) { index += 1; if (err) { errors.push(err); } if (index === resNetwork.value.length) { callback(errors); return; } }); } else { index += 1; if (index === resNetwork.value.length) { callback(errors); return; } } }); }); }); } function deleteIp(settings, callback) { var settingsIps = { regionContext: settings.regionContext, url: 'https://management.azure.com/subscriptions/' + settings.regionContext.subscriptionId + '/resourceGroups/' + settings.regionContext.groupId + '/providers/Microsoft.Network/publicIPAddresses?api-version=' + xMsVersion, successCode: 200 }, settingsNetwork = { regionContext: settings.regionContext, url: 'https://management.azure.com/subscriptions/' + settings.regionContext.subscriptionId + '/resourceGroups/' + settings.regionContext.groupId + '/providers/Microsoft.Network/networkInterfaces?api-version=' + xMsVersion, successCode: 200 }, networkIps, deleteINterval = (1000 * 60 * 10), settingsDelIp, index = 0, errors = []; azureRequest('GET', settingsIps, function (err, resIps) { if (err) { callback([err]); return; } azureRequest('GET', settingsNetwork, function (err, resNetwork) { if (err) { callback([err]); return; } networkIps = underscore.map(resNetwork.value, function (network) { return network.properties.ipConfigurations[0].properties.publicIPAddress.id; }); if (resIps.value.length === 0) { callback([]); return; } underscore.each(resIps.value, function (ip) { // all the network interface without VM and which were not created in the last 10 minutes if (networkIps.indexOf(ip.id) === -1 && (!ip.tags || !ip.tags.createTime || (parseInt(ip.tags.createTime) + deleteINterval) < new Date().valueOf())) { console.log('public IP-' + ip.id + 'on region-' + settings.regionContext.cloudRegion + ' will be deleted'); settingsDelIp = { regionContext: settings.regionContext, url: 'https://management.azure.com/subscriptions/' + settings.regionContext.subscriptionId + '/resourceGroups/' + settings.regionContext.groupId + '/providers/Microsoft.Network/publicIPAddresses/' + ip.name + '?api-version=' + xMsVersion, successCode: 202 }; azureRequest('DELETE', settingsDelIp, function (err, resVm) { index += 1; if (err) { errors.push(err); } if (index === resIps.value.length) { callback(errors); return; } }); } else { index += 1; if (index === resIps.value.length) { callback(errors); return; } } }); }); }); } function deleteDiscs(settings, callback) { var settingsVM = { regionContext: settings.regionContext, url: 'https://management.azure.com/subscriptions/' + settings.regionContext.subscriptionId + '/resourceGroups/' + settings.regionContext.groupId + '/providers/Microsoft.Compute/virtualMachines?api-version=' + xMsVersion, successCode: 200 }, deleteINterval = (1000 * 60 * 10), VMdiscs, index = 0, errors = []; azureRequest('GET', settingsVM, function (err, resVm) { if (err) { callback([err]); return; } getBlobsList(settings.regionContext, 'vhds', function (err, resBlob) { VMdiscs = underscore.map(resVm.value, function (Vm) { return Vm.properties.storageProfile.osDisk.vhd.uri.substring(Vm.properties.storageProfile.osDisk.vhd.uri.lastIndexOf('/') + 1); }); if (!resBlob) { callback([]); return; } if (resBlob.Name) { resBlob = [resBlob]; } underscore.each(resBlob, function (blob) { if (VMdiscs.indexOf(blob.Name) === -1) { console.log('disc-' + blob.Name + 'on region-' + settings.regionContext.cloudRegion + ' will be deleted'); deleteBlob(settings.regionContext, 'vhds', blob.Name, 3, interval, function (err, resDelete) { index += 1; if (err) { errors.push(err); } if (index === resBlob.length) { callback(errors); return; } }); } else { index += 1; if (index === resBlob.length) { callback(errors); return; } } }); }); }); } function deleteDeployment (settings, deployName){ var settingsDelDeploy = { regionContext: settings.regionContext, url: 'https://management.azure.com/subscriptions/' + settings.regionContext.subscriptionId + '/resourceGroups/' + settings.regionContext.groupId + '/providers/microsoft.resources/deployments/' + deployName + '?api-version=' + templateMsVersion, successCode: 202 }; azureRequest('DELETE', settingsDelDeploy, function (err, resDelDeploy) { if (err) { return; } }); } var that = { setProxy: function (proxyUrl) { tunnelingProxyURL = proxyUrl; }, createRegionContext: function (regionAuthSettings, regionLimits) { return { subscriptionId: regionAuthSettings.subscriptionId, cloudRegion: regionAuthSettings.cloudRegion, limits: regionLimits, pollingCount: 60, providerName: 'azure_v2', groupId: regionAuthSettings.groupId, tenantId: regionAuthSettings.tenantId, clientId: regionAuthSettings.clientId, secret: regionAuthSettings.secret, imagesContainer: regionAuthSettings.imagesContainer, vhdsContainer: regionAuthSettings.vhdsContainer, storageAccount: regionAuthSettings.storageAccount, storageAccessKey: regionAuthSettings.storageAccessKey, keyData: regionAuthSettings.keyData, WinRdpPassword: regionAuthSettings.WinRdpPassword, networkSecurityGroup: regionAuthSettings.networkSecurityGroup, networkSecurityResourceGroup: regionAuthSettings.networkSecurityResourceGroup }; }, createPreparation: function (settings, callback) { if (!settings.vnetName) { var deploymentName = 'deployment' + (new Date().valueOf()), deploymentTemplate = require('../templates/arm-deployment.js')(), deploymentTemplateParams = require('../templates/arm-deployment-params.js')(), deploymentSettings = { regionContext: settings.regionContext, jsonBody: { properties: { template: deploymentTemplate, mode: 'Incremental', parameters: deploymentTemplateParams, debugSetting: { detailLevel: 'requestContent, responseContent' } } }, url: 'https://management.azure.com/subscriptions/' + settings.regionContext.subscriptionId + '/resourcegroups/' + settings.regionContext.groupId + '/providers/microsoft.resources/deployments/' + deploymentName + '?api-version=' + templateMsVersion, successCode: 201 }; deploymentTemplateParams.vnetName.value = 'vnet' + (new Date().valueOf()); deploymentTemplateParams.nsgResourceGroup.value = settings.regionContext.networkSecurityResourceGroup; deploymentTemplateParams.nsgName.value = settings.regionContext.networkSecurityGroup; azureRequest('PUT', deploymentSettings, function (err, templateResult) { if (err) { return callback(new CBError(err.message, { rawResult: {}, node: {} }, 1, true, CBErrorCodes.AZURE_ARM_DEPLOYMENT_ERROR)); } function vnetReadyCallback(err, result) { deleteDeployment(settings, deploymentName); if (err) { err.isFatal = true; if (err.errorList && err.errorList.length > 0){ err.errorList[0].isFatal = true; } return callback(err); } settings.vnetName = result.properties.parameters.vnetName.value; return callback(null, settings.vnetName); } setTimeout(checkTemplateStatus, 4000, settings, templateResult, 225, vnetReadyCallback); }); } else { callback(null, settings.vnetName); } }, createNode: function (settings, cloudServicesTestSettings, nodeIndex, callback) { var nodeName = 'vm' + (new Date().valueOf()), settingsVmLinux, settingsVmLinuxParams, settingsVmWindows, settingsVmWindowsParams, userData, osType, vnetName, deploymentSettings; if (cloudServicesTestSettings) { vnetName = cloudServicesTestSettings; } else { vnetName = settings.vnetName; } if (settings.nodeParams.userData) { userData = new Buffer(JSON.stringify(settings.nodeParams.userData)).toString('base64'); } else { userData = 'IHt9'; } settingsVmLinux = require('../templates/arm-create-linux-vm.js')(); settingsVmLinuxParams = require('../templates/arm-create-linux-vm-params.js')(); settingsVmWindows = require('../templates/arm-create-win-vm.js')(); settingsVmWindowsParams = require('../templates/arm-create-win-vm-params.js')(); osType = (settings.nodeParams.tags.environment.indexOf('windows') !== -1 ? 'windows' : 'linux'); deploymentSettings = { regionContext: settings.regionContext, jsonBody: { properties: { template: osType === 'windows' ? settingsVmWindows : settingsVmLinux, mode: 'Incremental', parameters: osType === 'windows' ? settingsVmWindowsParams : settingsVmLinuxParams, debugSetting: { detailLevel: 'requestContent, responseContent' } } }, url: 'https://management.azure.com/subscriptions/' + settings.regionContext.subscriptionId + '/resourcegroups/' + settings.regionContext.groupId + '/providers/microsoft.resources/deployments/' + nodeName + '?api-version=' + templateMsVersion, successCode: 201 }; if (osType === 'windows') { settingsVmWindowsParams.adminPassword.value = settings.regionContext.WinRdpPassword; settingsVmWindowsParams.storageAccountName.value = settings.regionContext.storageAccount; settingsVmWindowsParams.customData.value = userData; settingsVmWindowsParams.osImageVhdUri.value = settings.regionContext.imagesContainer + settings.nodeParams.imageId; // settingsVmWindowsParams.tags.value = settings.nodeParams.tags; //Tagging done that way because of Microsoft limitations //"Currently, Resource Manager does not support processing an object for the tag names and values..." (https://azure.microsoft.com/en-us/documentation/articles/resource-group-using-tags/) settingsVmWindows.resources[2].tags = settings.nodeParams.tags; settingsVmWindowsParams.vmName.value = nodeName; settingsVmWindowsParams.vmSize.value = settings.nodeParams.instanceType; settingsVmWindowsParams.vnetName.value = vnetName; settingsVmWindowsParams.vnetResourceGroup.value = settings.regionContext.groupId; } else { settingsVmLinuxParams.storageAccountName.value = settings.regionContext.storageAccount; settingsVmLinuxParams.customData.value = userData; settingsVmLinuxParams.keyData.value = settings.regionContext.keyData; settingsVmLinuxParams.osImageVhdUri.value = settings.regionContext.imagesContainer + settings.nodeParams.imageId; // settingsVmLinuxParams.tags.value = settings.nodeParams.tags; //Tagging done that way because of Microsoft limitations //"Currently, Resource Manager does not support processing an object for the tag names and values..." (https://azure.microsoft.com/en-us/documentation/articles/resource-group-using-tags/) settingsVmLinux.resources[2].tags = settings.nodeParams.tags; settingsVmLinuxParams.vmName.value = nodeName; settingsVmLinuxParams.vmSize.value = settings.nodeParams.instanceType; settingsVmLinuxParams.vnetName.value = vnetName; settingsVmLinuxParams.vnetResourceGroup.value = settings.regionContext.groupId; } azureRequest('PUT', deploymentSettings, function (err, templateResult) { if (err) { return callback(new CBError(err.message, err, 1, true, CBErrorCodes.AZURE_ARM_DEPLOYMENT_ERROR)); } function intermidiateCallback(err, success) { var resultNode = { rawResult: 'node-' + nodeName + ' was created.', node: { id: nodeName, status: 'Starting', addresses: null, tags: settings.nodeParams.tags, vnetName: vnetName } }; deleteDeployment(settings, nodeName); if (err) { return callback(err, resultNode); } callback(null, resultNode); } setTimeout(checkTemplateStatus, 4000, settings, templateResult, 350, intermidiateCallback); }); }, listNodes: function (settings, callback) { var finalResults = {rawResult: {}, nodes: []}, settingsListVms = { regionContext: settings.regionContext, url: 'https://management.azure.com/subscriptions/' + settings.regionContext.subscriptionId + '/resourceGroups/' + settings.regionContext.groupId + '/providers/Microsoft.Compute/virtualMachines?api-version=' + xMsVersion, successCode: 200 }, settingsNetworkInterfaces = { regionContext: settings.regionContext, url: 'https://management.azure.com/subscriptions/' + settings.regionContext.subscriptionId + '/resourceGroups/' + settings.regionContext.groupId + '/providers/Microsoft.Network/networkInterfaces?api-version=' + xMsVersion, successCode: 200 }, settingsPublicIpAddresses = { regionContext: settings.regionContext, url: 'https://management.azure.com/subscriptions/' + settings.regionContext.subscriptionId + '/resourceGroups/' + settings.regionContext.groupId + '/providers/Microsoft.Network/publicIPAddresses?api-version=' + xMsVersion, successCode: 200 }; azureRetryRequest('GET', settingsListVms, 3, interval, function (err, resListVms) { if (err) { callback(err, finalResults); return; } if (underscore.isEmpty(resListVms)) { callback(undefined, finalResults); return; } azureRetryRequest('GET', settingsNetworkInterfaces, 3, interval, function (err, resListNetworkInterfaces) { if (err) { callback(err, finalResults); return; } azureRetryRequest('GET', settingsPublicIpAddresses, 3, interval, function (err, resListIpAddresses) { if (err) { callback(err, finalResults); return; } underscore.each(resListVms.value, function (vm) { if (vm) { //exclude deleted/deleting vms if (vm.properties && ((vm.properties.provisioningState === 'Deleting') || (vm.properties.provisioningState === 'Deleted'))) { return; } } var network, publicIP, node = { id: vm.name, status: (vm.properties.provisioningState === 'Succeeded' ? 'ACTIVE' : vm.properties.provisioningState), tags: vm.tags, addresses: [] }; if (node.status === 'Failed') { node.status = 'ERROR (Failed)'; } if ((resListNetworkInterfaces && !underscore.isEmpty(resListNetworkInterfaces.value)) && (resListIpAddresses && !underscore.isEmpty(resListIpAddresses.value))) { network = underscore.find(resListNetworkInterfaces.value, function (network) { return network.id === vm.properties.networkProfile.networkInterfaces[0].id; }); if (network && network.properties && network.properties.ipConfigurations[0] && network.properties.ipConfigurations[0].properties && network.properties.ipConfigurations[0].properties.publicIPAddress) { publicIP = underscore.find(resListIpAddresses.value, function (ip) { return ip.id === network.properties.ipConfigurations[0].properties.publicIPAddress.id; }); if (publicIP) { node.addresses = [network.properties.ipConfigurations[0].properties.privateIPAddress, publicIP.properties.ipAddress ]; } } } finalResults.nodes.push(node); }); finalResults.rawResult = resListVms; callback(null, finalResults); }); }); }); }, deleteNode: function (settings, callback) { if (!settings.node) { callback(null, {rawResult: 'no node to delete'}); return; } var settingsVm = { regionContext: settings.regionContext, url: 'https://management.azure.com/subscriptions/' + settings.regionContext.subscriptionId + '/resourceGroups/' + settings.regionContext.groupId + '/providers/Microsoft.Compute/virtualMachines/' + settings.node.id + '?api-version=' + deleteMsVersion }, settingsGetVm = underscore.extend({successCode: 200}, settingsVm), settingsDelVm = underscore.extend({successCode: 202}, settingsVm), settingsDelNic, settingDelIP, vmNic, vmOsDisc, settingsGetNic; azureRequest('GET', settingsGetVm, function (err, resVm) { if (err) { callback(err, {rawResult: {}}); return; } vmNic = resVm.properties.networkProfile.networkInterfaces[0].id; settingsGetNic = { regionContext: settings.regionContext, url: 'https://management.azure.com' + vmNic + '?api-version=' + deleteMsVersion, successCode: 200 }; callback(null, {rawResult: {}}); azureRequest('GET', settingsGetNic, function (err, resNic) { if (err) { callback(err, {rawResult: {}}); return; } var vmNicIp = resNic.properties.ipConfigurations[0].properties.publicIPAddress.id; var vnetStr = resNic.properties.ipConfigurations[0].properties.subnet.id.indexOf('/subnets/'); var vmVnet = resNic.properties.ipConfigurations[0].properties.subnet.id.substr(0, vnetStr); var settingsGetVnet = { regionContext: settings.regionContext, url: 'https://management.azure.com' + vmVnet + '?api-version=' + deleteMsVersion, successCode: 200 }; azureRequest('DELETE', settingsDelVm, function (err, resDelVm) { if (err) { callback(err, {rawResult: {}}); return; } function deleteRestOfIt(error, success) { if (error) { return; } vmOsDisc = resVm.properties.storageProfile.osDisk.vhd.uri.substring(resVm.properties.storageProfile.osDisk.vhd.uri.lastIndexOf('/') + 1); deleteBlob(settings.regionContext, 'vhds', vmOsDisc, 20, interval, function (err, resDelVHD) { if (err) { return; } // console.log('blob deleted: ', resDelVHD); }); settingsDelNic = { regionContext: settings.regionContext, url: 'https://management.azure.com' + vmNic + '?api-version=' + deleteMsVersion, successCode: 202 }; settingDelIP = { regionContext: settings.regionContext, url: 'https://management.azure.com' + vmNicIp + '?api-version=' + deleteMsVersion, successCode: 202 }; azureRequest('DELETE', settingsDelNic, function (err, resDelNic) { if (err) { return; } // console.log('nic deleted: ', resDelNic); azureRequest('DELETE', settingDelIP, function (err, resDelIP) { if (err) { return; } // console.log('IP deleted: ', resDelIP); azureRequest('GET', settingsGetVnet, function (err, resVnet) { if (err) { return; } if (!resVnet.properties.subnets[0].properties.ipConfigurations) { var settingsDelVnet = settingsGetVnet; settingsDelVnet.successCode = 202; azureRequest('DELETE', settingsDelVnet, function (err, resDelVnet) { if (err) { return; } // console.log('vnet deleted: ', resDelVnet); }); } }); }); }); } setTimeout(checkDeleteStatus, 4000, settings, resVm, 50, deleteRestOfIt); }); }); }); }, createImage: function (settings, callback) { var settingsStopVm = { regionContext: settings.regionContext, url: 'https://management.azure.com/subscriptions/' + settings.regionContext.subscriptionId + '/resourceGroups/' + settings.regionContext.groupId + '/providers/Microsoft.Compute/virtualMachines/' + settings.imageParams.nodeId + '/deallocate?api-version=' + xMsVersion, successCode: 202 }, settingsVMStatus = { regionContext: settings.regionContext, url: 'https://management.azure.com/subscriptions/' + settings.regionContext.subscriptionId + '/resourceGroups/' + settings.regionContext.groupId + '/providers/Microsoft.Compute/virtualMachines/' + settings.imageParams.nodeId + '/InstanceView?api-version=' + xMsVersion, successCode: 200 }, settingsGeneralizeVm = { regionContext: settings.regionContext, url: 'https://management.azure.com/subscriptions/' + settings.regionContext.subscriptionId + '/resourceGroups/' + settings.regionContext.groupId + '/providers/Microsoft.Compute/virtualMachines/' + settings.imageParams.nodeId + '/generalize?api-version=' + xMsVersion, successCode: 200, retryCodes: 409 }, imageName = 'image' + (new Date().valueOf()), settingsImage = { regionContext: settings.regionContext, jsonBody: { "vhdPrefix": imageName, "destinationContainerName": 'images', "overwriteVhds": true }, url: 'https://management.azure.com/subscriptions/' + settings.regionContext.subscriptionId + '/resourceGroups/' + settings.regionContext.groupId + '/providers/Microsoft.Compute/virtualMachines/' + settings.imageParams.nodeId + '/capture?api-version=' + xMsVersion, successCode: 202, retryCodes: 409 }; azureRequest('POST', settingsStopVm, function (err, res) { if (err) { callback(err); return; } checkReachedFinishedState(settingsVMStatus, 'PowerState/deallocated', 50, function (err, res) { azureRequest('POST', settingsGeneralizeVm, function (err, res) { if (err) { callback(err); return; } checkReachedFinishedState(settingsVMStatus, 'OSState/generalized', 20, function (err, res) { azureRequest('POST', settingsImage, function (err, res) { if (err) { callback(err); return; } getBlobFullName(settings.regionContext, 'system', imageName, foldersStructure, 3, function (err, res) { if (err || !res) { callback(err || new AzureError('image was not created properly')); return; } return callback(null, {rawResult: null, imageId: res}); }); }); }); }); }); }); }, deleteImage: function (settings, callback) { deleteBlob(settings.regionContext, 'system','Microsoft.Compute/