UNPKG

cloud-blender

Version:

A high level library for cloud compute operations

1,341 lines (1,154 loc) 60.9 kB
var request = require('request'), underscore = require('underscore'), crypto = require('crypto'), parseString = require('xml2js').parseString, async = require('async'), AWSError = require('./aws-error'), AWSSDK = require('aws-sdk'), proxyagent = require('proxy-agent'); AWSSDK.config.ec2 = { maxRetries: 5, sslEnabled: true, logger: process.stdout }; module.exports = (function() { var regionAzs = { }; function getApiVersion(serviceType){ return serviceType === 'ec2' ? '2015-04-15' : '2010-05-08'; } function parseBodyString(str, callback){ if(underscore.isString(str)){ parseString(str, callback); } else{ callback(null,null); } } // this function build the regionAzs data structure. // if it cant find a record - it queries the cloud // if it fails - it stops querying the cloud // This must be called dynamically the first time the lib is being loaded // since ec2 has a lot of mess with AZs function getAvailabiltyZone(settings, callback) { var region = settings.regionContext.computeSettings.region, regionAz = regionAzs[region], zones, index, selectedRegion; if (settings.regionContext.computeSettings.subnetId){ // AZ is implicit on subnetId // Use setImmediate so not to break async behaviour async.setImmediate(function (){ callback(null, null); }); return; } if (region === 'sa-east-1') { callback(new AWSError('Sau Paulo region has bug in az-c - disabling az usage for it')); return; } if (!regionAz) { that.describeAZs(settings, function(error, azs) { if (error) { regionAzs[region] = {counter: -1};// indicating error happened and not to try again callback(error); return; } regionAzs[region] = { counter: 1, // 1 is for incrementing the counter since we use 0 in this call zones: azs }; callback(undefined, azs[0]); }); return; } zones = regionAz.zones; index = regionAz.counter; if (index === -1) { // we failed in a previous call callback(new AWSError('failed to find azs')); return; } selectedRegion = zones[index%(zones.length)]; regionAz.counter ++; callback(undefined, selectedRegion); } // internal members // ---------------- var tunnelingProxyURL; // internal functions // ------------------ function uriEscape(string) { var output = encodeURIComponent(string); output = output.replace(/[^A-Za-z0-9_.~\-%]+/g, escape); output = output.replace(/[*]/g, function(ch) { return '%' + ch.charCodeAt(0).toString(16).toUpperCase(); }); return output; } function createURIString(params) { var sortedKeysArr, i, length, uriString = ''; sortedKeysArr = underscore.keys(params).sort(); for (i = 0, length = sortedKeysArr.length; i < length; i++) { uriString += uriEscape(sortedKeysArr[i]) + '=' + uriEscape(params[sortedKeysArr[i]]); if (i !== length -1) { uriString += '&'; } } return uriString; } function hash(data) { return crypto.createHash('sha256').update(new Buffer(data)).digest('hex'); } function sign(key, data, digest) { return crypto.createHmac('sha256', key).update(data).digest(digest); } function createNowDate() { return (new Date()).toISOString().replace(/[:\-]|\.\d{3}/g, ''); } // STEP 1 function canonicalRequest(settings, nowDate) { var canonical = ''; canonical += settings.method + '\n'; // HTTPRequestMethod canonical += settings.path + '\n'; canonical += ((settings.method === 'GET')?createURIString(settings.params):'') + '\n'; // CanonicalQueryString canonical += 'host:' + settings.serviceType + '.' + settings.region + '.amazonaws.com\n' + 'x-amz-date:' + nowDate + '\n\n'; canonical += 'host;x-amz-date' + '\n'; if (settings.method === 'POST') { canonical += hash(createURIString(settings.params)); } else { // no body in GET canonical += hash(''); } //console.log('\ncanonical:\n$' + canonical + '$'); return canonical; } // STEP 2 function stringToSign(settings, nowDate) { var str = ''; str += 'AWS4-HMAC-SHA256\n'; // Algorithm str += nowDate + '\n'; // RequestDate // CredentialScope str += nowDate.substr(0,8) + '/'; str += settings.region + '/'; str += settings.serviceType + '/'; str += 'aws4_request\n'; // HashedCanonicalRequest str += hash(canonicalRequest(settings, nowDate)); //console.log('\nstringToSign:\n$' + str+'$'); return str; } // STEP 3 function signature(settings, nowDate) { var kDate = sign('AWS4' + settings.credentials.secretAccessKey, nowDate.substr(0,8)), kRegion = sign(kDate, settings.region), kService = sign(kRegion, settings.serviceType), kCredentials = sign(kService, 'aws4_request'); return sign(kCredentials, stringToSign(settings, nowDate), 'hex'); } // STEP 4 // This is the main of the signature process for POST method function generateAuthorizationHeader(settings, nowDate) { var authHeader = ''; authHeader = 'AWS4-HMAC-SHA256 '; authHeader += 'Credential=' + settings.credentials.accessKeyId + '/'; authHeader += nowDate.substr(0,8) + '/'; authHeader += settings.region + '/'; authHeader += settings.serviceType + '/'; authHeader += 'aws4_request, '; authHeader += 'SignedHeaders=host;x-amz-date, '; authHeader += 'Signature=' + signature(settings, nowDate); //console.log('\nauth header:\n' + authHeader); return authHeader; } function generateEC2Request(settings, callback){ settings.serviceType = 'ec2'; generateAWSRequest(settings, callback); } function generateIAMRequest(settings, callback){ settings.serviceType = 'iam'; generateAWSRequest(settings, callback); } function generateAWSRequest(settings, callback) { var body, requestSettings, uriStr, authHeader, domainSuffix = ((settings.region === 'cn-north-1')?'.amazonaws.com.cn':'.amazonaws.com'), requestUrlBase = 'https://'+ settings.serviceType + (settings.serviceType === 'ec2' ? '.' + settings.region : '') + domainSuffix, nowDate = createNowDate(); settings.params.Action = settings.action; settings.params.Version = getApiVersion(settings.serviceType); authHeader = generateAuthorizationHeader(settings, nowDate); uriStr = createURIString(settings.params); //console.log('uri str is ' + uriStr); if (settings.method === 'POST') { body = uriStr; } requestSettings = { method: settings.method, url: requestUrlBase + settings.path + ((settings.method === 'POST')?'':('?' + uriStr)), headers: { 'Authorization': authHeader, 'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8', 'Host': settings.serviceType + '.' + settings.region + '.amazonaws.com', 'x-amz-date': nowDate }, body: body, timeout: 120000 }; if (tunnelingProxyURL) { requestSettings.proxy = tunnelingProxyURL; } //console.log(requestSettings); request(requestSettings, function (error, response, bodyString) { var errorRequest; //we always try and parse the body string , even if we have an error. It might contains important information //especially for StatusCode 400 errors . This information will be added to the providerError property //if bodyString is null it will not return an error but jsonObj will be null parseBodyString(bodyString, function(errParsing, jsonObj){ if(errParsing){ error = error || new AWSError(); error.message += 'bad parsing to XML of: ' + bodyString + ', parsing error is: ' + errParsing.message; } if(!jsonObj || error || response.statusCode>= 300){ errorRequest = new AWSError('problem in request: ' + JSON.stringify(requestSettings) + ', description: ' + bodyString + (response ? response.statusCode : 'request failed') + 'requestError: ' + error, jsonObj); callback(errorRequest); } else{ callback(null, jsonObj); } }); }); } function createSimpleNodeData(rawNode) { var node = { id: rawNode.instanceId[0], status: rawNode.instanceState[0].name[0].toUpperCase(), // libCloud addresses: [null, null], releaseInfo: {} }; if (node.status === 'RUNNING') { node.status = 'ACTIVE';// make it aligned with libCloud } if (rawNode.privateIpAddress) { node.addresses[0] = (rawNode.privateIpAddress[0]); } if (rawNode.ipAddress) { node.addresses[1] = (rawNode.ipAddress[0]); } if (rawNode.tagSet) { node.tags = {}; underscore.each(rawNode.tagSet, function(tagSet) { underscore.each(tagSet.item, function(item) { node.tags[item.key[0]] = item.value[0]; }); }); } return node; } function createTagsPolling(settings, nodeId, pollingCount, interval, callback) { var tagsSettings = { credentials: settings.regionContext.identitySettings.credentials, region: settings.regionContext.computeSettings.region, path: '/', method: 'POST', params: {'ResourceId.1': nodeId}, action: 'CreateTags' }, tagCount = 0; underscore.each(settings.nodeParams.tags, function(value, key) { tagsSettings.params['Tag.' + tagCount + '.Key'] = key; tagsSettings.params['Tag.' + tagCount + '.Value'] = value; tagCount++; }); generateEC2Request(tagsSettings, function(errorTags, result) { if (errorTags && pollingCount > 1){ // console.log('tags creation failed, polling count: ' + pollingCount + ' ' +errorTags); setTimeout(createTagsPolling, interval, settings, nodeId, pollingCount - 1, interval, callback); } else { callback(errorTags, result); } }); } function removeSecurityGroup(settings, groupInfo, callback){ var removeSecurityGroupSetting = { credentials: settings.regionContext.identitySettings.credentials, region: settings.regionContext.computeSettings.region, path: '/', method: 'POST', params: { GroupName: groupInfo.groupName }, action: 'DeleteSecurityGroup' }; generateEC2Request(removeSecurityGroupSetting, function (error) { callback(error); }); } function createSecurityGroup(settings, groupInfo, callback) { var createSecurityGroupSetting = { credentials: settings.regionContext.identitySettings.credentials, region: settings.regionContext.computeSettings.region, path: '/', method: 'POST', params: { GroupName: groupInfo.groupName, GroupDescription: groupInfo.groupDescription }, action: 'CreateSecurityGroup' }; generateEC2Request(createSecurityGroupSetting, function (error) { //what do we really want to do if we already have a duplicate group should we continue ? Do we want to check getEc2ErrorCode(error) !== 'InvalidGroup.Duplicate' ? if (error) { callback(error); return; } var addIngressRules = { credentials: settings.regionContext.identitySettings.credentials, region: settings.regionContext.computeSettings.region, path: '/', method: 'POST', params: { GroupName: groupInfo.groupName }, action: 'AuthorizeSecurityGroupIngress' }; groupInfo.ingressRules.forEach(function (rule, index) { index++; //we can use either port to define a single port or fromPort toPort. if(rule.port){ rule.fromPort = rule.toPort = rule.port; } addIngressRules.params['IpPermissions.' + index + '.' + 'IpProtocol'] = rule.protocol ? rule.protocol : 'tcp'; addIngressRules.params['IpPermissions.' + index + '.' + 'FromPort'] = rule.fromPort; addIngressRules.params['IpPermissions.' + index + '.' + 'ToPort'] = rule.toPort; addIngressRules.params['IpPermissions.' + index + '.' + 'IpRanges.1.CidrIp'] = rule.ipRange === 'all' ? '0.0.0.0/0' : rule.ipRange; }); generateEC2Request(addIngressRules, function (error, result) { if(error){ //We add a retry , because in some very rare cases , although we recieved a response that security group was created , it is still not ready , so we try again . //This was confirmed by the AWS support team that it could happen and that we should use a timeout and try again. setTimeout(function(){ generateEC2Request(addIngressRules, function(error, result){ console.log('Second call to AuthorizeSecurityGroupIngress for group: ' + groupInfo.groupName + ', region: ' + addIngressRules.region + ' ' + (error ? 'Failed.' : 'Succeeded.')); callback(error, result); }); }, 10000); return; } callback(null, result); }); }); } function removeKeyPair(settings, keyPair, callback) { var deleteKeyPair = { credentials: settings.regionContext.identitySettings.credentials, region: settings.regionContext.computeSettings.region, path: '/', method: 'POST', params: { KeyName: keyPair.name }, action: 'DeleteKeyPair' }; generateEC2Request(deleteKeyPair, function (error) { callback(error); }); } function importKeyPair(settings, keyPair, callback) { var importKeyPairSetting = { credentials: settings.regionContext.identitySettings.credentials, region: settings.regionContext.computeSettings.region, path: '/', method: 'POST', params: { KeyName: keyPair.name, //API always sends base64 encoded public key. Input can be either raw or base64 PublicKeyMaterial: keyPair.publicKey ? new Buffer(keyPair.publicKey).toString("base64") : keyPair.publicKeyBase64 }, action: 'ImportKeyPair' }; generateEC2Request(importKeyPairSetting, function (error) { callback(error); }); } function createEc2Instance(settings){ var credentials = settings.regionContext.identitySettings.credentials; return new AWSSDK.EC2({ region: settings.regionContext.computeSettings.region, accessKeyId: credentials.accessKeyId, secretAccessKey: credentials.secretAccessKey }); } function describeAddresses(settings, callback){ var ec2 = createEc2Instance(settings); if (!settings.publicIps || settings.publicIps.length === 0){ callback("Error, you must provide addresses"); } var params = {}; params.PublicIps = settings.publicIps; ec2.describeAddresses(params, function (err, data) { if (err) { callback(err); return; } callback(null, data.Addresses || []); }); } // the exported functions var that = { setProxy: function (proxyUrl) { tunnelingProxyURL = proxyUrl; if (!underscore.isEmpty(tunnelingProxyURL)) { AWSSDK.config.update({ httpOptions: { agent: proxyagent(tunnelingProxyURL)} }); } }, createPreparation: function (settings, callback) { callback(null, null); }, describeAZs: function (settings, callback) { var generateSettings = { credentials: settings.regionContext.identitySettings.credentials, region: settings.regionContext.computeSettings.region, path: '/', method: 'GET', params: {}, action: 'DescribeAvailabilityZones' }; generateEC2Request(generateSettings, function (error, result) { var regionAZs = []; if (error) { callback(error); return; } underscore.each(result.DescribeAvailabilityZonesResponse.availabilityZoneInfo[0].item, function (zone) { if (zone.zoneState[0] === 'available') { regionAZs.push(zone.zoneName[0]); } }); callback(error, regionAZs); }); }, createRegionContext: function (regionSettings, limits) { return { identitySettings: { credentials: { accessKeyId: regionSettings.accessKey, secretAccessKey: regionSettings.secretKey } }, computeSettings: { region: regionSettings.region }, limits: limits, providerName: 'aws', pollingCount: 180 }; }, listNodes: function (settings, callback) { var generateSettings = { credentials: settings.regionContext.identitySettings.credentials, region: settings.regionContext.computeSettings.region, path: '/', method: 'GET', params: {}, action: 'DescribeInstances' }; generateEC2Request(generateSettings, function (error, result) { var finalResults = { nodes: [], rawResult: result }; if (result) { //console.log(JSON.stringify(result, null, ' ')); underscore.each(result.DescribeInstancesResponse.reservationSet, function (group) { underscore.each(group.item, function (item) { underscore.each(item.instancesSet, function (instanceSet) { underscore.each(instanceSet.item, function (item) { var node = createSimpleNodeData(item); // terminated machines can confuse us // other cloud vendors may not return them so // we skip them - they can be obtained using rawNodes if (node.status !== 'TERMINATED') { finalResults.nodes.push(node); } }); }); }); }); } callback(error, finalResults); }); }, //listNodes createNode: function (settings, cloudServicesTestSettings, nodeIndex, callback) { getAvailabiltyZone(settings, function (errorAZ, az) { var generateSettings = { credentials: settings.regionContext.identitySettings.credentials, region: settings.regionContext.computeSettings.region, path: '/', method: 'POST', params: { ImageId: settings.nodeParams.imageId, InstanceType: settings.nodeParams.instanceType, KeyName: settings.nodeParams.keyName, MinCount: 1, MaxCount: 1, 'BlockDeviceMapping.1.DeviceName': '/dev/sdb', 'BlockDeviceMapping.1.VirtualName': 'ephemeral0' }, action: 'RunInstances' }; var finalResult = {}; var securityGroups = settings.nodeParams.securityGroups || []; var userData = settings.nodeParams.userData; var securityGroupsParamPrefix = "SecurityGroup."; var subnetId = settings.regionContext.computeSettings.subnetId; var isUsingVPC = settings.regionContext.computeSettings.isUsingVPC; if (isUsingVPC && subnetId){ generateSettings.params["SubnetId"] = subnetId; securityGroupsParamPrefix = "SecurityGroupId."; } underscore.each(securityGroups, function (group, i){ generateSettings.params[securityGroupsParamPrefix + i] = group; }); if (userData) { generateSettings.params.UserData = new Buffer(JSON.stringify(userData)).toString('base64'); } if (az) { generateSettings.params['Placement.AvailabilityZone'] = az; } // adding (and possibly overriding) vendor specific params underscore.extend(generateSettings.params, settings.nodeParams.vendorSpecificParams); generateEC2Request(generateSettings, function (error, result) { // we add tags synthetically since adding tags is not supported // in the RunInstance command like in hpcloud var node = { tags: settings.nodeParams.tags }, rawNode; if (error) { node.status = 'ERROR'; } else { rawNode = result.RunInstancesResponse.instancesSet[0].item[0]; node = underscore.extend(node, createSimpleNodeData(rawNode)); } finalResult.rawResult = result; finalResult.node = node; if (!error && settings.nodeParams.tags) { createTagsPolling(settings, node.id, 3, 10000, function (errorTags, result) { if (errorTags) { node.status = 'ERROR_TAGS'; } callback(errorTags, finalResult); }); } else { callback(error, finalResult); } }); }); },//createNode deleteNode: function (settings, callback) { var ec2, instanceId = settings.node.id; function releaseElasticIps(done) { function releaseEIP(association, cb) { //disassociate is mandatory for Nondefault VPC ec2.disassociateAddress({ AssociationId: association.AssociationId }, function(ignore) { ec2.releaseAddress({ AllocationId: association.AllocationId }, function(err, data) { cb(err); }); }); } try { var credentials = settings.regionContext.identitySettings.credentials; ec2 = new AWSSDK.EC2({ region: settings.regionContext.computeSettings.region, accessKeyId: credentials.accessKeyId, secretAccessKey: credentials.secretAccessKey }); ec2.describeInstances({InstanceIds: [instanceId] }, function (err, data) { if (err) { done(err); return; } var networkInterfaceId; if (!underscore.isEmpty(data.Reservations) && !underscore.isEmpty(data.Reservations[0]) && !underscore.isEmpty(data.Reservations[0].Instances)) { var instances = data.Reservations[0].Instances; if (!underscore.isEmpty(instances[0]) && !underscore.isEmpty(instances[0].NetworkInterfaces)) { if (instances[0].NetworkInterfaces[0] && instances[0].NetworkInterfaces[0].PrivateIpAddresses.length > 1) { networkInterfaceId = instances[0].NetworkInterfaces[0].NetworkInterfaceId; } } } if (underscore.isEmpty(networkInterfaceId)) { done(); return; } ec2.describeInstanceAttribute({ Attribute: 'userData', InstanceId: instanceId}, function (err, data) { if (err || underscore.isEmpty(data) || underscore.isEmpty(data.UserData)) { done(); return; } var decoded, userData; try { if (data.UserData && data.UserData.Value) { decoded = new Buffer(data.UserData.Value, 'base64').toString(); userData = JSON.parse(decoded); } } catch(ex) { console.log('decode failed: ' + ex.toString()); } if (!underscore.isEmpty(userData) && userData.injectorUseMultipleIps && !userData.useIPWhiteList ) { ec2.describeNetworkInterfaces({ NetworkInterfaceIds: [networkInterfaceId] }, function(err, data) { if (err) { done(err); return; } var association = []; if (!underscore.isEmpty(data.NetworkInterfaces) && !underscore.isEmpty(data.NetworkInterfaces[0])) { var networkInterface = data.NetworkInterfaces[0]; underscore.each(networkInterface.PrivateIpAddresses, function(item) { if (!item.Primary && item.Association) { association.push(item.Association); console.log('release Elastic IP. %s, %s ', item.Association.PublicIp, item.Association.AllocationId); } }); } async.eachSeries(association, releaseEIP, function(err, results) { if (err) { console.log('failed to release Elastic IP. (%s)', err.toString()); } done(err); }); }); } else { done(); } }); }); } catch(ex) { callback("deleteNode - got exception: " + ex.toString()); } } releaseElasticIps(function() { var deleteSettings = { credentials: settings.regionContext.identitySettings.credentials, region: settings.regionContext.computeSettings.region, path: '/', method: 'POST', params: {'InstanceId.1': settings.node.id}, action: 'TerminateInstances' }, finalResult = {}; generateEC2Request(deleteSettings, function (error, result) { var confirmationString = 'SUCCESS'; if (error) { confirmationString = 'ERROR'; } finalResult.rawResult = result; finalResult.result = confirmationString; callback(error, finalResult); }); }); },// delete node createImage: function (settings, callback) { var createImageSettings = { credentials: settings.regionContext.identitySettings.credentials, region: settings.regionContext.computeSettings.region, path: '/', method: 'POST', params: { InstanceId: settings.imageParams.nodeId, Name: new Date().valueOf() + '-createdByStorm' }, action: 'CreateImage' }, finalResult = {}; if (!settings.skipBlockDeviceMapping) { underscore.extend(createImageSettings.params, { 'BlockDeviceMapping.1.DeviceName': '/dev/sda1', 'BlockDeviceMapping.1.Ebs.VolumeType': 'gp2' //'BlockDeviceMapping.1.Ebs.VolumeType': 'io1', //'BlockDeviceMapping.1.Ebs.Iops': 300, //'BlockDeviceMapping.1.Ebs.VolumeSize': 10 }); } if (settings.imageParams.vendorSpecificParams) { underscore.extend(createImageSettings.params, settings.imageParams.vendorSpecificParams); } generateEC2Request(createImageSettings, function (error, result) { var tagCount = 0, tagsSettings; finalResult.rawResult = result; if (result) { finalResult.imageId = result.CreateImageResponse.imageId[0]; } if (!error && settings.imageParams.tags) { tagsSettings = { credentials: settings.regionContext.identitySettings.credentials, region: settings.regionContext.computeSettings.region, path: '/', method: 'POST', params: {'ResourceId.1': finalResult.imageId}, action: 'CreateTags' }; underscore.each(settings.imageParams.tags, function (value, key) { tagsSettings.params['Tag.' + tagCount + '.Key'] = key; tagsSettings.params['Tag.' + tagCount + '.Value'] = value; tagCount++; }); generateEC2Request(tagsSettings, function (errorTags, result) { callback(errorTags, finalResult); }); } else { callback(error, finalResult); } }); }, listImages: function (settings, imageIdsArr, callback) { var imagesObj = {}, imagesIndex = 0; underscore.each(imageIdsArr, function (image) { imagesIndex += 1; imagesObj['ImageId.' + imagesIndex] = image; }); var listSettings = { credentials: settings.regionContext.identitySettings.credentials, region: settings.regionContext.computeSettings.region, path: '/', method: 'GET', params: { 'Owner.1': 'self' }, action: 'DescribeImages' }, snapSettings = { credentials: settings.regionContext.identitySettings.credentials, region: settings.regionContext.computeSettings.region, path: '/', method: 'GET', params: { 'Owner.1': 'self' }, action: 'DescribeSnapshots' }, snapShots = {}; if (settings.vendorSpecificParams) { underscore.extend(listSettings.params, settings.vendorSpecificParams); } if (imagesObj) { underscore.extend(listSettings.params, imagesObj); } generateEC2Request(listSettings, function (error, result) { generateEC2Request(snapSettings, function (errorSnap, resultSnap) { underscore.each(resultSnap.DescribeSnapshotsResponse.snapshotSet[0].item, function (snapShot) { snapShots[snapShot.snapshotId[0]] = snapShot.startTime[0]; }); var finalResults = { images: [], rawResult: result }; if (result) { finalResults.images = []; underscore.each(result.DescribeImagesResponse.imagesSet, function (imageSet) { underscore.each(imageSet.item, function (item) { var image = underscore.pick(item, 'imageId', 'name', 'imageState', 'tagSet'), tags = {}, creationTime; var i, loopIndex = 0, ebsIndex = -1; if (item.blockDeviceMapping && item.blockDeviceMapping[0] && item.blockDeviceMapping[0].item) { loopIndex = item.blockDeviceMapping[0].item.length; } for (i = 0; i < loopIndex; i++) { if (item.blockDeviceMapping[0].item[i].ebs) { ebsIndex = i; break; } } if ((ebsIndex !== -1) && (item.imageState[0] === 'available')) { //only if the image is available we can be sure that we have the snapshotId creationTime = snapShots[item.blockDeviceMapping[0].item[ebsIndex].ebs[0].snapshotId[0]]; } else { creationTime = Date.now(); } underscore.each(image.tagSet, function (tagSet) { underscore.each(tagSet.item, function (tagItem) { tags[tagItem.key[0]] = tagItem.value[0]; }); }); finalResults.images.push({ id: image.imageId[0], status: (image.imageState[0] === 'available') ? 'ACTIVE' : image.imageState[0].toUpperCase(), name: image.name[0], creationTime: creationTime, tags: tags }); }); }); } callback(error, finalResults); }); }); }, deleteSnapshot: function (settings, callback) { var deleteSettings = { credentials: settings.regionContext.identitySettings.credentials, region: settings.regionContext.computeSettings.region, path: '/', method: 'POST', params: {'SnapshotId': settings.snapshotId}, action: 'DeleteSnapshot' }, finalResult = {}; generateEC2Request(deleteSettings, function (error, result) { var confirmationString = 'SUCCESS'; if (error) { confirmationString = 'ERROR'; } finalResult.rawResult = result; finalResult.result = confirmationString; callback(error, finalResult); }); }, deregisterImage: function (settings, callback) { var deregisterSettings = { credentials: settings.regionContext.identitySettings.credentials, region: settings.regionContext.computeSettings.region, path: '/', method: 'POST', params: {'ImageId': settings.imageParams.imageId}, action: 'DeregisterImage' }, finalResult = {}; generateEC2Request(deregisterSettings, function (error, result) { var confirmationString = 'SUCCESS'; if (error) { confirmationString = 'ERROR'; } finalResult.rawResult = result; finalResult.result = confirmationString; callback(error, finalResult); }); }, deleteImage: function (settings, callback) { var errorDeleteImage = new AWSError(), finalResult = { rawResult: [] }, listSettings = { regionContext: settings.regionContext }, snapshotsToDelete = [], i, length, snapshotCounter = 0; function deleteSnapshotCB(errorDeleteSnapshot, resultDeleteSnapshot) { snapshotCounter++; if (errorDeleteSnapshot) { errorDeleteImage.appendError(errorDeleteSnapshot); finalResult.result = 'ERROR'; } finalResult.rawResult.push(resultDeleteSnapshot.rawResult); if (snapshotCounter === length) { //we call the getCallbackError that returns null if there no errors in the errorDeleteImage. //we do this because we are iterating and the errorDeleteImage can contain 0 or more errors. //Instead of doing errorDeleteImage.length > 0 ? errorDeleteImage : null we do this logic in getCallbackError() callback(errorDeleteImage.getCallbackError(), finalResult); } } that.listImages(listSettings,settings.imageParams.imageId, function (errorList, resultList) { var finalError; if (errorList || ( resultList.images[0] && resultList.images[0].status !== 'ACTIVE')) { finalResult.result = 'ERROR'; finalError = errorList ? errorList : new AWSError('problem in images state. Status not ACTIVE: ' + JSON.stringify(resultList.images[0])); callback(finalError, finalResult); return; } // @@@@@@@ should make sure all these loops are necessary underscore.each(resultList.rawResult.DescribeImagesResponse.imagesSet, function (imageSet) { underscore.each(imageSet.item, function (item) { underscore.each(item.blockDeviceMapping, function (block) { underscore.each(block.item, function (blockItem) { underscore.each(blockItem.ebs, function (ebsItem) { snapshotsToDelete.push(ebsItem.snapshotId[0]); }); }); }); }); }); that.deregisterImage(settings, function (errorDeregister, resultDeregister) { finalResult.result = 'SUCCESS'; finalResult.rawResult.push(resultDeregister.rawResult); if (errorDeregister) { errorDeleteImage.appendError(errorDeregister); finalResult.result = 'ERROR'; callback(errorDeleteImage.getCallbackError(), finalResult); return; } for (i = 0, length = snapshotsToDelete.length; i < length; i++) { that.deleteSnapshot({ regionContext: settings.regionContext, snapshotId: snapshotsToDelete[i] }, deleteSnapshotCB); } }); }); }, associateAddress: function (settings, callback) { var ec2 = createEc2Instance(settings); var localSettings = { publicIps: [settings.associatePairs.publicIp], instanceId: settings.associatePairs.instanceId, regionContext: settings.regionContext, }; that.disassociateAddressesEx(localSettings, function (error) { describeAddresses(localSettings, function (error, addresses){ if (!addresses || !addresses[0]){ callback("Error, could not find address"); return; } var associationInfo = { InstanceId: localSettings.instanceId, PublicIp: localSettings.publicIps[0] }; if (addresses[0].Domain === "vpc"){ delete associationInfo.PublicIp; associationInfo.AllocationId = addresses[0].AllocationId; } ec2.associateAddress(associationInfo, function (e, result){ callback(e, { rawResult: result, result: !error ? 'SUCCESS' : 'ERROR' }); }); }); }); }, disassociateAddress: function (settings, callback) { var disassociateSettings = { credentials: settings.regionContext.identitySettings.credentials, region: settings.regionContext.computeSettings.region, path: '/', method: 'POST', params: {'PublicIp': settings.publicIp}, action: 'DisassociateAddress' }, finalResult = {}; generateEC2Request(disassociateSettings, function (error, result) { var confirmationString = 'SUCCESS'; if (error) { confirmationString = 'ERROR'; } finalResult.rawResult = result; finalResult.result = confirmationString; callback(error, finalResult); }); }, disassociateAddressesEx: function (settings, callback) { var ec2, addresses = [], params = {}, credentials = settings.regionContext.identitySettings.credentials; function disassociateIp(item, done) { var params = {}; if (item.Domain === 'vpc') { if (!item.AssociationId) { done(); return; } params.AssociationId = item.AssociationId; } else { params.PublicIp = item.PublicIp; } ec2.disassociateAddress(params, function (err) { if (err) { done(err); return; } done(); }); } ec2 = new AWSSDK.EC2({ region: settings.regionContext.computeSettings.region, accessKeyId: credentials.accessKeyId, secretAccessKey: credentials.secretAccessKey }); params.PublicIps = settings.publicIps; ec2.describeAddresses(params, function (err, data) { if (err) { callback(err); return; } if (!underscore.isEmpty(data.Addresses)) { underscore.each(data.Addresses, function(item) { addresses.push(underscore.pick(item, ['PublicIp', 'AssociationId', 'Domain'])); }); } async.eachSeries(addresses, disassociateIp, function(err) { if (err) { callback(err); return; } callback(); }); }); }, allocateAddress: function (settings, callback) { var finalResult = {}; var allocateSettings = { credentials: settings.regionContext.identitySettings.credentials, region: settings.regionContext.computeSettings.region, path: '/', method: 'POST', params: { Domain: settings.regionContext.computeSettings.isUsingVPC ? 'vpc' : 'standard' }, action: 'AllocateAddress' }; generateEC2Request(allocateSettings, function (error, result) { if (result) { finalResult.result = result.AllocateAddressResponse.publicIp[0]; finalResult.rawResult = result; } callback(error, finalResult); }); }, releaseAddress: function (settings, callback) { var finalResult = {}, paramsObj = (settings.requestInfo) ? {'AllocationId': settings.requestInfo} : {'PublicIp': settings.ip}, releaseSettings = { credentials: settings.regionContext.identitySettings.credentials, region: settings.regionContext.computeSettings.region, path: '/', method: 'POST', params: paramsObj, action: 'ReleaseAddress' }; generateEC2Request(releaseSettings, function (error, result) { if (result) { finalResult.result = result.ReleaseAddressResponse.return[0]; finalResult.ip = settings.ip; finalResult.rawResult = result; } callback(error, finalResult); }); }, assignPublicAddresses: function (settings, callback) { var ec2, publicIps = [], instanceInfo = {}, instanceId = settings.instanceId, numberOfPublicIps = settings.numberOfPublicIps; function allocatePublicIp(done) { ec2.allocateAddress({ Domain: 'vpc'}, function (err, data) { if (err) { done(err); return; } publicIps.push( { PublicIp: data.PublicIp, AllocationId: data.AllocationId } ); done(); }); } function describeInstance(done) { ec2.describeInstances({InstanceIds: [instanceId] }, function (err, data) { if (err) { done(err); return; } if (!underscore.isEmpty(data.Reservations) && !underscore.isEmpty(data.Reservations[0]) && !underscore.isEmpty(data.Reservations[0].Instances)) { var instances = data.Reservations[0].Instances; if (!underscore.isEmpty(instances[0]) && (instances[0].State.Code === 16) && !underscore.isEmpty(instances[0].NetworkInterfaces)) { instanceInfo.NetworkInterfaceId = instances[0].NetworkInterfaces[0].NetworkInterfaceId; instanceInfo.PrivateIpAddresses = instances[0].NetworkInterfaces[0].PrivateIpAddresses; instanceInfo.PrivateIpAddresses = underscore.reject(instanceInfo.PrivateIpAddresses, function(addr) { return addr.Primary === true; } ); done(); return; } } done('instance is not yet in running state or its network interface is not ready'); }); } function associateIps(data, done) { var params = { AllocationId: data.AllocationId, AllowReassociation: true, NetworkInterfaceId: instanceInfo.NetworkInterfaceId }; var privateIp = instanceInfo.PrivateIpAddresses.pop(); if (privateIp) { params.PrivateIpAddress = privateIp.PrivateIpAddress; } ec2.associateAddress(params, function (err, data) { if (err) { done(err); return; } done(); }); } function allocatePublicIps(done) { var tasks = [], params = {}; if (underscore.isEmpty(publicIps)) { underscore.times(numberOfPublicIps - 1, function () { tasks.push(allocatePublicIp); }); async.series(tasks, function(err) { done(err); }); } else { params.PublicIps = publicIps; ec2.describeAddresses(params, function (err, data) { if (err) { done(err); return; } publicIps = []; if (!underscore.isEmpty(data.Addresses)) { underscore.each(data.Addresses, function(item) { publicIps.push(underscore.pick(item, ['PublicIp', 'AllocationId'])); }); } done(); }); } } try { if (underscore.isArray(settings.publicIps) && (settings.publicIps.length >= 1)) { publicIps = underscore.clone(settings.publicIps); numberOfPublicIps = publicIps.length + 1; } if (underscore.isEmpty(instanceId) || isNaN(numberOfPublicIps) || (numberOfPublicIps < 2)) { callback(new Error('invalid parameter')); return; } var credentials = settings.regionContext.identitySettings.credentials; ec2 = new AWSSDK.EC2({ region: settings.regionContext.computeSettings.region, accessKeyId: credentials.accessKeyId, secretAccessKey: credentials.secretAccessKey }); async.retry({times: 10, interval: 5000}, describeInstance, function(err) { if (err) { callback(err); return; } var params = { NetworkInterfaceId: instanceInfo.NetworkInterfaceId, AllowReassignment: true, SecondaryPrivateIpAddressCount: numberOfPublicIps - 1 }; ec2.assignPrivateIpAddresses(params, function (err, data) { if (err) { callback(err); return; } describeInstance(function(err) { if (err) { callback(err); return; } allocatePublicIps(function(err) { if