diff --git a/lib/pkgcloud/azure-v2/compute/client/servers.js b/lib/pkgcloud/azure-v2/compute/client/servers.js index 8ad5b2787..aef26b036 100644 --- a/lib/pkgcloud/azure-v2/compute/client/servers.js +++ b/lib/pkgcloud/azure-v2/compute/client/servers.js @@ -6,7 +6,6 @@ */ var async = require('async'); var errs = require('errs'); -var _ = require('lodash'); var resourceManagement = require('azure-arm-resource'); var ComputeManagementClient = require('azure-arm-compute'); @@ -27,7 +26,9 @@ function getVersion(callback) { */ function getLimits(callback) { return errs.handle( - errs.create({ message: 'Azure\'s API is not rate limited' }), + errs.create({ + message: 'Azure\'s API is not rate limited' + }), callback ); } @@ -40,19 +41,17 @@ function getLimits(callback) { function getServers(callback) { var self = this; - self.login(function (err) { + self.login(err => { if (err) { return callback(err); } var client = new ComputeManagementClient(self.azure.credentials, self.config.subscriptionId); - client.virtualMachines.list(self.config.resourceGroup, function (err, results) { - return err - ? callback(err) - : callback(null, results.map(function (res) { - return new self.models.Server(self, res); - })); + client.virtualMachines.list(self.config.resourceGroup, (err, results) => { + return err ? + callback(err) : + callback(null, results.map(res => new self.models.Server(self, res))); }); }); } @@ -62,53 +61,60 @@ function getServers(callback) { * @param {Server|String} server Server id or a server * @param {Function} callback cb(err, serverId). */ -function getServer(server, callback) { - var self = this; +function getServer(server, hostname, callback) { + var self = this; var serverId = server instanceof self.models.Server ? server.name : server; - self.login(function (err) { + if (typeof hostname === 'function' && typeof callback === 'undefined') { + callback = hostname; + hostname = null; + } + + self.login(err => { if (err) { return callback(err); } var client = new ComputeManagementClient(self.azure.credentials, self.config.subscriptionId); - + // This will ensure returning of instances running status - var options = { expand: 'instanceView' }; - client.virtualMachines.get(self.config.resourceGroup, serverId, options, function (err, result) { + var options = { + expand: 'instanceView' + }; + client.virtualMachines.get(self.config.resourceGroup, serverId, options, (err, result) => { if (err) { return callback(err); } // Get public dns url - if (!result.networkProfile || - !result.networkProfile.networkInterfaces || - !result.networkProfile.networkInterfaces.length) { + if (!result.networkProfile || + !result.networkProfile.networkInterfaces || + !result.networkProfile.networkInterfaces.length) { return callback(null, new self.models.Server(self, result)); } var networkInterfaceId = result.networkProfile.networkInterfaces[0].id; var resourceClient = new resourceManagement.ResourceManagementClient(self.azure.credentials, self.config.subscriptionId); - - resourceClient.resources.getById(networkInterfaceId, constants.DEFAULT_API_VERSION, function (err, networkInterface) { + + resourceClient.resources.getById(networkInterfaceId, constants.DEFAULT_API_VERSION, (err, networkInterface) => { if (err) { return callback(err); } if (!networkInterface.properties.ipConfigurations || - !networkInterface.properties.ipConfigurations.length || - !networkInterface.properties.ipConfigurations[0] || - !networkInterface.properties.ipConfigurations[0].properties || - !networkInterface.properties.ipConfigurations[0].properties.publicIPAddress || - !networkInterface.properties.ipConfigurations[0].properties.publicIPAddress.id) { + !networkInterface.properties.ipConfigurations.length || + !networkInterface.properties.ipConfigurations[0] || + !networkInterface.properties.ipConfigurations[0].properties || + !networkInterface.properties.ipConfigurations[0].properties.publicIPAddress || + !networkInterface.properties.ipConfigurations[0].properties.publicIPAddress.id) { return callback(null, new self.models.Server(self, result)); } var publicIPID = networkInterface.properties.ipConfigurations[0].properties.publicIPAddress.id; - resourceClient.resources.getById(publicIPID, constants.DEFAULT_API_VERSION, function (err, publicIP) { + resourceClient.resources.getById(publicIPID, constants.DEFAULT_API_VERSION, (err, publicIP) => { if (err) { return callback(err); } @@ -143,71 +149,110 @@ function createServer(options, callback) { if (!options.name || !options.username || !options.password) { return errs.handle( - errs.create({ message: 'Please provide a name for the vm, as well as the username and password for login' }), + errs.create({ + message: 'Please provide a name for the vm, as well as the username and password for login' + }), callback ); } if (!options.flavor) { return errs.handle( - errs.create({ message: 'When creating an azure server a flavor or an image need to be supplied' }), + errs.create({ + message: 'When creating an azure server a flavor or an image need to be supplied' + }), callback ); } - var adjustVMTemplate = function (template) { - - var vmIndex = _.findIndex(template.resources, { 'type': 'Microsoft.Compute/virtualMachines' }); - - // Adding additional data disks - if (options.storageDataDiskNames && options.storageDataDiskNames.length) { - options.storageDataDiskNames.forEach(function (ddName, idx) { - template.resources[vmIndex].properties.storageProfile.dataDisks.push({ - 'name': 'datadisk' + idx.toString(), - 'diskSizeGB': '100', - 'lun': 0, - 'vhd': { - 'uri': '[concat(reference(concat(\'Microsoft.Storage/storageAccounts/\', variables(\'storageAccountName\')), \'2016-01-01\').primaryEndpoints.blob, parameters(\'storageContainerName\'),\'/\', \'' + ddName + '\', \'.vhd\')]' - }, - 'createOption': 'Empty' - }); - }); + var templateName = 'compute' + (options.imageSourceUri ? '-from-image' : ''); + self.deploy(templateName, options, (err, result) => { + + if (err) { + return callback(err); + } + + var hostname = null; + var location = null; + var vmID = null; + if (result && + result.properties && + result.properties.outputs && + result.properties.outputs.hostname && + result.properties.outputs.hostname.value && + result.properties.outputs.location && + result.properties.outputs.location.value && + result.properties.outputs.vmID && + result.properties.outputs.vmID.value) { + hostname = result.properties.outputs.hostname.value; + location = result.properties.outputs.location.value; + vmID = result.properties.outputs.vmID.value; + } else { + return callback(new Error('Result was not in the correct format: ' + JSON.stringify(result || {}))); } - // If this is a windows machine, add an extension that enables remote connection via WinRM if (options.osType === 'Windows') { - template.resources[vmIndex].resources = [{ - 'type': 'Microsoft.Compute/virtualMachines/extensions', - 'name': '[concat(variables(\'vmName\'),\'/WinRMCustomScriptExtension\')]', - 'apiVersion': constants.DEFAULT_API_VERSION, - 'location': '[resourceGroup().location]', - 'dependsOn': [ - '[concat(\'Microsoft.Compute/virtualMachines/\', variables(\'vmName\'))]' - ], - 'properties': { - 'publisher': 'Microsoft.Compute', - 'type': 'CustomScriptExtension', - 'typeHandlerVersion': '1.4', - 'settings': { - 'fileUris': [ - 'https://raw.githubusercontent.com/Azure/azure-quickstart-templates/master/201-vm-winrm-windows/ConfigureWinRM.ps1', - 'https://raw.githubusercontent.com/Azure/azure-quickstart-templates/master/201-vm-winrm-windows/makecert.exe', - 'https://raw.githubusercontent.com/Azure/azure-quickstart-templates/master/201-vm-winrm-windows/winrmconf.cmd' - ], - 'commandToExecute': '[concat(\'powershell -ExecutionPolicy Unrestricted -file ConfigureWinRM.ps1 \',variables(\'hostDNSNameScriptArgument\'))]' + var hostnameSuffix = hostname.substring(hostname.indexOf('.') + 1); + var extensionId = vmID + "/extensions/WinRMCustomScriptExtension"; + var resourceClient = new resourceManagement.ResourceManagementClient(self.azure.credentials, self.config.subscriptionId); + return resourceClient.resources.createOrUpdateById( + extensionId, + constants.DEFAULT_API_VERSION, { + "location": location, + "properties": { + "publisher": "Microsoft.Compute", + "typeHandlerVersion": "1.8", + "type": "CustomScriptExtension", + "settings": { + "fileUris": ["https://pluginsstorage.blob.core.windows.net/agentscripts/ssh.ps1"], + "commandToExecute": `powershell -File ssh.ps1 ${hostname}\\${options.username} ${options.password}` + }, + "protectedSettings": { + "storageAccountName": 'pluginsstorage', + "storageAccountKey": 'bHabDjY34dXwITjXEasmQxI84QinJqiBZHiU+Vc1dqLNSKQxvFrZbVsfDshPriIB+XIaFVaQ2R3ua1YMDYYfHw==' + }, + } + }, + (err, result) => { + return err ? + callback(err) : + self.getServer(options.name, hostname, callback); + } + ); + } else { // Linux + var extensionId = vmID + "/extensions/LinuxCustomScriptExtension"; + var resourceClient = new resourceManagement.ResourceManagementClient(self.azure.credentials, self.config.subscriptionId); + return resourceClient.resources.createOrUpdateById( + extensionId, + constants.DEFAULT_API_VERSION, { + "location": location, + "properties": { + "publisher": "Microsoft.OSTCExtensions", + "typeHandlerVersion": "1.5", + "type": "CustomScriptForLinux", + "settings": { + "fileUris": [ + "https://pluginsstorage.blob.core.windows.net/agentscripts/sudo.sh" + ], + "commandToExecute": 'bash sudo.sh ' + options.username + }, + "protectedSettings": { + "storageAccountName": 'pluginsstorage', + "storageAccountKey": 'bHabDjY34dXwITjXEasmQxI84QinJqiBZHiU+Vc1dqLNSKQxvFrZbVsfDshPriIB+XIaFVaQ2R3ua1YMDYYfHw==' + }, } + }, + (err, result) => { + return err ? + callback(err) : + self.getServer(options.name, hostname, callback); } - }]; + ); } - return template; - }; - - var templateName = 'compute' + (options.imageSourceUri ? '-from-image' : ''); - self.deploy(templateName, options, adjustVMTemplate, function (err) { return err ? callback(err) : - self.getServer(options.name, callback); + self.getServer(options.name, hostname, callback); }); } @@ -236,57 +281,57 @@ function destroyServer(server, options, callback) { var serverDetails; var nicsIds; var nicsDetails; - + var vnets; var publicIPs; async.waterfall([ - function (next) { + (next) => { self.login(next); }, - function (credentials, next) { + (credentials, next) => { self.getServer(serverId, next); }, - function (_server, next) { + (_server, next) => { serverDetails = _server; next(); }, - function (next) { + (next) => { // Deleting the vm resourceClient = new resourceManagement.ResourceManagementClient(self.azure.credentials, self.config.subscriptionId); var client = new ComputeManagementClient(self.azure.credentials, self.config.subscriptionId); - client.virtualMachines.deleteMethod(self.config.resourceGroup, serverId, next); + client.virtualMachines.deleteMethod(self.config.resourceGroup, serverId, next); } - ], function (err) { + ], (err) => { if (err) { return callback(err); } if (!options.destroyNics && - !options.destroyPublicIP && - !options.destroyVnet && - !options.destroyStorage) { + !options.destroyPublicIP && + !options.destroyVnet && + !options.destroyStorage) { return callback(); } async.waterfall([ - function (next) { + (next) => { // Deleting the nics - nicsIds = serverDetails && - serverDetails.azure && - serverDetails.azure.networkProfile && - serverDetails.azure.networkProfile.networkInterfaces || []; + nicsIds = serverDetails && + serverDetails.azure && + serverDetails.azure.networkProfile && + serverDetails.azure.networkProfile.networkInterfaces || []; // Go over all nics, get their details and go on to delete them - async.eachSeries(nicsIds, function (nic, cb) { + async.eachSeries(nicsIds, (nic, cb) => { nicsDetails = []; async.waterfall([ - function (nx) { + (nx) => { resourceClient.resources.getById(nic.id, constants.MANAGEMENT_API_VERSION, nx); }, - function (nicDetails, request, response, nx) { + (nicDetails, request, response, nx) => { nicsDetails.push(nicDetails); if (options.destroyNics) { @@ -297,16 +342,16 @@ function destroyServer(server, options, callback) { }, next); }, - function (next) { + (next) => { // Collecting public ips and vnet ids publicIPs = []; vnets = []; - nicsDetails.forEach(function (nic) { + nicsDetails.forEach((nic) => { var configs = nic && nic.properties && nic.properties.ipConfigurations || []; // Collecting - configs.forEach(function (config) { + configs.forEach((config) => { var props = config && config.properties || {}; if (props.publicIPAddress && props.publicIPAddress.id) { publicIPs.push(props.publicIPAddress.id); @@ -320,40 +365,40 @@ function destroyServer(server, options, callback) { }); next(); }, - function (next) { + (next) => { if (!options.destroyPublicIP) { return next(); } // Deleting public ips - async.eachSeries(publicIPs, function (publicIP, cb) { + async.eachSeries(publicIPs, (publicIP, cb) => { resourceClient.resources.deleteById(publicIP, constants.MANAGEMENT_API_VERSION, cb); - }, next); + }, next); }, - function (next) { + (next) => { if (!options.destroyVnet) { return next(); } // Deleting vnets - async.eachSeries(vnets, function (vnet, cb) { + async.eachSeries(vnets, (vnet, cb) => { resourceClient.resources.deleteById(vnet, constants.MANAGEMENT_API_VERSION, cb); - }, next); + }, next); }, - function (next) { + (next) => { // Deleting storage account if (!options.destroyStorage) { return next(); } - var storageUri = serverDetails && - serverDetails.azure && - serverDetails.azure.storageProfile && - serverDetails.azure.storageProfile.osDisk && - serverDetails.azure.storageProfile.osDisk.vhd && - serverDetails.azure.storageProfile.osDisk.vhd.uri || null; + var storageUri = serverDetails && + serverDetails.azure && + serverDetails.azure.storageProfile && + serverDetails.azure.storageProfile.osDisk && + serverDetails.azure.storageProfile.osDisk.vhd && + serverDetails.azure.storageProfile.osDisk.vhd.uri || null; if (!storageUri || !storageUri.startsWith('https://')) { return next(); @@ -364,17 +409,15 @@ function destroyServer(server, options, callback) { // Presuming the storage account is in the same resource group as the vm resourceClient.resources.deleteMethod( - self.config.resourceGroup, - 'Microsoft.Storage', - 'storageAccounts', - storageName, + self.config.resourceGroup, + 'Microsoft.Storage', + 'storageAccounts', + storageName, '', '2016-01-01', next); } - ], function (error) { - callback(error, serverDetails); - }); + ], callback); }); - + } /** @@ -386,17 +429,17 @@ function stopServer(server, callback) { var self = this; var serverId = server instanceof self.models.Server ? server.id : server; - self.login(function (err) { + self.login(err => { if (err) { return callback(err); } var client = new ComputeManagementClient(self.azure.credentials, self.config.subscriptionId); - client.virtualMachines.powerOff(self.config.resourceGroup, serverId, function (err) { - return err - ? callback(err) - : callback(null, serverId); + client.virtualMachines.powerOff(self.config.resourceGroup, serverId, (err) => { + return err ? + callback(err) : + callback(null, serverId); }); }); } @@ -410,17 +453,17 @@ function rebootServer(server, callback) { var self = this; var serverId = server instanceof self.models.Server ? server.id : server; - self.login(function (err) { + self.login(err => { if (err) { return callback(err); } var client = new ComputeManagementClient(self.azure.credentials, self.config.subscriptionId); - client.virtualMachines.restart(self.config.resourceGroup, serverId, function (err) { - return err - ? callback(err) - : callback(null, serverId); + client.virtualMachines.restart(self.config.resourceGroup, serverId, (err) => { + return err ? + callback(err) : + callback(null, serverId); }); }); } @@ -432,19 +475,21 @@ function rebootServer(server, callback) { */ function renameServer(server, callback) { return errs.handle( - errs.create({ message: 'Not supported by Azure.' }), + errs.create({ + message: 'Not supported by Azure.' + }), callback ); } module.exports = { - getVersion: getVersion, - getLimits: getLimits, - getServers: getServers, - getServer: getServer, - createServer: createServer, - destroyServer: destroyServer, - stopServer: stopServer, - rebootServer: rebootServer, - renameServer: renameServer -}; + getVersion, + getLimits, + getServers, + getServer, + createServer, + destroyServer, + stopServer, + rebootServer, + renameServer +}; \ No newline at end of file diff --git a/lib/pkgcloud/azure-v2/templates/arm-compute.json b/lib/pkgcloud/azure-v2/templates/arm-compute.json index 990ae8c9a..7020467b6 100644 --- a/lib/pkgcloud/azure-v2/templates/arm-compute.json +++ b/lib/pkgcloud/azure-v2/templates/arm-compute.json @@ -2,31 +2,88 @@ "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", "contentVersion": "1.0.0.0", "parameters": { - "name": { "type": "string" }, - "flavor": { "type": "string", "defaultValue": "Standard_A1" }, - "username": { "type": "string" }, - "password": { "type": "securestring" }, + "name": { + "type": "string" + }, + "flavor": { + "type": "string", + "defaultValue": "Standard_A1" + }, + "username": { + "type": "string" + }, + "password": { + "type": "securestring" + }, - "storageAccountName": { "type": "string", "defaultValue": "_NONE_" }, - "storageAccountType": { "type": "string", "defaultValue": "Standard_LRS" }, - "storageContainerName": { "type": "string", "defaultValue": "vhds" }, - "storageOSDiskName": { "type": "string", "defaultValue": "osdisk" }, + "storageAccountName": { + "type": "string", + "defaultValue": "_NONE_" + }, + "storageAccountType": { + "type": "string", + "defaultValue": "Standard_LRS" + }, + "storageContainerName": { + "type": "string", + "defaultValue": "vhds" + }, + "storageOSDiskName": { + "type": "string", + "defaultValue": "osdisk" + }, - "publicIPAddressName": { "type": "string", "defaultValue": "_NONE_" }, - "publicIPAddressType": { "type": "string", "defaultValue": "Dynamic" }, - "dnsLabelPrefix": { "type": "string", "defaultValue": "_NONE_"}, + "publicIPAddressName": { + "type": "string", + "defaultValue": "_NONE_" + }, + "publicIPAddressType": { + "type": "string", + "defaultValue": "Dynamic" + }, + "dnsLabelPrefix": { + "type": "string", + "defaultValue": "_NONE_" + }, - "vnetName": { "type": "string", "defaultValue": "_NONE_" }, - "vnetAddressPrefix": { "type": "string", "defaultValue": "10.0.0.0/16" }, - "vnetSubnetName": { "type": "string", "defaultValue": "Subnet" }, - "vnetSubnetPrefix": { "type": "string", "defaultValue": "10.0.0.0/24" }, + "vnetName": { + "type": "string", + "defaultValue": "_NONE_" + }, + "vnetAddressPrefix": { + "type": "string", + "defaultValue": "10.0.0.0/16" + }, + "vnetSubnetName": { + "type": "string", + "defaultValue": "Subnet" + }, + "vnetSubnetPrefix": { + "type": "string", + "defaultValue": "10.0.0.0/24" + }, - "nicName": { "type": "string", "defaultValue": "_NONE_" }, + "nicName": { + "type": "string", + "defaultValue": "_NONE_" + }, - "imagePublisher": { "type": "string", "defaultValue": "Canonical" }, - "imageOffer": { "type": "string", "defaultValue": "UbuntuServer" }, - "imageSku": { "type": "string", "defaultValue": "16.04.0-LTS" }, - "imageVersion": { "type": "string", "defaultValue": "latest" } + "imagePublisher": { + "type": "string", + "defaultValue": "Canonical" + }, + "imageOffer": { + "type": "string", + "defaultValue": "UbuntuServer" + }, + "imageSku": { + "type": "string", + "defaultValue": "16.04.0-LTS" + }, + "imageVersion": { + "type": "string", + "defaultValue": "latest" + } }, "variables": { "vmName": "[parameters('name')]", @@ -37,13 +94,11 @@ "dnsLabelPrefix": "[replace(parameters('dnsLabelPrefix'), '_NONE_', concat(variables('vmName'), '-vmdns'))]", "vnetName": "[replace(parameters('vnetName'), '_NONE_', concat(variables('vmName'), '-vnet'))]", "nicName": "[replace(parameters('nicName'), '_NONE_', concat(variables('vmName'), '-nic'))]", - + "vnetID": "[resourceId('Microsoft.Network/virtualNetworks',variables('vnetName'))]", - "subnetRef": "[concat(variables('vnetID'),'/subnets/',parameters('vnetSubnetName'))]", - "hostDNSNameScriptArgument": "[concat('*.',resourceGroup().location,'.cloudapp.azure.com')]" + "subnetRef": "[concat(variables('vnetID'),'/subnets/',parameters('vnetSubnetName'))]" }, - "resources": [ - { + "resources": [{ "type": "Microsoft.Storage/storageAccounts", "name": "[variables('storageAccountName')]", "apiVersion": "2016-01-01", @@ -77,14 +132,12 @@ "[parameters('vnetAddressPrefix')]" ] }, - "subnets": [ - { - "name": "[parameters('vnetSubnetName')]", - "properties": { - "addressPrefix": "[parameters('vnetSubnetPrefix')]" - } + "subnets": [{ + "name": "[parameters('vnetSubnetName')]", + "properties": { + "addressPrefix": "[parameters('vnetSubnetPrefix')]" } - ] + }] } }, { @@ -97,20 +150,18 @@ "[resourceId('Microsoft.Network/virtualNetworks/', variables('vnetName'))]" ], "properties": { - "ipConfigurations": [ - { - "name": "ipconfig1", - "properties": { - "privateIPAllocationMethod": "Dynamic", - "publicIPAddress": { - "id": "[resourceId('Microsoft.Network/publicIPAddresses',variables('publicIPAddressName'))]" - }, - "subnet": { - "id": "[variables('subnetRef')]" - } + "ipConfigurations": [{ + "name": "ipconfig1", + "properties": { + "privateIPAllocationMethod": "Dynamic", + "publicIPAddress": { + "id": "[resourceId('Microsoft.Network/publicIPAddresses',variables('publicIPAddressName'))]" + }, + "subnet": { + "id": "[variables('subnetRef')]" } } - ] + }] } }, { @@ -145,15 +196,12 @@ }, "caching": "ReadWrite", "createOption": "FromImage" - }, - "dataDisks": [] + } }, "networkProfile": { - "networkInterfaces": [ - { - "id": "[resourceId('Microsoft.Network/networkInterfaces',variables('nicName'))]" - } - ] + "networkInterfaces": [{ + "id": "[resourceId('Microsoft.Network/networkInterfaces',variables('nicName'))]" + }] }, "diagnosticsProfile": { "bootDiagnostics": {