From bcbccbf162278c8c3f662fcf18f97e25327741e5 Mon Sep 17 00:00:00 2001 From: morsh Date: Mon, 12 Dec 2016 17:25:18 +0200 Subject: [PATCH 01/41] initial checkin azure v2 --- .vscode/launch.json | 29 + examples/compute/azure-v2.js | 63 ++ lib/pkgcloud.js | 1 + lib/pkgcloud/azure-v2/client.js | 54 ++ .../azure-v2/compute/client/flavors.js | 44 ++ .../azure-v2/compute/client/images.js | 121 +++ lib/pkgcloud/azure-v2/compute/client/index.js | 103 +++ lib/pkgcloud/azure-v2/compute/client/keys.js | 98 +++ .../azure-v2/compute/client/servers.js | 195 +++++ lib/pkgcloud/azure-v2/compute/flavor.js | 36 + lib/pkgcloud/azure-v2/compute/image.js | 22 + lib/pkgcloud/azure-v2/compute/index.js | 15 + lib/pkgcloud/azure-v2/compute/server.js | 61 ++ lib/pkgcloud/azure-v2/index.js | 8 + lib/pkgcloud/azure-v2/utils/_azureApi.js | 709 ++++++++++++++++++ lib/pkgcloud/azure-v2/utils/azureApiV2.js | 260 +++++++ .../azure-v2/utils/templates/index.js | 42 ++ .../azure-v2/utils/templates/vm.identity.json | 6 + .../azure-v2/utils/templates/vm.params.json | 37 + package.json | 2 + 20 files changed, 1906 insertions(+) create mode 100644 .vscode/launch.json create mode 100644 examples/compute/azure-v2.js create mode 100644 lib/pkgcloud/azure-v2/client.js create mode 100644 lib/pkgcloud/azure-v2/compute/client/flavors.js create mode 100644 lib/pkgcloud/azure-v2/compute/client/images.js create mode 100644 lib/pkgcloud/azure-v2/compute/client/index.js create mode 100644 lib/pkgcloud/azure-v2/compute/client/keys.js create mode 100644 lib/pkgcloud/azure-v2/compute/client/servers.js create mode 100644 lib/pkgcloud/azure-v2/compute/flavor.js create mode 100644 lib/pkgcloud/azure-v2/compute/image.js create mode 100644 lib/pkgcloud/azure-v2/compute/index.js create mode 100644 lib/pkgcloud/azure-v2/compute/server.js create mode 100644 lib/pkgcloud/azure-v2/index.js create mode 100644 lib/pkgcloud/azure-v2/utils/_azureApi.js create mode 100644 lib/pkgcloud/azure-v2/utils/azureApiV2.js create mode 100644 lib/pkgcloud/azure-v2/utils/templates/index.js create mode 100644 lib/pkgcloud/azure-v2/utils/templates/vm.identity.json create mode 100644 lib/pkgcloud/azure-v2/utils/templates/vm.params.json diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 000000000..47dca7065 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,29 @@ +{ + // Use IntelliSense to learn about possible Node.js debug attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "Launch Program", + "program": "${workspaceRoot}\\lib\\pkgcloud", + "cwd": "${workspaceRoot}" + }, + { + "type": "node", + "request": "launch", + "name": "Launch Azure V2", + "program": "${workspaceRoot}\\examples\\compute\\azure-v2.js", + "cwd": "${workspaceRoot}", + "stopOnEntry": true + }, + { + "type": "node", + "request": "attach", + "name": "Attach to Process", + "port": 5858 + } + ] +} \ No newline at end of file diff --git a/examples/compute/azure-v2.js b/examples/compute/azure-v2.js new file mode 100644 index 000000000..58d2451d5 --- /dev/null +++ b/examples/compute/azure-v2.js @@ -0,0 +1,63 @@ +var pkgcloud = require('../../lib/pkgcloud'), + fs = require('fs'), + client, + options; + +// +// Create a pkgcloud compute instance +// +options = { + resourceGroup: '{resourceGroup}', + provider: 'azure-v2', + storageAccount: '{storeName}', + storageAccessKey: '{storeKey}', + subscriptionId: '{subscriptionId}', + spClientId: "{spClientId}", + spSecret: "{spSecret}", + spDomain: "{spDomain}", + spSubscriptionId: "{spSubscriptionId}" +}; +client = pkgcloud.compute.createClient(options); + +// +// Create a server. +// This may take several minutes. +// +options = { + // pkgcloud compute properties + name: 'ms-pkgc-test2', // name of the server + flavor: 'Standard_D1', // azure vm size + //image: '5112500ae3b842c8b9c604889f8753c3__OpenLogic-CentOS63DEC20121220', // OS Image to use + image: { + uri: 'https://{storename}.blob.core.windows.net/osdiks/ms-pkgc-test-os2.vhd', + OS: 'linux' + }, + nic: '/subscriptions/{subscriptionId}/resourceGroups/{resourceGroup}/providers/Microsoft.Network/networkInterfaces/{nicName}', + + // Azure vm properties + location: 'West Europe', // Azure location for server + username: 'pkgcloud', // Username for server + password: 'Pkgcloud!!' // Password for server +}; + +console.log('creating server...'); + +client.createServer(options, function (err, server) { + if (err) { + console.log(err); + } else { + // Wait for the server to reach the RUNNING state. + // This may take several minutes. + console.log('waiting for server RUNNING state...'); + server.setWait({ status: server.STATUS.running }, 10000, function (err, server) { + if (err) { + console.log(err); + } else { + console.dir(server); + } + }); + } +}); + + + diff --git a/lib/pkgcloud.js b/lib/pkgcloud.js index b70995991..927a7d693 100644 --- a/lib/pkgcloud.js +++ b/lib/pkgcloud.js @@ -21,6 +21,7 @@ var components = [ var providers = [ 'amazon', 'azure', + 'azure-v2', 'digitalocean', 'google', 'iriscouch', diff --git a/lib/pkgcloud/azure-v2/client.js b/lib/pkgcloud/azure-v2/client.js new file mode 100644 index 000000000..d48920f55 --- /dev/null +++ b/lib/pkgcloud/azure-v2/client.js @@ -0,0 +1,54 @@ +/* + * client.js: Base client from which all Azure clients inherit from + * + * (C) Microsoft Open Technologies, Inc. All rights reserved. + * + */ + +var util = require('util'), + base = require('../core/base'); + +var Client = exports.Client = function (options) { + base.Client.call(this, options); + + options = options || {}; + + // Allow overriding serversUrl in child classes + this.provider = 'azure-v2'; + this.protocol = options.protocol || 'https://'; + + if (!this.before) { + this.before = []; + } +}; + +util.inherits(Client, base.Client); + +Client.prototype._toArray = function toArray(obj) { + if (typeof obj === 'undefined') { + return []; + } + + return Array.isArray(obj) ? obj : [obj]; +}; + +Client.prototype.failCodes = { + 400: 'Bad Request', + 401: 'Unauthorized', + 403: 'Forbidden', + 404: 'Item not found', + 409: 'Already exists or in progress', + 412: 'Lease error', + 413: 'Request Entity Too Large', + 415: 'Bad Media Type', + 500: 'Fault', + 503: 'Service Unavailable' +}; + +Client.prototype.successCodes = { + 200: 'OK', + 201: 'Created', + 202: 'Accepted', + 203: 'Non-authoritative information', + 204: 'No content' +}; diff --git a/lib/pkgcloud/azure-v2/compute/client/flavors.js b/lib/pkgcloud/azure-v2/compute/client/flavors.js new file mode 100644 index 000000000..144d3e1f4 --- /dev/null +++ b/lib/pkgcloud/azure-v2/compute/client/flavors.js @@ -0,0 +1,44 @@ +/* + * flavors.js: Implementation of Azure Flavors Client. + * + * (C) Microsoft Open Technologies, Inc. + * + */ + +var pkgcloud = require('../../../../../lib/pkgcloud'), + base = require('../../../core/compute'), + compute = pkgcloud.providers.azure.compute; + +// +// ### function getFlavors (callback) +// #### @callback {function} f(err, flavors). `flavors` is an array that +// represents the flavors that are available to your account +// +// Lists all flavors available to your account. +// +exports.getFlavors = function getFlavors(callback) { + var self = this; + + callback(null, Object.keys(compute.Flavor.options).map(function (name) { + return new compute.Flavor(self, { name: name }); + })); +}; + +// +// ### function getFlavor (flavor, callback) +// #### @image {Flavor|String} Flavor ID or an Flavor +// #### @callback {function} f(err, flavor). `flavor` is an object that +// represents the flavor that was retrieved. +// +// Gets a specified flavor of AWS DataSets using the provided details +// object. +// +exports.getFlavor = function getFlavor(flavor, callback) { + var flavorId = flavor instanceof base.Flavor ? flavor.id : flavor; + + if (flavor instanceof base.Flavor) { + return callback(null, flavor); + } + + callback(null, new compute.Flavor(this, { id : flavorId })); +}; diff --git a/lib/pkgcloud/azure-v2/compute/client/images.js b/lib/pkgcloud/azure-v2/compute/client/images.js new file mode 100644 index 000000000..a6b9c6cf8 --- /dev/null +++ b/lib/pkgcloud/azure-v2/compute/client/images.js @@ -0,0 +1,121 @@ +/* + * images.js: Implementation of Azure Images Client. + * + * (C) Microsoft Open Technologies, Inc. + * + */ +var pkgcloud = require('../../../../../lib/pkgcloud'), + base = require('../../../core/compute'), + compute = pkgcloud.providers.azure.compute, + azureApi = require('../../utils/azureApiV2'); + +// +// ### function getImages (callback) +// #### @callback {function} f(err, images). `images` is an array that +// represents the images that are available to your account +// +// Lists all images available to your account. +// +exports.getImages = function getImages(options, callback) { + if (!callback && typeof options === 'function') { + callback = options; + options = null; + } + + var path = this.config.subscriptionId + '/services/images', + self = this; + + return this.get(path, function (err, body, res) { + return err + ? callback(err) + : callback(null, self._toArray(body.OSImage).map(function (image) { + return new compute.Image(self, image); + }), res); + }); +}; + +// ### function getImage (image, callback) +// #### @image {Image|String} Image id or an Image +// #### @callback {function} f(err, image). `image` is an object that +// represents the image that was retrieved. +// +// Gets a specified image of Azure using the provided details +// object. +// +exports.getImage = function getImage(image, callback) { + var self = this, + imageId = image instanceof base.Image ? image.id : image, + path = this.config.subscriptionId + '/services/images/' + imageId; + + this.get(path, function (err, body, res) { + + if (err) { + return callback(err); + } + + var result = null; + if (body) { + result = new compute.Image(self, body); + } + + return result + ? callback(null, result, res) + : callback(new Error('Image not found')); + }); +}; + +// +// ### function createImage(options, callback) +// #### @id {Object} an object literal with options +// #### @name {String} String name of the image +// #### @server {Server} the server to use +// #### @callback {function} f(err, image). `image` is an object that +// represents the image that was created. +// +// Creates an image in Azure based on a server +// +exports.createImage = function createImage(options, callback) { + options || (options = {}); + + if (!options.name) { + throw new TypeError('`name` is a required option'); + } + + if (!options.server) { + throw new TypeError('`server` is a required option'); + } + + var self = this, + serverId = options.server instanceof base.Server + ? options.server.id + : options.server; + + azureApi.createImage(this, serverId, options.name, function (err, result) { + return !err + ? self.getImage(result, callback) + : callback(err); + }); +}; + +// +// ### function destroyImage(image, callback) +// #### @image {Image|String} Image id or an Image +// #### @callback {function} f(err, image). `image` is an object that +// represents the image that was deleted. +// +// Destroys an image in Azure +// +exports.destroyImage = function destroyImage(image, callback) { + var self = this, + imageId = image instanceof base.Image ? image.id : image, + path = self.config.subscriptionId + '/services/images/' + imageId; + + self._xmlRequest({ + method: 'DELETE', + path: path + }, function (err, body, res) { + return err + ? callback(err) + : callback(null, { ok: imageId }, res); + }); +}; diff --git a/lib/pkgcloud/azure-v2/compute/client/index.js b/lib/pkgcloud/azure-v2/compute/client/index.js new file mode 100644 index 000000000..ffd13964d --- /dev/null +++ b/lib/pkgcloud/azure-v2/compute/client/index.js @@ -0,0 +1,103 @@ +/* + * index.js: Compute client for Azure + * + * (C) Microsoft Open Technologies, Inc. + * + */ + +var util = require('util'), + urlJoin = require('url-join'), + https = require('https'), + auth = require('../../../common/auth'), + azureApi = require('../../utils/azureApiV2.js'), + //xml2JSON = require('../../utils/xml2json.js').xml2JSON, + azure = require('../../client'), + _ = require('lodash'); + +var Client = exports.Client = function (options) { + azure.Client.call(this, options); + + _.extend(this, require('./flavors')); + _.extend(this, require('./images')); + _.extend(this, require('./servers')); + _.extend(this, require('./keys')); + + this.serversUrl = options.serversUrl || azureApi.MANAGEMENT_ENDPOINT; + this.version = azureApi.MANAGEMENT_API_VERSION; + this.subscriptionId = this.config.subscriptionId; + + this.azureKeys = { + key: this.config.key, + cert: this.config.cert + }; + + this.azureKeys.subscriptionId = this.config.subscriptionId; + + this.before.push(auth.azure.managementSignature); + + // The https agent is used by request for authenticating TLS/SSL https calls + if (this.protocol === 'https://') { + this.before.push(function (req) { + req.agent = new https.Agent({ + host: this.serversUrl, + key: options.key, + cert: options.cert + }); + }); + } +}; + +util.inherits(Client, azure.Client); + +Client.prototype._query = function query(action, query, callback) { + return this._request({ + method: 'POST', + headers: { }, + body: _.extend({ Action: action }, query) + }, function (err, body, res) { + if (err) { return callback(err); } + xml2JSON(body, function (err, data) { + return err + ? callback(err) + : callback(data, res); + }); + }); +}; + +Client.prototype.get = function get(action, callback) { + return this._request({ path: action }, function (err, body, res) { + if (err) { + return callback(err); + } + xml2JSON(body, function (err, data) { + return err + ? callback(err) + : callback(null, data, res); + }); + }); +}; + +Client.prototype._xmlRequest = function query(options, callback) { + + return this._request(options, function (err, body, res) { + if (err) { + return callback(err); + } + xml2JSON(body, function (err, data) { + return err ? + callback(err) : + callback(null, data, res); + }); + }); +}; + +Client.prototype._getUrl = function (options) { + options = options || {}; + + return urlJoin(this.protocol + this.serversUrl + '/', + (typeof options === 'string' + ? options + : options.path)); +}; + + diff --git a/lib/pkgcloud/azure-v2/compute/client/keys.js b/lib/pkgcloud/azure-v2/compute/client/keys.js new file mode 100644 index 000000000..500008670 --- /dev/null +++ b/lib/pkgcloud/azure-v2/compute/client/keys.js @@ -0,0 +1,98 @@ +/* + * keys.js: Implementation of Azure SSH keys Client. + * + * (C) Microsoft Open Technologies, Inc. + * + */ + +var errs = require('errs'); + +// +// ### function listKeys (options, callback) +// #### @options {Object} **Optional** Filter parameters when listing keys +// #### @callback {function} Continuation to respond to when complete. +// +// Lists all EC2 Key Pairs matching the specified `options`. +// +exports.listKeys = function (options, callback) { + if (!callback && typeof options === 'function') { + callback = options; + options = {}; + } + + var self = this; + options = options || {}; + + return this._query('DescribeKeyPairs', options, function (err, body) { + return err + ? callback(err) + : callback(null, self._toArray(body.keySet.item)); + }); +}; + +// +// ### function getKey (name, callback) +// #### @name {string} Name of the EC2 key pair to get +// #### @callback {function} Continuation to respond to when complete. +// +// Gets the details of the EC2 Key Pair with the specified `name`. +// +exports.getKey = function (name, callback) { + return this.listKeys({ + 'KeyName.1': name + }, function (err, body) { + return err + ? callback(err) + : callback(null, body[0]); + }); +}; + +// +// ### function addKey (options, callback) +// #### @options {Object} SSH Public Key details +// #### @name {string} String name of the key +// #### @key {string} SSH Public Key +// #### @callback {function} Continuation to respond to when complete. +// +// Adds an EC2 Key Pair with the specified `options`. +// +exports.addKey = function (options, callback) { + if (!options || !options.key || !options.name) { + return errs.handle( + errs.create({ message: '`key` and `name` are required options.' }), + callback + ); + } + + return this._query( + 'ImportKeyPair', + { + KeyName: options.name, + PublicKeyMaterial: new Buffer(options.key).toString('base64') + }, + function (err) { + return err + ? callback(err) + : callback(null, true); + } + ); +}; + +// +// ### function getKey (name, callback) +// #### @name {string} Name of the EC2 key pair to destroy +// #### @callback {function} Continuation to respond to when complete. +// +// Destroys EC2 Key Pair with the specified `name`. +// +exports.destroyKey = function (name, callback) { + return this._query( + 'DeleteKeyPair', + { KeyName: name }, + function (err) { + return err + ? callback(err) + : callback(null, true); + } + ); +}; diff --git a/lib/pkgcloud/azure-v2/compute/client/servers.js b/lib/pkgcloud/azure-v2/compute/client/servers.js new file mode 100644 index 000000000..247b3c3da --- /dev/null +++ b/lib/pkgcloud/azure-v2/compute/client/servers.js @@ -0,0 +1,195 @@ +/* + * servers.js: Instance methods for working with servers from Azure Cloud + * + * (C) Microsoft Open Technologies, Inc. + * + */ +var base = require('../../../core/compute'), + pkgcloud = require('../../../../../lib/pkgcloud'), + errs = require('errs'), + azureApi = require('../../utils/azureApiV2'), + compute = require('../server'); + +// +// ### function getVersion (callback) +// #### @callback {function} f(err, version). +// +// Gets the current API version +// +exports.getVersion = function getVersion(callback) { + callback(null, this.version); +}; + +// +// ### function getLimits (callback) +// #### @callback {function} f(err, version). +// +// Gets the current API limits +// +exports.getLimits = function getLimits(callback) { + return errs.handle( + errs.create({ message: 'Azure\'s API is not rate limited' }), + callback + ); +}; + +// +// ### function getServers (callback) +// #### @callback {function} f(err, servers). `servers` is an array that +// represents the servers that are available to your account +// +// Lists all servers available to your account. +// +exports.getServers = function getServers(callback) { + var self = this; + + azureApi.getServers(this, function (err, results) { + if (err) { + return callback(err); + } + + callback(null, results.map(function (server) { + return new compute.Server(self, server); + })); + }); +}; + +// +// ### function getServer(server, callback) +// #### @server {Server|String} Server id or a server +// #### @callback {Function} f(err, serverId). +// +// Gets a server in Azure. +// +exports.getServer = function getServer(server, callback) { + var self = this, + serverId = server instanceof base.Server ? server.name : server; + + // azure does not like multiple server status requests + // setWait() does not wait for result of previous query before + // issuing a new query. + if (server instanceof compute.Server) { + if (server.requestPending) { + return callback(null, new compute.Server(self, server)); + } + } + + server.requestPending = true; + azureApi.getServer(this, serverId, function (err, result) { + server.requestPending = false; + return !err + ? callback(null, new compute.Server(self, result)) + : callback(err); + }); +}; + +// +// ### function createServer (options, callback) +// #### @opts {Object} **Optional** options +// #### @name {String} **Optional** the name of server +// #### @image {String|Image} the image (AMI) to use +// #### @flavor {String|Flavor} **Optional** flavor to use for this image +// #### @callback {Function} f(err, server). +// +// Creates a server with the specified options. The flavor +// properties of the options can be instances of Flavor +// OR ids to those entities in Azure. +// +exports.createServer = function createServer(options, callback) { + var self = this; + + if (typeof options === 'function') { + callback = options; + options = {}; + } + + options = options || {}; // no args + azureApi.createServer(this, options, function (err, server) { + return !err + ? callback(null, new compute.Server(self, server)) + : callback(err); + }); +}; + +// +// ### function destroyServer(server, callback) +// #### @server {Server|String} Server id or a server +// #### @callback {Function} f(err, serverId). +// +// Destroy a server in Azure. +// +exports.destroyServer = function destroyServer(server, callback) { + var serverId = server instanceof base.Server ? server.id : server; + + azureApi.destroyServer(this, serverId, function (err) { + if (callback) { + return !err + ? callback && callback(null, { ok: serverId }) + : callback && callback(err); + } + }); +}; + +// +// ### function stopServer(server, callback) +// #### @server {Server|String} Server id or a server +// #### @callback {Function} f(err, serverId). +// +// Destroy a server in Azure. +// +exports.stopServer = function stopServer(server, callback) { + var serverId = server instanceof base.Server ? server.id : server; + + azureApi.stopServer(this, serverId, function (err) { + return !err + ? callback(null, { ok: serverId }) + : callback(err); + }); +}; + +// +// ### function createHostedService(serviceName, callback) +// #### @serviceName {String} name of the Hosted Service +// #### @callback {Function} f(err, serverId). +// +// Creates a Hosted Service in Azure. +// +exports.createHostedService = function createHostedService(serviceName, callback) { + azureApi.createHostedService(this, serviceName, function (err, res) { + return !err + ? callback(null, res) + : callback(err); + }); +}; + +// +// ### function rebootServer (server, options, callback) +// #### @server {Server|String} The server to reboot +// #### @callback {Function} f(err, server). +// +// Reboots a server +// +exports.rebootServer = function rebootServer(server, callback) { + var serverId = server instanceof base.Server ? server.id : server; + + azureApi.rebootServer(this, serverId, function (err) { + return !err + ? callback(null, { ok: serverId }) + : callback(err); + }); +}; + +// +// ### function renameServer(server, name, callback) +// #### @server {Server|String} Server id or a server +// #### @name {String} New name to apply to the server +// #### @callback {Function} f(err, server). +// +// Renames a server +// +exports.renameServer = function renameServer(server, name, callback) { + return errs.handle( + errs.create({ message: 'Not supported by Azure.' }), + callback + ); +}; diff --git a/lib/pkgcloud/azure-v2/compute/flavor.js b/lib/pkgcloud/azure-v2/compute/flavor.js new file mode 100644 index 000000000..ee2f5f66d --- /dev/null +++ b/lib/pkgcloud/azure-v2/compute/flavor.js @@ -0,0 +1,36 @@ +/* + * flavor.js: Azure Cloud Package flavors + * + * (C) Microsoft Open Technologies, Inc. + * + */ + +var util = require('util'), + base = require('../../core/compute/flavor'); + +var Flavor = exports.Flavor = function Flavor(client, details) { + base.Flavor.call(this, client, details); +}; + +util.inherits(Flavor, base.Flavor); + +Flavor.options = { + 'ExtraSmall': { ram: 0.768 * 1024, disk: 20 }, + 'Small': { ram: 1.75 * 1024, disk: 50 }, + 'Medium': { ram: 3.5 * 1024, disk: 100 }, + 'Large': { ram: 7 * 1024, disk: 200 }, + 'ExtraLarge': { ram: 14 * 1024, disk: 400 } +}; + +Flavor.prototype._setProperties = function (details) { + var id = details.name || details.id || 'ExtraSmall'; + + if (!Flavor.options[id]) { + throw new TypeError('No such Azure Flavor: ' + id); + } + + this.id = id; + this.name = id; + this.ram = Flavor.options[id].ram; + this.disk = Flavor.options[id].disk; +}; diff --git a/lib/pkgcloud/azure-v2/compute/image.js b/lib/pkgcloud/azure-v2/compute/image.js new file mode 100644 index 000000000..06a1682d7 --- /dev/null +++ b/lib/pkgcloud/azure-v2/compute/image.js @@ -0,0 +1,22 @@ +/* + * image.js: Azure OS Images + * + * (C) Microsoft Open Technologies, Inc. + * + */ + +var util = require('util'), + base = require('../../core/compute/image'); + +var Image = exports.Image = function Image(client, details) { + base.Image.call(this, client, details); +}; + +util.inherits(Image, base.Image); + +Image.prototype._setProperties = function (details) { + this.id = details.Name; + this.name = details.Name; + this.created = new Date(0); + this.details = this.azure = details; +}; diff --git a/lib/pkgcloud/azure-v2/compute/index.js b/lib/pkgcloud/azure-v2/compute/index.js new file mode 100644 index 000000000..ee51ee171 --- /dev/null +++ b/lib/pkgcloud/azure-v2/compute/index.js @@ -0,0 +1,15 @@ +/* + * index.js: Top-level include for the Azure compute module + * + * (C) Microsoft Open Technologies, Inc. + * + */ + +exports.Client = require('./client').Client; +exports.Flavor = require('./flavor').Flavor; +exports.Image = require('./image').Image; +exports.Server = require('./server').Server; + +exports.createClient = function (options) { + return new exports.Client(options); +}; diff --git a/lib/pkgcloud/azure-v2/compute/server.js b/lib/pkgcloud/azure-v2/compute/server.js new file mode 100644 index 000000000..0338c0041 --- /dev/null +++ b/lib/pkgcloud/azure-v2/compute/server.js @@ -0,0 +1,61 @@ +/* + * server.js: Azure Server + * + * (C) Microsoft Open Technologies, Inc. + * + */ + +var util = require('util'); +var base = require('../../core/compute/server'); +var _ = require('lodash'); + +var Server = exports.Server = function Server(client, details) { + base.Server.call(this, client, details); + this.requestPending = false; +}; + +util.inherits(Server, base.Server); + +Server.prototype._setProperties = function (details) { + + details = details || {}; + this.id = details.id || ''; + this.name = details.name || ''; + + //console.log('Status: ' + details.Status + ' RoleInstanceList: ' + roleInstance ? roleInstance.InstanceStatus : 'UNKNOWN'); + + // azure can return an inconsistent RoleInstance status (not in azure rest api docs) so we check everything. + // an azure vm has a complicated state machine. We need to check the status of both the deployment and the role. + // azure first starts a deployment and then starts a role. The role seems to go through STOPPEDVM, PROVISIONING and then + // READYROLE. + // Note: since azureAPI has to wait until azure responds to our createServer request, we most likely will miss all of the + // deployment states unless something goes wrong + // TODO: there doesn't seem to be an ERROR or FAIL status in pkgcloud + + var statuses = details.statuses || []; + var provisioningStatus = _.find(statuses, status => status.code.startsWith('ProvisioningState/')) || {}; + var powerStateStatus = _.find(statuses, status => status.code.startsWith('PowerState/')) || {}; + + if (provisioningStatus.code == 'ProvisioningState/succeeded' && powerStateStatus.code == 'PowerState/deallocated') { + this.status = this.STATUS.stopped; + } else if (provisioningStatus.code == 'ProvisioningState/succeeded' && powerStateStatus.code == 'PowerState/running') { + this.status = this.STATUS.running; + } else { + this.status = this.STATUS.unknown; + } + + var addresses = { private: [], public: [] }; + + // TODO: Need to clean up once I understand what is private ip? + this.addresses = details.addresses = addresses; + + if (details.RoleList && details.RoleList.Role) { + if (details.RoleList.Role.OSVirtualHardDisk) { + this.imageId = details.RoleList.Role.OSVirtualHardDisk.SourceImageName; + } + } + + this.serviceName = details.serviceName || details.Name; + + this.original = this.azure = details; +}; diff --git a/lib/pkgcloud/azure-v2/index.js b/lib/pkgcloud/azure-v2/index.js new file mode 100644 index 000000000..636cd95dc --- /dev/null +++ b/lib/pkgcloud/azure-v2/index.js @@ -0,0 +1,8 @@ +/* + * index.js: Top-level include for the Azure module. + * + * (C) Microsoft Open Technologies, Inc. All rights reserved. + * + */ + +exports.compute = require('./compute'); diff --git a/lib/pkgcloud/azure-v2/utils/_azureApi.js b/lib/pkgcloud/azure-v2/utils/_azureApi.js new file mode 100644 index 000000000..8e3eb1adf --- /dev/null +++ b/lib/pkgcloud/azure-v2/utils/_azureApi.js @@ -0,0 +1,709 @@ +/** + * (C) Microsoft Open Technologies, Inc. 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. + */ + +var HeaderConstants = require('./constants').HeaderConstants; +var async = require('async'); +var templates = require('../compute/templates/templates'); +var _ = require('lodash'); +var errs = require('errs'); +var URL = require('url'); +var cert = require('../utils/cert'); +var pkgcloud = require('../../../../../pkgcloud'); + +exports.MANAGEMENT_API_VERSION = '2012-03-01'; +exports.MANAGEMENT_ENDPOINT = 'management.core.windows.net'; +var STORAGE_ENDPOINT = exports.STORAGE_ENDPOINT = 'blob.core.windows.net'; +exports.STORAGE_API_VERSION = HeaderConstants.TARGET_STORAGE_VERSION; +exports.TABLES_ENDPOINT = 'table.core.windows.net'; +exports.TABLES_API_VERSION = '2012-02-12'; +var MINIMUM_POLL_INTERVAL = exports.MINIMUM_POLL_INTERVAL = 3000; + +// Declaring variables for helper functions defined later +var createVM, createLinuxVM, createWindowsVM, validateCreateOptions, getServer, + getServers, rebootServer, stopServer, deleteHostedService, destroyServer, createImage, + captureServer, deleteImage, destroyImage, getMediaLinkUrl, createEndpoints, + makeTemplateRequest, createHostedService, addCertificate, getHostedServices, + deleteServer, getOSImage, deleteOSDisk, deleteOSBlob, getServersFromServices, + getServersFromService, isVM, getHostedServiceProperties, pollRequestStatus, + getStorageInfoFromUri; + +/** + * createServer() + * + * In order to deploy a vm, Azure requires us to do the following + * before we can actually try to create the vm. + * 1. get or create a Hosted Service (we use the same name as the vm) + * 2. resolve the OSImage url to a container on the user's account + * 3. upload SSH certificate (if necessary) + * 4. create the VM + * + * Note: creating a VM on Azure will fail if one of the following is true + * 1. The VM (with the same name) already exists + * 2. The blob storage (with the same name) for the OSImage already exists + * 3. The VM disk (with the same name) for the OSImage already exists + * 4. The storage account is in a different azure location than the vm + * (East US, West US...) + * + * Note: createServer() must wait for Azure to respond if the createDeployment (vm) + * request succeeded. createServer() asynchronously polls Azure to get + * the result. Once the result is received, the callback function will be called + * with the server information or error. The state of returned server will most likely + * be PROVISIONING or STOPPED. Use server.setWait() to continue polling the server until + * its status is RUNNING. This entire process may take several minutes. + */ + +exports.createServer = function (client, options, callback) { + var vmOptions = {}, + ssh; + + // async execute the following tasks one by one and bail if there is an error + async.waterfall([ + function (next) { + // validate createServer options + validateCreateOptions(options, client.config, next); + }, + function (next) { + getHostedServiceProperties(client, options.name, next); + }, + function (service, next) { + // if the HostedService does not exist, create it + vmOptions.hostedService = service; + if (vmOptions.hostedService === null) { + createHostedService(client, options, function (err, service) { + if (err) { + next(err); + } else { + vmOptions.hostedService = service; + next(null); + } + }); + } else { + next(null); + } + }, + function (next) { + // get the server's OSImage info + getOSImage(client, options.image, function (err, res) { + if (err) { + next(err); + } else { + vmOptions.image = res; + next(null); + } + }); + }, + function (next) { + ssh = options.ssh; + if (ssh) { + vmOptions.sshCertInfo = cert.getAzureCertInfo(ssh.cert); + } + next(); + }, + function (next) { + // add the ssh certificate to the service + if (vmOptions.sshCertInfo) { + addCertificate(client, options.name, vmOptions.sshCertInfo.cert, ssh.pemPassword, function (err) { + next(err); + }); + } else { + next(null); + } + }, + function (next) { + // create the VM and wait for response + createVM(client, options, vmOptions, next); + }, + function (next) { + // now get the actual server info + getServer(client, options.name, next); + }], + function (err, result) { + if (err) { + callback(err); + } else { + // return the server info + callback(null, result); + } + } + ); +}; + +/** + * getServer + */ +getServer = exports.getServer = function (client, serverName, callback) { + getServersFromService(client, serverName, function (err, servers) { + return !err + ? callback(err, servers[0] ? servers[0] : null) + : callback(err); + }); +}; + +getServers = exports.getServers = function (client, callback) { + // async execute the following tasks one by one and bail if there is an error + async.waterfall([ + function (next) { + // get the list of Hosted Services + getHostedServices(client, next); + }, + function (hostedServices, next) { + // get the list of Servers from the Hosted Services + getServersFromServices(client, hostedServices, next); + }], + function (err, servers) { + callback(err, servers); + } + ); +}; + +makeTemplateRequest = function (client, path, templateName, params, callback) { + var headers = {}, + body; + + // async execute the following tasks one by one and bail if there is an error + async.waterfall([ + function (next) { + templates.load(templateName, next); + }, + function (template, next) { + // compile template with params + var compiled = _.template(template); + body = compiled(params); + headers['content-length'] = body.length; + headers['content-type'] = 'application/xml'; + headers['accept'] = 'application/xml'; + client._request({ + method: 'POST', + path: path, + body: body, + headers: headers + }, function (err, body, res) { + if (err) { + return next(err); + } + // poll azure for result of request + pollRequestStatus(client, res.headers['x-ms-request-id'], MINIMUM_POLL_INTERVAL, next); + }); + }], + function (err) { + callback(err); + } + ); +}; + +createHostedService = exports.createHostedService = function (client, options, callback) { + var path = client.subscriptionId + '/services/hostedservices'; + var params = { + NAME: options.name, + LABEL_BASE64: new Buffer(options.name).toString('base64'), + LOCATION: options.location + }; + + makeTemplateRequest(client, path, 'createHostedService.xml', params, callback); +}; + +/** + * rebootServer + * uses Restart Role + * POST https://management.core.windows.net//services/hostedservices//deployments//roleinstances//operations + * A successful operation returns status code 201 (Created). Need to poll for success? + */ +rebootServer = exports.rebootServer = function (client, serviceName, callback) { + var path = client.subscriptionId + '/services/hostedservices/' + + serviceName + '/deployments/' + + serviceName + '/roleInstances/' + + serviceName + '/Operations'; + + makeTemplateRequest(client, path, 'restartRole.xml', {}, callback); +}; + +/** + * stopServer + * uses Shutdown Role + * POST https://management.core.windows.net//services/hostedservices//deployments//roleinstances//operations + * A successful operation returns status code 201 (Created). Need to poll for success? + */ +stopServer = exports.stopServer = function (client, serviceName, callback) { + var path = client.subscriptionId + '/services/hostedservices/' + + serviceName + '/deployments/' + + serviceName + '/roleInstances/' + + serviceName + '/Operations'; + + makeTemplateRequest(client, path, 'shutdownRole.xml', {}, callback); +}; + +addCertificate = function (client, serviceName, cert, password, callback) { + var path = client.subscriptionId + '/services/hostedservices/' + + serviceName + '/certificates'; + + var params = { + CERT_BASE64: new Buffer(cert, 'utf8').toString('base64'), + PASSWORD: password + }; + + makeTemplateRequest(client, path, 'addCertificate.xml', params, callback); +}; + +deleteHostedService = exports.deleteHostedService = function (client, serviceName, callback) { + // DELETE https://management.core.windows.net//services/hostedservices/ + var path = client.subscriptionId + '/services/hostedservices/' + serviceName; + + client._request({ + method: 'DELETE', + path: path + }, function (err, body, res) { + if (err) { + return callback(err); + } + // poll azure for result of request + pollRequestStatus(client, res.headers['x-ms-request-id'], MINIMUM_POLL_INTERVAL, callback); + }); +}; + +getHostedServices = exports.getHostedServices = function (client, callback) { + var path = client.subscriptionId + '/services/hostedservices', + services = []; + + client.get(path, function (err, body) { + if (err) { + return callback(err); + } + if (body.HostedService) { + // need to check if azure returned an array or single object + if (Array.isArray(body.HostedService)) { + body.HostedService.forEach(function (service) { + services.push(service); + }); + } else { + services.push(body.HostedService); + } + } + + callback(null, services); + }); +}; + +/** + * destroyServer + * uses Delete Deployment + * DELETE https://management.core.windows.net//services/hostedservices//deployments/ + * Because Delete Deployment is an asynchronous operation, it always returns status code 202 (Accept). + * To determine the status code for the operation once it is complete, call Get Operation Status. + * Because Delete Deployment is an asynchronous operation, it always returns status code 202 (Accept). + */ +destroyServer = exports.destroyServer = function (client, serverName, callback) { + var server = null; + + // async execute the following tasks one by one and bail if there is an error + async.waterfall([ + function (next) { + // get the list of Hosted Services + getServer(client, serverName, next); + }, + function (result, next) { + server = result; + // get the list of Hosted Services + stopServer(client, serverName, next); + }, + function (next) { + deleteServer(client, serverName, next); + }, + function (next) { + deleteOSDisk(client, server, next); + }, + function (next) { + deleteOSBlob(client, server, next); + }, + function (next) { + deleteHostedService(client, serverName, next); + }], + function (err) { + callback(err, true); + } + ); +}; + +deleteServer = function (client, serverName, callback) { + var path = client.subscriptionId + '/services/hostedservices/' + serverName; + path += '/deployments/' + serverName; + + client._request({ + method: 'DELETE', + path: path + }, function (err, body, res) { + if (err) { + return callback(err); + } + // poll azure for result of request + pollRequestStatus(client, res.headers['x-ms-request-id'], MINIMUM_POLL_INTERVAL, callback); + }); +}; + +getOSImage = exports.getOSImage = function (client, imageName, callback) { + var path = '/' + client.subscriptionId + '/services/images/' + imageName; + + var onError = function (err) { + if (err.failCode === 'Item not found') { + callback(null, null); + + } else { + callback(err); + } + }; + + client.get(path, function (err, body) { + return err + ? onError(err) + : callback(null, body); + }); +}; + +deleteOSDisk = function (client, server, callback) { + var diskName = null, + path; + + if (server && server.RoleList && server.RoleList.Role) { + if (server.RoleList.Role.OSVirtualHardDisk) { + diskName = server.RoleList.Role.OSVirtualHardDisk.DiskName; + } + } + + if (diskName === null) { + callback(null); + return; + } + + // https://management.core.windows.net//services/disks/ + path = client.subscriptionId + '/services/disks/' + diskName; + + client._request({ + method: 'DELETE', + path: path + }, function (err, body, res) { + if (err) { + return callback(err); + } + // poll azure for result of request + pollRequestStatus(client, res.headers['x-ms-request-id'], MINIMUM_POLL_INTERVAL, callback); + }); +}; + +deleteOSBlob = function (client, server, callback) { + var blob = null; + + if (server && server.RoleList && server.RoleList.Role) { + if (server.RoleList.Role.OSVirtualHardDisk) { + blob = server.RoleList.Role.OSVirtualHardDisk.MediaLink; + } + } + + if (blob === null) { + callback(null); + return; + } + + getStorageInfoFromUri(blob, function (err, info) { + if (err) { + callback(err); + } else { + var storage = pkgcloud.storage.createClient(client.config); + storage.removeFile(info.container, info.file, function (err) { + callback(err); + }); + } + }); +}; + +/** + * getServersFromServices + * Retrieves all servers (VMs) from the list of services + */ +getServersFromServices = function (client, services, callback) { + var task = function (service, next) { + getServersFromService(client, service.ServiceName, function (err, servers) { + next(err, servers); + }); + }; + + // Check each service for deployed VMs. + async.concat(services, task, function (err, servers) { + callback(err, servers); + }); +}; + +/** + * getServersFromServices + * Retrieves all servers (VMs) from a Hosted Service + */ +getServersFromService = function (client, serviceName, callback) { + var servers = []; + getHostedServiceProperties(client, serviceName, function (err, result) { + if (err) { + return callback(err); + } + + if (result && result.Deployments && result.Deployments.Deployment) { + if (isVM(result.Deployments.Deployment)) { + servers.push(result.Deployments.Deployment); + } + } + + callback(null, servers); + }); +}; + +isVM = function (deployment) { + if (deployment.RoleList && deployment.RoleList.Role) { + if (deployment.RoleList.Role.RoleType === 'PersistentVMRole') { + return true; + } + } + + return false; +}; + +/** + Get Hosted Service Properties + GET https://management.core.windows.net//services/hostedservices/?embed-detail=true + A successful operation returns status code 200 (OK). + */ +getHostedServiceProperties = function (client, serviceName, callback) { + var path = client.subscriptionId + '/services/hostedservices/' + serviceName + '?embed-detail=true'; + + var onError = function (err) { + return err.failCode === 'Item not found' + ? callback(null, null) + : callback(err); + }; + + client.get(path, function (err, body) { + return err + ? onError(err) + : callback(null, body); + }); +}; + +/** + * pollRequestStatus + * uses Get Operation Status + * GET https://management.core.windows.net//operations/ + */ + +pollRequestStatus = function (client, requestId, interval, callback) { + var checkStatus = function () { + var path = client.subscriptionId + '/operations/' + requestId; + client.get(path, function (err, body) { + if (err) { + return callback(err); + } + switch (body.Status) { + case 'InProgress': + setTimeout(checkStatus, interval); + break; + case 'Failed': + callback(body.Error); + break; + case 'Succeeded': + callback(null); + break; + } + }); + }; + + checkStatus(); +}; + +getStorageInfoFromUri = exports.getStorageInfoFromUri = function (uri, callback) { + var u, tokens, path, + info = {}; + + u = URL.parse(uri); + if (!u.host || !u.path) { + return callback(errs.create({message: 'invalid Azure container or blob uri'})); + } + + tokens = u.host.split('.'); + info.storage = tokens[0]; + + path = u.path; + // if necessary, remove leading '/' from path + if (path.charAt(0) === '/') { + path = path.substr(1); + } + tokens = path.split('/'); + info.container = tokens.shift(); + info.file = tokens.join('/'); + + callback(null, info); +}; + +/** + * createImage() + * 1. Check if the server exists + * 2. stop server if it is running + * 3. capture server image + */ +createImage = exports.createImage = function (client, serverName, targetImageName, callback) { + async.waterfall([ + function (next) { + // stop the server + stopServer(client, serverName, next); + }, + function (next) { + // capture the server image + captureServer(client, serverName, targetImageName, next); + }], + function (err) { + callback(err, targetImageName); + } + ); +}; + +deleteImage = function (client, image, callback) { + // https://management.core.windows.net//services/images/ + var path = client.subscriptionId + '/services/images/' + image.Name; + + var configParams = { + LABEL: image.LABEL + }; + + makeTemplateRequest(client, path, 'deleteImage.xml', configParams, callback); +}; + +/** + * destroyImage() + * 1. get the requested image + * 2. delete the image using its label + */ +destroyImage = exports.destroyImage = function (client, imageName, callback) { + async.waterfall([ + function (next) { + // stop the server + client.getImage(client, imageName, next); + }, + function (image, next) { + deleteImage(client, image, next); + }], + function (err) { + callback(err, imageName); + } + ); +}; + +createVM = function (client, options, vmOptions, callback) { + // check OS type of image to determine if we are creating a linux or windows VM + switch (vmOptions.image.OS.toLowerCase()) { + case 'linux': + createLinuxVM(client, options, vmOptions, callback); + break; + case 'windows': + createWindowsVM(client, options, vmOptions, callback); + break; + default: + callback(errs.create({message: 'Unknown Image OS: ' + vmOptions.image.OS})); + break; + } +}; + +getMediaLinkUrl = function (storageAccount, fileName) { + return 'http://' + storageAccount + '.' + STORAGE_ENDPOINT + '/vhd/' + fileName; +}; + +createEndpoints = function (ports) { + var endPoints = '', + template = templates.loadSync('endpoint.xml'); + + (ports || []).forEach(function (port) { + endPoints += templates.compileSync(template, port); + }); + return endPoints; +}; + +createLinuxVM = function (client, options, vmOptions, callback) { + var path = client.subscriptionId + '/services/hostedservices/' + options.name + '/deployments'; + var mediaLink = getMediaLinkUrl(client.config.storageAccount, options.name + '.vhd'); + var label = new Buffer(options.name).toString('base64'); + + var configParams = { + NAME: options.name, + LABEL_BASE64: label, + USERNAME: options.username, + PASSWORD: options.password, + SSH_CERTIFICATE_FINGERPRINT: vmOptions.sshCertInfo.fingerprint, + PORT: options.ssh.port || '22', + LOCAL_PORT: options.ssh.localPort || '22', + ROLESIZE: options.flavor, + ENDPOINTS: createEndpoints(options.ports), + OS_SOURCE_IMAGE_NAME: vmOptions.image.Name, + OS_IMAGE_MEDIALINK: mediaLink + }; + + makeTemplateRequest(client, path, 'linuxDeployment.xml', configParams, callback); +}; + +createWindowsVM = function (client, options, vmOptions, callback) { + var path = client.subscriptionId + '/services/hostedservices/' + options.name + '/deployments'; + var mediaLink = getMediaLinkUrl(client.config.storageAccount, options.name + '.vhd'); + var label = new Buffer(options.name).toString('base64'); + + var configParams = { + NAME: options.name, + COMPUTER_NAME: options.computerName || options.name.slice(0, 15), + LABEL_BASE64: label, + PASSWORD: options.password, + ROLESIZE: options.flavor, + ENDPOINTS: createEndpoints(options.ports), + OS_SOURCE_IMAGE_NAME: vmOptions.image.Name, + OS_IMAGE_MEDIALINK: mediaLink + }; + + makeTemplateRequest(client, path, 'windowsDeployment.xml', configParams, callback); +}; + +captureServer = function (client, serverName, targetImageName, callback) { + // /services/hostedservices//deployments//roleinstances//operations + var path = client.subscriptionId + '/services/hostedservices/' + + serverName + '/deployments/' + + serverName + '/roleInstances/' + + serverName + '/Operations'; + + var configParams = { + NAME: targetImageName + }; + + makeTemplateRequest(client, path, 'captureRole.xml', configParams, callback); +}; + +validateCreateOptions = function (options, config, callback) { + if (typeof options === 'function') { + options = {}; + } + options = options || {}; // no args + + // check required options values + ['flavor', 'image', 'name', 'username', 'password', 'location'].forEach(function (member) { + if (!options[member]) { + errs.handle( + errs.create({ message: 'options.' + member + ' is a required argument.' }), + callback + ); + } + }); + callback(); +}; + +exports._updateMinimumPollInterval = function(interval) { + MINIMUM_POLL_INTERVAL = interval; +}; diff --git a/lib/pkgcloud/azure-v2/utils/azureApiV2.js b/lib/pkgcloud/azure-v2/utils/azureApiV2.js new file mode 100644 index 000000000..0c33dfb49 --- /dev/null +++ b/lib/pkgcloud/azure-v2/utils/azureApiV2.js @@ -0,0 +1,260 @@ +var async = require('async'); +var _ = require('lodash'); +var errs = require('errs'); + +var msRestAzure = require('ms-rest-azure'); +var resourceManagement = require("azure-arm-resource"); + +var template = require('./templates'); + +/** + * createServer() + * + * In order to deploy a vm, Azure requires us to do the following + * before we can actually try to create the vm. + * 1. Lofin + * 2. resolve the OSImage url to a container on the user's account + * 3. upload SSH certificate (if necessary) + * 4. create the VM + * + * Note: creating a VM on Azure will fail if one of the following is true + * 1. The VM (with the same name) already exists + * 2. The blob storage (with the same name) for the OSImage already exists + * 3. The VM disk (with the same name) for the OSImage already exists + * 4. The storage account is in a different azure location than the vm + * (East US, West US...) + * + * Note: createServer() must wait for Azure to respond if the createDeployment (vm) + * request succeeded. createServer() asynchronously polls Azure to get + * the result. Once the result is received, the callback function will be called + * with the server information or error. The state of returned server will most likely + * be PROVISIONING or STOPPED. Use server.setWait() to continue polling the server until + * its status is RUNNING. This entire process may take several minutes. + */ +exports.createServer = function (client, options, callback) { + + var vmOptions = {}; + + // async execute the following tasks one by one and bail if there is an error + async.waterfall([ + function (next) { + validateCreateOptions(options, client.config, next); + }, + function (next) { + loginToAzure(client, options, next); + }, + function (next) { + createVM(client, options, vmOptions, next); + }, + function (server, next) { + // now get the actual server info + getServer(client, options, server, next); + }], + function (err, result) { + if (err) { + callback(err); + } else { + // return the server info + callback(null, result); + } + } + ); +} + +/** + * loginToAzure + * In order to perform any ARM action on azure, we first need to authenticate using user name and password. + * https://www.npmjs.com/package/ms-rest-azure + */ +var loginToAzure = function (client, options, callback) { + var config = client.config; + msRestAzure.loginWithServicePrincipalSecret(config.spClientId, config.spSecret, config.spDomain, function (err, credentials) { + + if (err) { + errs.handle( + errs.create({ message: 'There was a problem connecting to azure: ' + err }), + callback + ); + } + + client.credentials = credentials; + + return callback(); + }); +} + +var validateCreateOptions = function (options, config, callback) { + if (typeof options === 'function') { + options = {}; + } + options = options || {}; // no args + + // check required options values + ['flavor', 'image', 'name', 'username', 'password', 'location'].forEach(function (member) { + if (!options[member]) { + errs.handle( + errs.create({ message: 'options.' + member + ' is a required argument.' }), + callback + ); + } + }); + callback(); +}; + +var createVM = function (client, options, vmOptions, callback) { + // check OS type of image to determine if we are creating a linux or windows VM + switch (options.image.OS.toLowerCase()) { + case 'linux': + createLinuxVM(client, options, vmOptions, callback); + break; + case 'windows': + createWindowsVM(client, options, vmOptions, callback); + break; + default: + callback(errs.create({message: 'Unknown Image OS: ' + options.image.OS})); + break; + } +}; + +var createLinuxVM = function (client, options, vmOptions, callback) { + + var configParams = { + API_VERSION: '2016-03-30', + NAME: options.name, + USERNAME: options.username, + PASSWORD: options.password, + VM_SIZE: options.flavor, + LOCATION: options.location, + VM_NAME: options.name, + OS_DISK_URI: options.image.uri, + NICK_ID: options.nic + }; + + makeTemplateRequest(client, 'vm', configParams, callback); +}; + +var createWindowsVM = function (client, options, vmOptions, callback) { + var path = client.subscriptionId + '/services/hostedservices/' + options.name + '/deployments'; + var mediaLink = getMediaLinkUrl(client.config.storageAccount, options.name + '.vhd'); + var label = new Buffer(options.name).toString('base64'); + + var configParams = { + NAME: options.name, + COMPUTER_NAME: options.computerName || options.name.slice(0, 15), + LABEL_BASE64: label, + PASSWORD: options.password, + ROLESIZE: options.flavor, + ENDPOINTS: createEndpoints(options.ports), + OS_SOURCE_IMAGE_NAME: vmOptions.image.Name, + OS_IMAGE_MEDIALINK: mediaLink + }; + + makeTemplateRequest(client, path, 'windowsDeployment.xml', configParams, callback); +}; + +exports.getServer = function (client, name, callback) { + + var configParams = { + API_VERSION: '2016-03-30', + NAME: name, + USERNAME: '', + PASSWORD: '', + VM_SIZE: '', + LOCATION: '', + VM_NAME: '', + OS_DISK_URI: '', + NICK_ID: '' + }; + + template.resolve('vm', configParams, function (err, identity, parameters) { + + var config = client.config; + var resourceClient = new resourceManagement.ResourceManagementClient(client.credentials, config.subscriptionId); + resourceClient.resources.get( + config.resourceGroup, + identity.resourceProviderNamespace, identity.resourceType, + identity.resourceName, + 'InstanceView', + identity.resourceProviderApiVersion, null, + function (err, result, request, response) { + if (err) { + errs.handle( + errs.create('A problem during resource creation: ' + err), + callback + ); + } + + var jsonResult = JSON.parse(response.body); + jsonResult.name = name; + callback(null, jsonResult); + } + ); + + }); +} + +getServer = function (client, options, server, callback) { + + var configParams = { + API_VERSION: '2016-03-30', + NAME: options.name, + USERNAME: options.username, + PASSWORD: options.password, + VM_SIZE: options.flavor, + LOCATION: options.location, + VM_NAME: options.name, + OS_DISK_URI: options.image.uri, + NICK_ID: options.nic + }; + + template.resolve('vm', configParams, function (err, identity, parameters) { + var config = client.config; + var resourceClient = new resourceManagement.ResourceManagementClient(client.credentials, config.subscriptionId); + resourceClient.resources.get( + config.resourceGroup, + identity.resourceProviderNamespace, identity.resourceType, + identity.resourceName, + 'InstanceView', + identity.resourceProviderApiVersion, null, + function (err, result, request, response) { + if (err) { + errs.handle( + errs.create('A problem during resource creation: ' + err), + callback + ); + } + + var jsonResult = JSON.parse(response.body); + jsonResult = _.extend(jsonResult, server); + callback(null, jsonResult); + } + ); + }); +}; + +makeTemplateRequest = function (client, templateName, params, callback) { + + template.resolve(templateName, params, function (err, identity, parameters) { + + var config = client.config; + var resourceClient = new resourceManagement.ResourceManagementClient(client.credentials, config.subscriptionId); + resourceClient.resources.beginCreateOrUpdate( + config.resourceGroup, + identity.resourceProviderNamespace, '', + identity.resourceType, + identity.resourceName, + identity.resourceProviderApiVersion, + parameters, null, + function (err, result, request, response) { + if (err) { + errs.handle( + errs.create('A problem during resource creation: ' + err), + callback + ); + } + + callback(null, result); + } + ); + }); +}; \ No newline at end of file diff --git a/lib/pkgcloud/azure-v2/utils/templates/index.js b/lib/pkgcloud/azure-v2/utils/templates/index.js new file mode 100644 index 000000000..fd70000d2 --- /dev/null +++ b/lib/pkgcloud/azure-v2/utils/templates/index.js @@ -0,0 +1,42 @@ +var fs = require('fs'); +var path = require('path'); +var async = require('async'); +var _ = require('lodash'); + +exports.resolve = function (templateName, params, callback) { + var identityTemplate; + var paramsTemplate; + async.waterfall([ + function (next) { + load(templateName + '.identity.json', next); + }, + function (loadedTemplate, next) { + identityTemplate = loadedTemplate; + load(templateName + '.params.json', next); + }, + function (loadedTemplate, next) { + // compile template with params + paramsTemplate = loadedTemplate; + var compiledIdentity = _.template(identityTemplate); + var compiledParams = _.template(paramsTemplate); + var identity = JSON.parse(compiledIdentity(params)); + var parameters = JSON.parse(compiledParams(params)); + next(null, identity, parameters); + } + ], callback); +} + +var load = exports.load = function (templateName, callback) { + var templatePath = path.join(__dirname, templateName); + fs.readFile(templatePath, 'utf8', function (err, data) { + callback(err, data); + }); +}; + +var compile = exports.compile = function (name, params, callback) { + var path = PATH.join(__dirname, name); + fs.readFile(path, 'utf8', function (err, data) { + var compiled = _.template(data); + callback(err, compiled(params)); + }); +}; \ No newline at end of file diff --git a/lib/pkgcloud/azure-v2/utils/templates/vm.identity.json b/lib/pkgcloud/azure-v2/utils/templates/vm.identity.json new file mode 100644 index 000000000..fff6e1d9a --- /dev/null +++ b/lib/pkgcloud/azure-v2/utils/templates/vm.identity.json @@ -0,0 +1,6 @@ +{ + "resourceName": "<%= NAME %>", + "resourceProviderNamespace": "Microsoft.Compute", + "resourceType": "virtualMachines", + "resourceProviderApiVersion": "<%= API_VERSION %>" +} \ No newline at end of file diff --git a/lib/pkgcloud/azure-v2/utils/templates/vm.params.json b/lib/pkgcloud/azure-v2/utils/templates/vm.params.json new file mode 100644 index 000000000..d939e0362 --- /dev/null +++ b/lib/pkgcloud/azure-v2/utils/templates/vm.params.json @@ -0,0 +1,37 @@ +{ + "location": "<%= LOCATION %>", + "properties": { + "hardwareProfile": { + "vmSize": "<%= VM_SIZE %>" + }, + "osProfile": { + "computerName": "<%= VM_NAME %>", + "adminUsername": "<%= USERNAME %>", + "adminPassword": "<%= PASSWORD %>" + }, + "storageProfile": { + "imageReference": { + "publisher": "Canonical", + "offer": "UbuntuServer", + "sku": "16.04.0-LTS", + "version": "latest" + }, + "osDisk": { + "name": "osdisk", + "vhd": { + "uri": "<%= OS_DISK_URI %>" + }, + "caching": "ReadWrite", + "createOption": "FromImage" + } + }, + "networkProfile": { + "networkInterfaces": [ + { + "id": "<%= NICK_ID %>", + "primary": true + } + ] + } + } +} \ No newline at end of file diff --git a/package.json b/package.json index a306656f8..9804308ee 100644 --- a/package.json +++ b/package.json @@ -57,6 +57,7 @@ "dependencies": { "async": "0.9.x", "aws-sdk": "^2.2.43", + "azure-arm-resource": "^1.6.1-preview", "errs": "0.3.x", "eventemitter2": "0.4.x", "fast-json-patch": "0.5.x", @@ -65,6 +66,7 @@ "ip": "0.3.x", "lodash": "^3.10.1", "mime": "1.2.x", + "ms-rest-azure": "^1.15.2", "qs": "1.2.x", "request": "2.40.x", "s3-upload-stream": "~1.0.7", From d62014b73efcc1f34e39509854a3161e079727d4 Mon Sep 17 00:00:00 2001 From: morsh Date: Tue, 13 Dec 2016 13:15:29 +0200 Subject: [PATCH 02/41] adding login to get server --- lib/pkgcloud/azure-v2/utils/azureApiV2.js | 63 ++++++++++++++--------- 1 file changed, 39 insertions(+), 24 deletions(-) diff --git a/lib/pkgcloud/azure-v2/utils/azureApiV2.js b/lib/pkgcloud/azure-v2/utils/azureApiV2.js index 0c33dfb49..81b3eff95 100644 --- a/lib/pkgcloud/azure-v2/utils/azureApiV2.js +++ b/lib/pkgcloud/azure-v2/utils/azureApiV2.js @@ -41,7 +41,7 @@ exports.createServer = function (client, options, callback) { validateCreateOptions(options, client.config, next); }, function (next) { - loginToAzure(client, options, next); + loginToAzure(client, next); }, function (next) { createVM(client, options, vmOptions, next); @@ -66,7 +66,7 @@ exports.createServer = function (client, options, callback) { * In order to perform any ARM action on azure, we first need to authenticate using user name and password. * https://www.npmjs.com/package/ms-rest-azure */ -var loginToAzure = function (client, options, callback) { +var loginToAzure = function (client, callback) { var config = client.config; msRestAzure.loginWithServicePrincipalSecret(config.spClientId, config.spSecret, config.spDomain, function (err, credentials) { @@ -166,31 +166,46 @@ exports.getServer = function (client, name, callback) { NICK_ID: '' }; - template.resolve('vm', configParams, function (err, identity, parameters) { + async.waterfall([ + function (next) { + loginToAzure(client, next); + }, + function (next) { + template.resolve('vm', configParams, function (err, identity, parameters) { - var config = client.config; - var resourceClient = new resourceManagement.ResourceManagementClient(client.credentials, config.subscriptionId); - resourceClient.resources.get( - config.resourceGroup, - identity.resourceProviderNamespace, identity.resourceType, - identity.resourceName, - 'InstanceView', - identity.resourceProviderApiVersion, null, - function (err, result, request, response) { - if (err) { - errs.handle( - errs.create('A problem during resource creation: ' + err), - callback - ); - } + var config = client.config; + var resourceClient = new resourceManagement.ResourceManagementClient(client.credentials, config.subscriptionId); + resourceClient.resources.get( + config.resourceGroup, + identity.resourceProviderNamespace, identity.resourceType, + identity.resourceName, + 'InstanceView', + identity.resourceProviderApiVersion, null, + function (err, result, request, response) { + if (err) { + errs.handle( + errs.create('A problem during resource creation: ' + err), + callback + ); + } - var jsonResult = JSON.parse(response.body); - jsonResult.name = name; - callback(null, jsonResult); - } - ); + var jsonResult = JSON.parse(response.body); + jsonResult.name = name; + next(null, jsonResult); + } + ); - }); + }); + }], + function (err, result) { + if (err) { + callback(err); + } else { + // return the server info + callback(null, result); + } + } + ); } getServer = function (client, options, server, callback) { From 906472d6e6749d453a932be420e0d1ae63791b15 Mon Sep 17 00:00:00 2001 From: jeniawhite Date: Tue, 13 Dec 2016 14:21:30 +0200 Subject: [PATCH 03/41] DestroyServer addition Added destroy server and example for it --- .vscode/launch.json | 12 +- examples/compute/azure-v2-destroy.js | 38 ++ examples/compute/azure-v2.js | 17 +- .../azure-v2/compute/client/servers.js | 6 +- lib/pkgcloud/azure-v2/utils/azureApiV2.js | 447 ++++++++++-------- 5 files changed, 312 insertions(+), 208 deletions(-) create mode 100644 examples/compute/azure-v2-destroy.js diff --git a/.vscode/launch.json b/.vscode/launch.json index 47dca7065..afafe1e17 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -14,8 +14,16 @@ { "type": "node", "request": "launch", - "name": "Launch Azure V2", - "program": "${workspaceRoot}\\examples\\compute\\azure-v2.js", + "name": "Launch Azure V2 Destroy", + "program": "${workspaceRoot}//examples//compute//azure-v2-destroy.js", + "cwd": "${workspaceRoot}", + "stopOnEntry": true + }, + { + "type": "node", + "request": "launch", + "name": "Launch Azure V2 Create", + "program": "${workspaceRoot}//examples//compute//azure-v2.js", "cwd": "${workspaceRoot}", "stopOnEntry": true }, diff --git a/examples/compute/azure-v2-destroy.js b/examples/compute/azure-v2-destroy.js new file mode 100644 index 000000000..3e8ce27c6 --- /dev/null +++ b/examples/compute/azure-v2-destroy.js @@ -0,0 +1,38 @@ +var pkgcloud = require('../../lib/pkgcloud'), + fs = require('fs'), + client, + options; + +// +// Create a pkgcloud compute instance +// +options = { + resourceGroup: '{resourceGroup}', + provider: 'azure-v2', + storageAccount: '{storeName}', + storageAccessKey: '{storeKey}', + subscriptionId: '{subscriptionId}', + spClientId: '{spClientId}', + spSecret: '{spSecret}', + spDomain: '{spDomain}', + spSubscriptionId: '{spSubscriptionId}' +}; +client = pkgcloud.compute.createClient(options); + +// +// Create a server. +// This may take several minutes. +// +options = { + name: 'ms-pkgc-vm-test', // name of the server +}; + +console.log('deleting server...'); + +client.destroyServer(options, function (err, server) { + if (err) { + console.log(err); + } else { + console.log('Started DELETE of VM'); + } +}); diff --git a/examples/compute/azure-v2.js b/examples/compute/azure-v2.js index 58d2451d5..caede7b0c 100644 --- a/examples/compute/azure-v2.js +++ b/examples/compute/azure-v2.js @@ -12,10 +12,10 @@ options = { storageAccount: '{storeName}', storageAccessKey: '{storeKey}', subscriptionId: '{subscriptionId}', - spClientId: "{spClientId}", - spSecret: "{spSecret}", - spDomain: "{spDomain}", - spSubscriptionId: "{spSubscriptionId}" + spClientId: '{spClientId}', + spSecret: '{spSecret}', + spDomain: '{spDomain}', + spSubscriptionId: '{spSubscriptionId}' }; client = pkgcloud.compute.createClient(options); @@ -25,12 +25,12 @@ client = pkgcloud.compute.createClient(options); // options = { // pkgcloud compute properties - name: 'ms-pkgc-test2', // name of the server + name: 'ms-pkgc-vm-test', // name of the server flavor: 'Standard_D1', // azure vm size //image: '5112500ae3b842c8b9c604889f8753c3__OpenLogic-CentOS63DEC20121220', // OS Image to use image: { - uri: 'https://{storename}.blob.core.windows.net/osdiks/ms-pkgc-test-os2.vhd', - OS: 'linux' + uri: 'https://{storename}.blob.core.windows.net/osdiks/ms-pkgc-test-os2.vhd', + OS: 'linux' }, nic: '/subscriptions/{subscriptionId}/resourceGroups/{resourceGroup}/providers/Microsoft.Network/networkInterfaces/{nicName}', @@ -58,6 +58,3 @@ client.createServer(options, function (err, server) { }); } }); - - - diff --git a/lib/pkgcloud/azure-v2/compute/client/servers.js b/lib/pkgcloud/azure-v2/compute/client/servers.js index 247b3c3da..e2eb960ea 100644 --- a/lib/pkgcloud/azure-v2/compute/client/servers.js +++ b/lib/pkgcloud/azure-v2/compute/client/servers.js @@ -119,12 +119,12 @@ exports.createServer = function createServer(options, callback) { // Destroy a server in Azure. // exports.destroyServer = function destroyServer(server, callback) { - var serverId = server instanceof base.Server ? server.id : server; + var serverName = server.name; - azureApi.destroyServer(this, serverId, function (err) { + azureApi.destroyServer(this, serverName, function (err) { if (callback) { return !err - ? callback && callback(null, { ok: serverId }) + ? callback && callback(null, { ok: serverName }) : callback && callback(err); } }); diff --git a/lib/pkgcloud/azure-v2/utils/azureApiV2.js b/lib/pkgcloud/azure-v2/utils/azureApiV2.js index 0c33dfb49..f41bf5d12 100644 --- a/lib/pkgcloud/azure-v2/utils/azureApiV2.js +++ b/lib/pkgcloud/azure-v2/utils/azureApiV2.js @@ -12,7 +12,7 @@ var template = require('./templates'); * * In order to deploy a vm, Azure requires us to do the following * before we can actually try to create the vm. - * 1. Lofin + * 1. Login * 2. resolve the OSImage url to a container on the user's account * 3. upload SSH certificate (if necessary) * 4. create the VM @@ -31,34 +31,35 @@ var template = require('./templates'); * be PROVISIONING or STOPPED. Use server.setWait() to continue polling the server until * its status is RUNNING. This entire process may take several minutes. */ -exports.createServer = function (client, options, callback) { - - var vmOptions = {}; - - // async execute the following tasks one by one and bail if there is an error - async.waterfall([ - function (next) { - validateCreateOptions(options, client.config, next); - }, - function (next) { - loginToAzure(client, options, next); - }, - function (next) { - createVM(client, options, vmOptions, next); - }, - function (server, next) { - // now get the actual server info - getServer(client, options, server, next); - }], - function (err, result) { - if (err) { - callback(err); - } else { - // return the server info - callback(null, result); - } - } - ); +exports.createServer = function(client, options, callback) { + + var vmOptions = {}; + + // async execute the following tasks one by one and bail if there is an error + async.waterfall([ + function(next) { + validateCreateOptions(options, client.config, next); + }, + function(next) { + loginToAzure(client, options, next); + }, + function(next) { + createVM(client, options, vmOptions, next); + }, + function(server, next) { + // now get the actual server info + getServer(client, options, server, next); + } + ], + function(err, result) { + if (err) { + callback(err); + } else { + // return the server info + callback(null, result); + } + } + ); } /** @@ -66,195 +67,255 @@ exports.createServer = function (client, options, callback) { * In order to perform any ARM action on azure, we first need to authenticate using user name and password. * https://www.npmjs.com/package/ms-rest-azure */ -var loginToAzure = function (client, options, callback) { - var config = client.config; - msRestAzure.loginWithServicePrincipalSecret(config.spClientId, config.spSecret, config.spDomain, function (err, credentials) { - - if (err) { - errs.handle( - errs.create({ message: 'There was a problem connecting to azure: ' + err }), - callback - ); - } +var loginToAzure = function(client, options, callback) { + var config = client.config; + msRestAzure.loginWithServicePrincipalSecret(config.spClientId, config.spSecret, config.spDomain, function(err, credentials) { + + if (err) { + errs.handle( + errs.create({ + message: 'There was a problem connecting to azure: ' + err + }), + callback + ); + } - client.credentials = credentials; + client.credentials = credentials; - return callback(); + return callback(); }); } -var validateCreateOptions = function (options, config, callback) { - if (typeof options === 'function') { - options = {}; - } - options = options || {}; // no args - - // check required options values - ['flavor', 'image', 'name', 'username', 'password', 'location'].forEach(function (member) { - if (!options[member]) { - errs.handle( - errs.create({ message: 'options.' + member + ' is a required argument.' }), - callback - ); +var validateCreateOptions = function(options, config, callback) { + if (typeof options === 'function') { + options = {}; } - }); - callback(); + options = options || {}; // no args + + // check required options values + ['flavor', 'image', 'name', 'username', 'password', 'location'].forEach(function(member) { + if (!options[member]) { + errs.handle( + errs.create({ + message: 'options.' + member + ' is a required argument.' + }), + callback + ); + } + }); + callback(); }; -var createVM = function (client, options, vmOptions, callback) { - // check OS type of image to determine if we are creating a linux or windows VM - switch (options.image.OS.toLowerCase()) { - case 'linux': - createLinuxVM(client, options, vmOptions, callback); - break; - case 'windows': - createWindowsVM(client, options, vmOptions, callback); - break; - default: - callback(errs.create({message: 'Unknown Image OS: ' + options.image.OS})); - break; - } +var createVM = function(client, options, vmOptions, callback) { + // check OS type of image to determine if we are creating a linux or windows VM + switch (options.image.OS.toLowerCase()) { + case 'linux': + createLinuxVM(client, options, vmOptions, callback); + break; + case 'windows': + createWindowsVM(client, options, vmOptions, callback); + break; + default: + callback(errs.create({ + message: 'Unknown Image OS: ' + options.image.OS + })); + break; + } }; -var createLinuxVM = function (client, options, vmOptions, callback) { - - var configParams = { - API_VERSION: '2016-03-30', - NAME: options.name, - USERNAME: options.username, - PASSWORD: options.password, - VM_SIZE: options.flavor, - LOCATION: options.location, - VM_NAME: options.name, - OS_DISK_URI: options.image.uri, - NICK_ID: options.nic - }; - - makeTemplateRequest(client, 'vm', configParams, callback); +var createLinuxVM = function(client, options, vmOptions, callback) { + + var configParams = { + API_VERSION: '2016-03-30', + NAME: options.name, + USERNAME: options.username, + PASSWORD: options.password, + VM_SIZE: options.flavor, + LOCATION: options.location, + VM_NAME: options.name, + OS_DISK_URI: options.image.uri, + NICK_ID: options.nic + }; + + makeTemplateRequest(client, 'vm', configParams, callback); }; -var createWindowsVM = function (client, options, vmOptions, callback) { - var path = client.subscriptionId + '/services/hostedservices/' + options.name + '/deployments'; - var mediaLink = getMediaLinkUrl(client.config.storageAccount, options.name + '.vhd'); - var label = new Buffer(options.name).toString('base64'); - - var configParams = { - NAME: options.name, - COMPUTER_NAME: options.computerName || options.name.slice(0, 15), - LABEL_BASE64: label, - PASSWORD: options.password, - ROLESIZE: options.flavor, - ENDPOINTS: createEndpoints(options.ports), - OS_SOURCE_IMAGE_NAME: vmOptions.image.Name, - OS_IMAGE_MEDIALINK: mediaLink - }; - - makeTemplateRequest(client, path, 'windowsDeployment.xml', configParams, callback); +var createWindowsVM = function(client, options, vmOptions, callback) { + var path = client.subscriptionId + '/services/hostedservices/' + options.name + '/deployments'; + var mediaLink = getMediaLinkUrl(client.config.storageAccount, options.name + '.vhd'); + var label = new Buffer(options.name).toString('base64'); + + var configParams = { + NAME: options.name, + COMPUTER_NAME: options.computerName || options.name.slice(0, 15), + LABEL_BASE64: label, + PASSWORD: options.password, + ROLESIZE: options.flavor, + ENDPOINTS: createEndpoints(options.ports), + OS_SOURCE_IMAGE_NAME: vmOptions.image.Name, + OS_IMAGE_MEDIALINK: mediaLink + }; + + makeTemplateRequest(client, path, 'windowsDeployment.xml', configParams, callback); }; -exports.getServer = function (client, name, callback) { +exports.getServer = function(client, name, callback) { - var configParams = { - API_VERSION: '2016-03-30', - NAME: name, - USERNAME: '', - PASSWORD: '', - VM_SIZE: '', - LOCATION: '', - VM_NAME: '', - OS_DISK_URI: '', - NICK_ID: '' - }; + var configParams = { + API_VERSION: '2016-03-30', + NAME: name, + USERNAME: '', + PASSWORD: '', + VM_SIZE: '', + LOCATION: '', + VM_NAME: '', + OS_DISK_URI: '', + NICK_ID: '' + }; - template.resolve('vm', configParams, function (err, identity, parameters) { + template.resolve('vm', configParams, function(err, identity, parameters) { - var config = client.config; - var resourceClient = new resourceManagement.ResourceManagementClient(client.credentials, config.subscriptionId); - resourceClient.resources.get( - config.resourceGroup, - identity.resourceProviderNamespace, identity.resourceType, - identity.resourceName, - 'InstanceView', - identity.resourceProviderApiVersion, null, - function (err, result, request, response) { - if (err) { - errs.handle( - errs.create('A problem during resource creation: ' + err), - callback - ); - } + var config = client.config; + var resourceClient = new resourceManagement.ResourceManagementClient(client.credentials, config.subscriptionId); + resourceClient.resources.get( + config.resourceGroup, + identity.resourceProviderNamespace, identity.resourceType, + identity.resourceName, + 'InstanceView', + identity.resourceProviderApiVersion, null, + function(err, result, request, response) { + if (err) { + errs.handle( + errs.create('A problem during resource creation: ' + err), + callback + ); + } - var jsonResult = JSON.parse(response.body); - jsonResult.name = name; - callback(null, jsonResult); - } - ); + var jsonResult = JSON.parse(response.body); + jsonResult.name = name; + callback(null, jsonResult); + } + ); - }); + }); } -getServer = function (client, options, server, callback) { - - var configParams = { - API_VERSION: '2016-03-30', - NAME: options.name, - USERNAME: options.username, - PASSWORD: options.password, - VM_SIZE: options.flavor, - LOCATION: options.location, - VM_NAME: options.name, - OS_DISK_URI: options.image.uri, - NICK_ID: options.nic - }; - - template.resolve('vm', configParams, function (err, identity, parameters) { - var config = client.config; - var resourceClient = new resourceManagement.ResourceManagementClient(client.credentials, config.subscriptionId); - resourceClient.resources.get( - config.resourceGroup, - identity.resourceProviderNamespace, identity.resourceType, - identity.resourceName, - 'InstanceView', - identity.resourceProviderApiVersion, null, - function (err, result, request, response) { - if (err) { - errs.handle( - errs.create('A problem during resource creation: ' + err), - callback - ); - } +getServer = function(client, options, server, callback) { - var jsonResult = JSON.parse(response.body); - jsonResult = _.extend(jsonResult, server); - callback(null, jsonResult); - } - ); - }); + var configParams = { + API_VERSION: '2016-03-30', + NAME: options.name, + USERNAME: options.username, + PASSWORD: options.password, + VM_SIZE: options.flavor, + LOCATION: options.location, + VM_NAME: options.name, + OS_DISK_URI: options.image.uri, + NICK_ID: options.nic + }; + + template.resolve('vm', configParams, function(err, identity, parameters) { + var config = client.config; + var resourceClient = new resourceManagement.ResourceManagementClient(client.credentials, config.subscriptionId); + resourceClient.resources.get( + config.resourceGroup, + identity.resourceProviderNamespace, identity.resourceType, + identity.resourceName, + 'InstanceView', + identity.resourceProviderApiVersion, null, + function(err, result, request, response) { + if (err) { + errs.handle( + errs.create('A problem during resource creation: ' + err), + callback + ); + } + + var jsonResult = JSON.parse(response.body); + jsonResult = _.extend(jsonResult, server); + callback(null, jsonResult); + } + ); + }); }; -makeTemplateRequest = function (client, templateName, params, callback) { +makeTemplateRequest = function(client, templateName, params, callback) { - template.resolve(templateName, params, function (err, identity, parameters) { + template.resolve(templateName, params, function(err, identity, parameters) { - var config = client.config; - var resourceClient = new resourceManagement.ResourceManagementClient(client.credentials, config.subscriptionId); - resourceClient.resources.beginCreateOrUpdate( - config.resourceGroup, - identity.resourceProviderNamespace, '', - identity.resourceType, - identity.resourceName, - identity.resourceProviderApiVersion, - parameters, null, - function (err, result, request, response) { - if (err) { - errs.handle( - errs.create('A problem during resource creation: ' + err), - callback - ); - } + var config = client.config; + var resourceClient = new resourceManagement.ResourceManagementClient(client.credentials, config.subscriptionId); + resourceClient.resources.beginCreateOrUpdate( + config.resourceGroup, + identity.resourceProviderNamespace, '', + identity.resourceType, + identity.resourceName, + identity.resourceProviderApiVersion, + parameters, null, + function(err, result, request, response) { + if (err) { + errs.handle( + errs.create('A problem during resource creation: ' + err), + callback + ); + } + + callback(null, result); + } + ); + }); +}; + +var deleteServer = function(client, serverName, callback) { + var configParams = { + API_VERSION: '2016-03-30', + NAME: serverName, + USERNAME: ',', + PASSWORD: '', + VM_SIZE: '', + LOCATION: '', + VM_NAME: '', + OS_DISK_URI: '', + NICK_ID: '' + }; + + template.resolve('vm', configParams, function(err, identity, parameters) { + + var config = client.config; + var resourceClient = new resourceManagement.ResourceManagementClient(client.credentials, config.subscriptionId); + resourceClient.resources.beginDeleteMethod( + config.resourceGroup, + identity.resourceProviderNamespace, '', + identity.resourceType, + identity.resourceName, + identity.resourceProviderApiVersion, + parameters, + function(err, result, request, response) { + if (err) { + errs.handle( + errs.create('A problem during resource deletion: ' + err), + callback + ); + } + callback(); + } + ); + }); +}; - callback(null, result); - } +exports.destroyServer = function(client, serverName, callback) { + var server = null; + + async.waterfall([ + function(next) { + loginToAzure(client, {}, next); + }, + function(next) { + deleteServer(client, serverName, next); + } + ], + function(err) { + callback(err, true); + } ); - }); -}; \ No newline at end of file +}; From 043e151554680ee7ab48a58c09301acd942bf51d Mon Sep 17 00:00:00 2001 From: jeniawhite Date: Tue, 13 Dec 2016 16:18:33 +0200 Subject: [PATCH 04/41] Fixing removal of options in logintoazure after merge --- lib/pkgcloud/azure-v2/utils/azureApiV2.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/pkgcloud/azure-v2/utils/azureApiV2.js b/lib/pkgcloud/azure-v2/utils/azureApiV2.js index 5880e928a..f0337598f 100644 --- a/lib/pkgcloud/azure-v2/utils/azureApiV2.js +++ b/lib/pkgcloud/azure-v2/utils/azureApiV2.js @@ -183,11 +183,11 @@ exports.getServer = function(client, name, callback) { var config = client.config; var resourceClient = new resourceManagement.ResourceManagementClient(client.credentials, config.subscriptionId); resourceClient.resources.get( - config.resourceGroup, + config.resourceGroup, identity.resourceProviderNamespace, identity.resourceType, - identity.resourceName, - 'InstanceView', - identity.resourceProviderApiVersion, null, + identity.resourceName, + 'InstanceView', + identity.resourceProviderApiVersion, null, function (err, result, request, response) { if (err) { errs.handle( @@ -323,7 +323,7 @@ exports.destroyServer = function(client, serverName, callback) { async.waterfall([ function(next) { - loginToAzure(client, {}, next); + loginToAzure(client, next); }, function(next) { deleteServer(client, serverName, next); From 49745ccbecc2a2249aec978efd28df4211410853 Mon Sep 17 00:00:00 2001 From: jeniawhite Date: Tue, 13 Dec 2016 17:29:31 +0200 Subject: [PATCH 05/41] Fixing get server status response on errors --- lib/pkgcloud/azure-v2/utils/azureApiV2.js | 87 ++++++++++++----------- 1 file changed, 44 insertions(+), 43 deletions(-) diff --git a/lib/pkgcloud/azure-v2/utils/azureApiV2.js b/lib/pkgcloud/azure-v2/utils/azureApiV2.js index f0337598f..368973293 100644 --- a/lib/pkgcloud/azure-v2/utils/azureApiV2.js +++ b/lib/pkgcloud/azure-v2/utils/azureApiV2.js @@ -41,7 +41,7 @@ exports.createServer = function(client, options, callback) { validateCreateOptions(options, client.config, next); }, function(next) { - loginToAzure(client, next); + loginToAzure(client, next); }, function(next) { createVM(client, options, vmOptions, next); @@ -67,7 +67,7 @@ exports.createServer = function(client, options, callback) { * In order to perform any ARM action on azure, we first need to authenticate using user name and password. * https://www.npmjs.com/package/ms-rest-azure */ -var loginToAzure = function (client, callback) { +var loginToAzure = function(client, callback) { var config = client.config; msRestAzure.loginWithServicePrincipalSecret(config.spClientId, config.spSecret, config.spDomain, function(err, credentials) { @@ -173,46 +173,47 @@ exports.getServer = function(client, name, callback) { NICK_ID: '' }; - async.waterfall([ - function (next) { - loginToAzure(client, next); - }, - function (next) { - template.resolve('vm', configParams, function (err, identity, parameters) { + async.waterfall([ + function(next) { + loginToAzure(client, next); + }, + function(next) { + template.resolve('vm', configParams, function(err, identity, parameters) { + + var config = client.config; + var resourceClient = new resourceManagement.ResourceManagementClient(client.credentials, config.subscriptionId); + resourceClient.resources.get( + config.resourceGroup, + identity.resourceProviderNamespace, identity.resourceType, + identity.resourceName, + 'InstanceView', + identity.resourceProviderApiVersion, null, + function(err, result, request, response) { + if (err) { + errs.handle( + errs.create('A problem during resource creation: ' + err), + callback + ); + } else { + var jsonResult = JSON.parse(response.body); + jsonResult.name = name; + next(null, jsonResult); + } + } + ); - var config = client.config; - var resourceClient = new resourceManagement.ResourceManagementClient(client.credentials, config.subscriptionId); - resourceClient.resources.get( - config.resourceGroup, - identity.resourceProviderNamespace, identity.resourceType, - identity.resourceName, - 'InstanceView', - identity.resourceProviderApiVersion, null, - function (err, result, request, response) { + }); + } + ], + function(err, result) { if (err) { - errs.handle( - errs.create('A problem during resource creation: ' + err), - callback - ); + callback(err); + } else { + // return the server info + callback(null, result); } - - var jsonResult = JSON.parse(response.body); - jsonResult.name = name; - next(null, jsonResult); - } - ); - - }); - }], - function (err, result) { - if (err) { - callback(err); - } else { - // return the server info - callback(null, result); - } - } - ); + } + ); } getServer = function(client, options, server, callback) { @@ -244,11 +245,11 @@ getServer = function(client, options, server, callback) { errs.create('A problem during resource creation: ' + err), callback ); + } else { + var jsonResult = JSON.parse(response.body); + jsonResult = _.extend(jsonResult, server); + callback(null, jsonResult); } - - var jsonResult = JSON.parse(response.body); - jsonResult = _.extend(jsonResult, server); - callback(null, jsonResult); } ); }); From 6244f3455901fd055b55413935f4081831984410 Mon Sep 17 00:00:00 2001 From: morsh Date: Fri, 30 Dec 2016 18:38:53 +0200 Subject: [PATCH 06/41] adding storage for azure-v2 --- .vscode/launch.json | 10 +- lib/pkgcloud/azure-v2/compute/server.js | 1 + lib/pkgcloud/azure-v2/index.js | 1 + .../azure-v2/storage/client/containers.js | 179 ++++++++++ lib/pkgcloud/azure-v2/storage/client/files.js | 306 ++++++++++++++++++ lib/pkgcloud/azure-v2/storage/client/index.js | 25 ++ .../azure-v2/storage/client/storage-api.js | 36 +++ lib/pkgcloud/azure-v2/storage/container.js | 35 ++ lib/pkgcloud/azure-v2/storage/file.js | 28 ++ lib/pkgcloud/azure-v2/storage/index.js | 15 + lib/pkgcloud/azure-v2/utils/azureApi.js | 198 ++++++++++++ lib/pkgcloud/azure-v2/utils/constants.js | 39 +++ .../azure-v2/utils/templates/index.js | 19 +- .../azure-v2/utils/templates/storage.json | 7 + package.json | 2 + 15 files changed, 886 insertions(+), 15 deletions(-) create mode 100644 lib/pkgcloud/azure-v2/storage/client/containers.js create mode 100644 lib/pkgcloud/azure-v2/storage/client/files.js create mode 100644 lib/pkgcloud/azure-v2/storage/client/index.js create mode 100644 lib/pkgcloud/azure-v2/storage/client/storage-api.js create mode 100644 lib/pkgcloud/azure-v2/storage/container.js create mode 100644 lib/pkgcloud/azure-v2/storage/file.js create mode 100644 lib/pkgcloud/azure-v2/storage/index.js create mode 100644 lib/pkgcloud/azure-v2/utils/azureApi.js create mode 100644 lib/pkgcloud/azure-v2/utils/constants.js create mode 100644 lib/pkgcloud/azure-v2/utils/templates/storage.json diff --git a/.vscode/launch.json b/.vscode/launch.json index afafe1e17..f2eb25ef2 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -19,7 +19,7 @@ "cwd": "${workspaceRoot}", "stopOnEntry": true }, - { + { "type": "node", "request": "launch", "name": "Launch Azure V2 Create", @@ -27,6 +27,14 @@ "cwd": "${workspaceRoot}", "stopOnEntry": true }, + { + "type": "node", + "request": "launch", + "name": "Launch Azure V2 Storage", + "program": "${workspaceRoot}//examples//storage//azure-v2.js", + "cwd": "${workspaceRoot}", + "stopOnEntry": true + }, { "type": "node", "request": "attach", diff --git a/lib/pkgcloud/azure-v2/compute/server.js b/lib/pkgcloud/azure-v2/compute/server.js index 0338c0041..b7de1f062 100644 --- a/lib/pkgcloud/azure-v2/compute/server.js +++ b/lib/pkgcloud/azure-v2/compute/server.js @@ -36,6 +36,7 @@ Server.prototype._setProperties = function (details) { var provisioningStatus = _.find(statuses, status => status.code.startsWith('ProvisioningState/')) || {}; var powerStateStatus = _.find(statuses, status => status.code.startsWith('PowerState/')) || {}; + // Azure ARM VMs are natively constructed out of a collection of roles. if (provisioningStatus.code == 'ProvisioningState/succeeded' && powerStateStatus.code == 'PowerState/deallocated') { this.status = this.STATUS.stopped; } else if (provisioningStatus.code == 'ProvisioningState/succeeded' && powerStateStatus.code == 'PowerState/running') { diff --git a/lib/pkgcloud/azure-v2/index.js b/lib/pkgcloud/azure-v2/index.js index 636cd95dc..7da97c062 100644 --- a/lib/pkgcloud/azure-v2/index.js +++ b/lib/pkgcloud/azure-v2/index.js @@ -6,3 +6,4 @@ */ exports.compute = require('./compute'); +exports.storage = require('./storage'); diff --git a/lib/pkgcloud/azure-v2/storage/client/containers.js b/lib/pkgcloud/azure-v2/storage/client/containers.js new file mode 100644 index 000000000..b5887af95 --- /dev/null +++ b/lib/pkgcloud/azure-v2/storage/client/containers.js @@ -0,0 +1,179 @@ +/* + * containers.js: Instance methods for working with containers from Azure + * + * (C) Microsoft Open Technologies, Inc. + * + */ + +var async = require('async'); +var StorageManagementClient = require('azure-arm-storage'); + +var base = require('../../../core/storage'); +var pkgcloud = require('../../../../../lib/pkgcloud'); +var azureApi = require('../../utils/azureApi'); +var constants = require('../../utils/constants'); + +var storage = pkgcloud.providers['azure-v2'].storage; + +/** + * list a collection of storage accounts under a resource group + * @param {function} callback + */ +function getContainers(callback) { + var client = this; + + async.waterfall([ + (next) => { + azureApi.login(client, next); + }, + (credentials, next) => { + var storageClient = new StorageManagementClient(credentials, client.config.subscriptionId); + storageClient.storageAccounts.listByResourceGroup(client.config.resourceGroup, (err, results) => { + return err + ? next(err) + : next(null, results.map(res => new (storage.Container)(client, res))); + }); + } + ], callback); +}; + +/** + * Responds with the azure stoarge account with the given name + * @param {string|storage.Container} container - container name of container configuration + * @param {function} callback - Continuation to respond to when complete. + */ +function getContainer(container, callback) { + var containerName = container instanceof storage.Container ? container.name : container; + var client = this; + + async.waterfall([ + (next) => { + azureApi.login(client, next); + }, + (credentials, next) => { + var storageClient = new StorageManagementClient(credentials, client.config.subscriptionId); + storageClient.storageAccounts.getProperties(client.config.resourceGroup, containerName, (err, result) => { + return err + ? next(err) + : next(null, new (storage.Container)(client, result)); + }); + } + ], callback); +}; + +/** + * Create a new storage account + * @param {string|storage.Container} container - container name of container configuration + * @param {function} callback - Continuation to respond to when complete. + * + * From Azure docs: + * A container that was recently deleted cannot be recreated until all of + * its blobs are deleted. Depending on how much data was stored within the container, + * complete deletion can take seconds or minutes. If you try to create a container + * of the same name during this cleanup period, your call returns an error immediately. + */ +function createContainer(options, callback) { + var containerName = options instanceof base.Container ? options.name : options; + var client = this; + + async.waterfall([ + (next) => { + azureApi.login(client, next); + }, + (creds, next) => { + azureApi.setup(client, next); + }, + (next) => { + var parameters = { + location: options.location || client.azure.location, + sku: { + name: options.accountType || constants.DEFAULT_STORAGE_SKU, + }, + kind: 'storage' + }; + + var storageClient = new StorageManagementClient(client.azure.credentials, client.config.subscriptionId); + return storageClient.storageAccounts.create(client.config.resourceGroup, containerName, parameters, (err, result) => { + return err + ? next(err) + : next(null, new (storage.Container)(client, result)); + }); + } + ], callback); +}; + +/** + * Destroy a new storage account + * @param {string|storage.Container} container - container name of container configuration + * @param {function} callback - Continuation to respond to when complete. + * + * From Azure docs: + * A container that was recently deleted cannot be recreated until all of + * its blobs are deleted. Depending on how much data was stored within the container, + * complete deletion can take seconds or minutes. If you try to create a container + * of the same name during this cleanup period, your call returns an error immediately. + */ +function destroyContainer(container, callback) { + var containerName = container instanceof base.Container ? container.name : container; + var client = this; + + async.waterfall([ + (next) => { + azureApi.login(client, next); + }, + (credentials, next) => { + var storageClient = new StorageManagementClient(credentials, client.config.subscriptionId); + storageClient.storageAccounts.deleteMethod(client.config.resourceGroup, containerName, next); + } + ], callback); +}; + +function listContainerKeys(container, callback) { + var containerName = container instanceof storage.Container ? container.name : container; + var client = this; + + async.waterfall([ + (next) => { + azureApi.login(client, next); + }, + (credentials, next) => { + var storageClient = new StorageManagementClient(credentials, client.config.subscriptionId); + storageClient.storageAccounts.listKeys(client.config.resourceGroup, containerName, (err, result) => { + return err + ? next(err) + : next(null, result); + }); + } + ], callback); +} + +function getContainerKey (container, callback) { + var containerName = container instanceof this.models.Container ? container.name : container; + var client = this; + + client.azure = client.azure || {}; + client.azure.storageKeys = client.azure.storageKeys || {}; + + if (client.azure.storageKeys[containerName]) { + return callback(null, client.azure.storageKeys[containerName]); + } + + client.listContainerKeys(container, (err, result) => { + if (err) { + return callback(err); + } + + var key = result.keys[0].value; + client.azure.storageKeys[containerName] = key; + return callback(null, key); + }); +} + +module.exports = { + getContainers, + getContainer, + createContainer, + destroyContainer, + listContainerKeys, + getContainerKey +} \ No newline at end of file diff --git a/lib/pkgcloud/azure-v2/storage/client/files.js b/lib/pkgcloud/azure-v2/storage/client/files.js new file mode 100644 index 000000000..66e3083b0 --- /dev/null +++ b/lib/pkgcloud/azure-v2/storage/client/files.js @@ -0,0 +1,306 @@ +/* + * files.js: Instance methods for working with files for Openstack Object Storage + * + * (C) 2013 Rackspace, Ken Perkins + * MIT LICENSE + * + */ + +var async = require('async'); +var filed = require('filed'); +var mime = require('mime'); +var through = require('through2'); +var _ = require('lodash'); +var urlJoin = require('url-join'); + +var StorageManagementClient = require('azure-arm-storage'); + +var azureApi = require('../../utils/azureApi'); +var storageApi = require('./storage-api'); +var constants = require('../../utils/constants'); +var base = require('../../../core/storage'); + +/** + * client.removeFile + * + * @description remove a file from a container + * + * @param {String|object} container the container or containerName + * @param {String|object} file the file or fileName to delete + * @param callback + */ +exports.TODO_removeFile = function (container, file, callback) { + var containerName = container instanceof this.models.Container ? container.name : container, + fileName = file instanceof this.models.File ? file.name : file; + + this._request({ + method: 'DELETE', + container: containerName, + path: fileName + }, function(err) { + return err + ? callback(err) + : callback(null, true); + } + ); +}; + +/** + * client.bulkDelete + * + * @description remove a list of files from a container + * + * @param {String|object} container the container or containerName + * @param {array} files the files or fileNames to delete + * @param callback + */ +exports.TODO_bulkDelete = function(container, files, callback) { + var self = this, + containerName = container instanceof this.models.Container ? container.name : container; + this._request({ + method: 'DELETE', + body: files.map(function(file) { + return urlJoin(containerName, (file instanceof self.models.File ? file.name : file)); + }).join('\r\n'), + headers: { + 'Content-Type': 'text/plain' + }, + qs: { + 'bulk-delete': true + } + }, function(err, results) { + return err + ? callback(err) + : callback(null, results); + }); +}; + +/** + * client.upload + * + * @description upload a new file to a container. + * Returns the pipe interface so you can call: + * + * request('http://some.com/file.txt').pipe(client.upload(options)); + * + * @param {object} options + * @param {String|object} options.container the container to store the file in + * @param {String} options.remote the file name for the new file + * @param {String} [options.local] an optional local file path to upload + * @param {Stream} [options.stream] optionally explicitly provide the stream instead of pipe + * @param {object} [options.headers] optionally provide headers for the call + * @param {object} [options.metadata] optionally provide metadata for the object + * @param callback + * @returns {request|*} + */ +exports.TODO_upload = function (options) { + var self = this; + + // check for deprecated calling with a callback + if (typeof arguments[arguments.length - 1] === 'function') { + self.emit('log::warn', 'storage.upload no longer supports calling with a callback'); + } + + var container = options.container, + writableStream, + proxyStream = through(), + uploadOptions = { + method: 'PUT', + upload: true, + container: container, + path: options.remote, + headers: options.headers || {} + }; + + if (options.container instanceof this.models.Container) { + uploadOptions.container = options.container.name; + } + + if (options.contentType) { + uploadOptions.headers['content-type'] = options.contentType; + } + else { + uploadOptions.headers['content-type'] = mime.lookup(options.remote); + } + + if (options.metadata) { + uploadOptions.headers = _.extend(uploadOptions.headers, + self.serializeMetadata(self.OBJECT_META_PREFIX, options.metadata)); + } + + writableStream = this._request(uploadOptions); + + writableStream.on('complete', function(response) { + var err = self._parseError(response); + + if (err) { + proxyStream.emit('error', err); + return; + } + + // load the file metadata from the cloud, so we can return a proper model + self.getFile(uploadOptions.container, options.remote, function (err, file) { + if (err) { + proxyStream.emit('error', err); + return; + } + + proxyStream.emit('success', file); + }); + }); + + writableStream.on('error', function (err) { + proxyStream.emit('error', err); + }); + + writableStream.on('data', function (chunk) { + proxyStream.emit('data', chunk); + }); + + // we need a proxy stream so we can always return a file model + // via the 'success' event + proxyStream.pipe(writableStream); + + return proxyStream; +}; + + +/** + * client.getFiles + * + * @description get the list of files in a container. Returns at most 10,000 files if options.limit is unspecified. + * Abstracts the aggregation of files in the case that options.limit is >10,000. + * + * @param {String|object} container the container or containerName + * @param {object|Function} options + * @param {Number} [options.limit] the number of records to return + * @param {String} [options.marker] the id of the first record to return in the current query + * @param {Function} callback + */ +exports.getFiles = function (container, options, callback) { + + var client = this; + options = options || {}; + var containerName = container instanceof this.models.Container ? container.name : container; + var azureContainer = options.container || constants.DEFAULT_STORAGE_CONTAINER; + var blobs = []; + + async.waterfall([ + (next) => { + storageApi.getBlobService(client, azureContainer, containerName, next); + }, + (blobService, next) => { + + var aggregateBlobs = (err, result, cb) => { + if (err) { + cb(err); + } else { + blobs = blobs.concat(result.entries); + if (result.continuationToken !== null) { + blobService.listBlobsSegmented(azureContainer, result.continuationToken, aggregateBlobs); + } else { + cb(null, blobs); + } + } + } + + blobService.listBlobsSegmented(azureContainer, null, function(err, result) { + aggregateBlobs(err, result, (err, blobs) => { + return err ? + next(err) : + next(null, blobs.map(blob => new client.models.File(client, blob))); + }); + }); + + } + ], callback); +}; + +/** + * client.getFile + * + * @description get the details for a specific file + * + * @param {String|object} container the container or containerName + * @param {String|object} file the file or fileName to get details for + * @param callback + */ +exports.getFile = function (container, file, options, callback) { + + options = options || {}; + var containerName = container instanceof this.models.Container ? container.name : container; + var azureContainerName = options.storage && options.storage.container || constants.DEFAULT_STORAGE_CONTAINER; + var fileName = file instanceof this.models.File ? file.name : file; + var client = this; + + async.waterfall([ + (next) => { + storageApi.getBlobService(client, azureContainerName, containerName, next); + }, + (blobService, next) => { + + blobService.getBlobProperties(azureContainerName, fileName, (err, properties, status) => { + return err ? + next(err) : (!status || !status.isSuccessful) ? + next(new Error(`status is not successfull: ${JSON.stringify(status || null)}`)) : + next(null, new client.models.File(client, properties)); + }); + } + ], callback); +}; + +/** + * client.download + * + * @description download a file from a container + * Returns the pipe interface so you can call: + * + * client.download(options).pipe(fs.createWriteStream(options2)); + * + * @param {object} options + * @param {String|object} options.container the container to store the file in + * @param {String} options.remote the file name for the new file + * @param {String} [options.local] an optional local file path to download to + * @param {Stream} [options.stream] optionally explicitly provide the stream instead of pipe + * @param callback + * @returns {request|*} + */ +exports.download = function (options, callback) { + var client = this; + var container = options.container; + var containerName = container instanceof this.models.Container ? container.name : container; + var azureContainerName = options.storage && options.storage.container || constants.DEFAULT_STORAGE_CONTAINER; + var blobName = options.remote instanceof this.models.File ? options.remote.name : options.remote; + var inputStream; + var apiStream; + + if (options.local) { + inputStream = filed(options.local); + } + else if (options.stream) { + inputStream = options.stream; + } + + var blobService; + async.waterfall([ + (next) => { + storageApi.getBlobService(client, azureContainerName, containerName, next); + }, + (_blobService, next) => { + + blobService = _blobService; + blobService.getBlobProperties(azureContainerName, blobName, (err, properties, status) => { + return err ? + next(err) : (!status || !status.isSuccessful) ? + next(new Error(`status is not successfull: ${JSON.stringify(status || null)}`)) : + next(null, properties); + }); + }, + (properties, next) => { + blobService.createReadStream(azureContainerName, blobName).pipe(inputStream); + return next(); + } + ], callback); + + return inputStream; +}; \ No newline at end of file diff --git a/lib/pkgcloud/azure-v2/storage/client/index.js b/lib/pkgcloud/azure-v2/storage/client/index.js new file mode 100644 index 000000000..b2cacbfbc --- /dev/null +++ b/lib/pkgcloud/azure-v2/storage/client/index.js @@ -0,0 +1,25 @@ +/* + * client.js: Storage client for Azure + * + * (C) Microsoft Open Technologies, Inc. + * + */ + +var util = require('util'), + azureApi = require('../../utils/azureApi.js'), + azure = require('../../client'), + _ = require('lodash'); + +var Client = exports.Client = function (options) { + azure.Client.call(this, options); + + this.models = { + Container: require('../container').Container, + File: require('../file').File + }; + + _.extend(this, require('./containers')); + _.extend(this, require('./files')); +}; + +util.inherits(Client, azure.Client); \ No newline at end of file diff --git a/lib/pkgcloud/azure-v2/storage/client/storage-api.js b/lib/pkgcloud/azure-v2/storage/client/storage-api.js new file mode 100644 index 000000000..5880c82ea --- /dev/null +++ b/lib/pkgcloud/azure-v2/storage/client/storage-api.js @@ -0,0 +1,36 @@ + +var async = require('async'); +var azureStorage = require('azure-storage'); + +var azureApi = require('../../utils/azureApi'); +var constants = require('../../utils/constants'); + +function getBlobService(client, options, storageAccountName, callback) { + + options = options || {}; + var azureContainer = typeof options == 'string' ? options : (options.container || constants.DEFAULT_STORAGE_CONTAINER); + + client.getContainerKey(storageAccountName, (err, containerKey) => { + if (err) { + return callback(err); + } + + var retryOperations = new azureStorage.ExponentialRetryPolicyFilter(); + var blobService = azureStorage.createBlobService(storageAccountName, containerKey).withFilter(retryOperations); + blobService.createContainerIfNotExists(azureContainer, null, function(error) { + return error ? + callback(error) : + callback(null, blobService); + }); + }); +}; + + +function listStorageAccountFiles(client, storageAccountName, options, callback) { + +} + +module.exports = { + getBlobService, + listStorageAccountFiles +} \ No newline at end of file diff --git a/lib/pkgcloud/azure-v2/storage/container.js b/lib/pkgcloud/azure-v2/storage/container.js new file mode 100644 index 000000000..b20825035 --- /dev/null +++ b/lib/pkgcloud/azure-v2/storage/container.js @@ -0,0 +1,35 @@ +/* + * container.js: Azure container + * + * (C) Microsoft Open Technologies, Inc. + * + */ + +var util = require('util'), + _ = require('lodash'), + base = require('../../core/storage/container'); + +var Container = exports.Container = function Container(client, details) { + base.Container.call(this, client, details); +}; + +util.inherits(Container, base.Container); + +Container.prototype._setProperties = function (details) { + if (typeof details === 'string') { + this.name = details; + return; + } + + this.name = details.name; + + // + // Azure specific + // + this.original = this.azure = details; + +}; + +Container.prototype.toJSON = function () { + return _.pick(this, ['name']); +}; diff --git a/lib/pkgcloud/azure-v2/storage/file.js b/lib/pkgcloud/azure-v2/storage/file.js new file mode 100644 index 000000000..212249537 --- /dev/null +++ b/lib/pkgcloud/azure-v2/storage/file.js @@ -0,0 +1,28 @@ +/* + * container.js: Azure File (Blob) + * + * (C) Microsoft Open Technologies, Inc. + * + */ + +var util = require('util'), + _ = require('lodash'), + base = require('../../core/storage/file'); + +var File = exports.File = function File(client, details) { + base.File.call(this, client, details); +}; + +util.inherits(File, base.File); + +File.prototype._setProperties = function (details) { + + this.name = details.name; + this.size = details.contentLength ? parseInt(details.contentLength, 10) : 0; + this.lastModified = new Date(details.lastModified || null); + this.contentType = details.contentSettings && details.contentSettings.contentType; +}; + +File.prototype.toJSON = function () { + return _.pick(this, ['name', 'size', 'lastModified', 'contentType' ]); +}; \ No newline at end of file diff --git a/lib/pkgcloud/azure-v2/storage/index.js b/lib/pkgcloud/azure-v2/storage/index.js new file mode 100644 index 000000000..a55e536d9 --- /dev/null +++ b/lib/pkgcloud/azure-v2/storage/index.js @@ -0,0 +1,15 @@ +/* + * index.js: Top-level include for the Azure module + * + * (C) Microsoft Open Technologies, Inc. + * + */ + +exports.Client = require('./client').Client; +exports.Container = require('./container').Container; +exports.File = require('./file').File; +//exports.ChunkedStream = require('./utils').ChunkedStream; + +exports.createClient = function (options) { + return new exports.Client(options); +}; diff --git a/lib/pkgcloud/azure-v2/utils/azureApi.js b/lib/pkgcloud/azure-v2/utils/azureApi.js new file mode 100644 index 000000000..75d15b4d9 --- /dev/null +++ b/lib/pkgcloud/azure-v2/utils/azureApi.js @@ -0,0 +1,198 @@ +var async = require('async'); +var errs = require('errs'); +var _ = require('lodash'); + +var msRestAzure = require('ms-rest-azure'); +var resourceManagement = require("azure-arm-resource"); + +var constants = require('./constants'); +var templates = require('./templates'); + +/** + * This callback type is called `requestCallback` and is displayed as a global symbol. + * + * @callback requestCallback + * @param {object} error + * @param {object} result + */ + +/** + * Request and save credentials for accessing azure ARM resources. + * @param {object} client object containing configuration. + * @param {requestCallback} callback to respond to when complete. + */ +function login(client, callback) { + + // Make sure credentials are refreshed by intervals + if (client.azure && client.azure.credentials && client.azure.lastRefresh) { + var now = new Date(); + if (now - client.azure.lastRefresh < constants.CREDENTIALS_LIFESPAN) { + return callback(null, client.azure.credentials); + } + } + + var config = client.config; + var servicePrincipal = config.servicePrincipal; + msRestAzure.loginWithServicePrincipalSecret( + servicePrincipal.clientId, + servicePrincipal.secret, + servicePrincipal.domain, + (err, credentials) => { + + if (err) { + errs.handle( + errs.create({ + message: 'There was a problem connecting to azure: ' + err + }), + callback + ); + } + + client.azure = client.azure || {}; + client.azure.credentials = credentials; + client.azure.lastRefresh = new Date(); + + return callback(null, credentials); + }); +} + +function setup(client, callback) { + login(client, (err, credentials) => { + + if (err) { + return callback(err); + } + + client.azure = client.azure || {}; + client.azure.location = client.azure.location || client.config.location; + if (client.azure.location) { + return callback(); + } + + if (client.config.resourceGroup) { + var resourceClient = new resourceManagement.ResourceManagementClient(credentials, client.config.subscriptionId); + resourceClient.resourceGroups.get(client.config.resourceGroup, (err, result) => { + + if (err) { + return callback(err); + } + + client.azure.location = result.location; + return callback(); + }); + } + }); +} + +/** + * list all resources inside a resource group + * @param {object} client + * @param {object} provider + * @param {string} provider.namespace + * @param {string} provider.resourceType + * @param {requestCallback} callback - callback with results + */ +function list(client, provider, callback) { + var client = this; + var config = client.config; + + async.waterfall([ + function (next) { + azureApi.login(client, next); + }, + function (credentials, next) { + + var resourceClient = new resourceManagement.ResourceManagementClient(client.credentials, config.subscriptionId); + resourceClient.resourceGroups.listResources( + config.resourceGroup, + { + filter: `resourceType eq '${provider.namespace}/${provider.resourceType}'` + }, + function(err, result, request, response) { + if (err) { + return errs.handle(errs.create('A problem during resource creation: ' + err), callback); + } + + callback(null, result); + } + ); + } + ], callback); +} + +/** + * list all resources inside a resource group + * @param {object} client + * @param {string} id + * @param {object} provider + * @param {string} provider.namespace + * @param {string} provider.resourceType + * @param {requestCallback} callback - callback with results + */ +function getById(client, provider, id, callback) { + var config = client.config; + + login(client, (err, credentials) => { + + if (err) { + return errs.handle(errs.create('A problem gettinh resource: ' + err), callback); + } + + var resourceClient = new resourceManagement.ResourceManagementClient(credentials, config.subscriptionId); + resourceClient.resources.get( + config.resourceGroup, + provider.namespace, provider.resourceType, + id, '', + constants.DEFAULT_API_VERSION, + (err, result, request, response) => { + if (err) { + return errs.handle(errs.create('A problem during resource creation: ' + err), callback); + } + + callback(null, result); + } + ); + }); + +} + +function create(client, provider, id, parameters, callback) { + var config = client.config; + + var _params = null; + async.waterfall([ + (next) => { + templates.resolve('storage', {}, next); + }, + (params, next) => { + _params = params; + login(client, next); + }, + (credentials, next) => { + + var resourceClient = new resourceManagement.ResourceManagementClient(credentials, config.subscriptionId); + resourceClient.resources.createOrUpdate( + config.resourceGroup, + provider.namespace, provider.resourceType, + id, '', + constants.DEFAULT_API_VERSION, + _params, + (err, result, request, response) => { + if (err) { + return errs.handle(errs.create('A problem during resource creation: ' + err), callback); + } + + callback(null, result); + } + ); + } + ], callback); +} + +module.exports = { + login, + setup, + list, + getById, + create +} \ No newline at end of file diff --git a/lib/pkgcloud/azure-v2/utils/constants.js b/lib/pkgcloud/azure-v2/utils/constants.js new file mode 100644 index 000000000..ef84653d6 --- /dev/null +++ b/lib/pkgcloud/azure-v2/utils/constants.js @@ -0,0 +1,39 @@ +/** +* Copyright (c) Microsoft. 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. +*/ + +var Constants = { + /** + * Uri endpoint for accessing blob storage + */ + STORAGE_URI_SUFFIX: 'blob.core.windows.net', + /** + * Azure credentials refresh rate in milliseconds + */ + CREDENTIALS_LIFESPAN: 5000, + /** + * default api version when querying ARM resrouces + */ + DEFAULT_API_VERSION: '2016-01-01', + /** + * Default size for new storage account + */ + DEFAULT_STORAGE_SKU: 'Standard_LRS', + /** + * Default container to work with when none is specified + */ + DEFAULT_STORAGE_CONTAINER: 'pkgcloud-container' +}; + +module.exports = Constants; \ No newline at end of file diff --git a/lib/pkgcloud/azure-v2/utils/templates/index.js b/lib/pkgcloud/azure-v2/utils/templates/index.js index fd70000d2..fcdf0feba 100644 --- a/lib/pkgcloud/azure-v2/utils/templates/index.js +++ b/lib/pkgcloud/azure-v2/utils/templates/index.js @@ -4,24 +4,15 @@ var async = require('async'); var _ = require('lodash'); exports.resolve = function (templateName, params, callback) { - var identityTemplate; - var paramsTemplate; async.waterfall([ function (next) { - load(templateName + '.identity.json', next); + load(templateName + '.json', next); }, - function (loadedTemplate, next) { - identityTemplate = loadedTemplate; - load(templateName + '.params.json', next); - }, - function (loadedTemplate, next) { + function (template, next) { // compile template with params - paramsTemplate = loadedTemplate; - var compiledIdentity = _.template(identityTemplate); - var compiledParams = _.template(paramsTemplate); - var identity = JSON.parse(compiledIdentity(params)); - var parameters = JSON.parse(compiledParams(params)); - next(null, identity, parameters); + var compiled = _.template(template); + var result = JSON.parse(compiled(params)); + next(null, result); } ], callback); } diff --git a/lib/pkgcloud/azure-v2/utils/templates/storage.json b/lib/pkgcloud/azure-v2/utils/templates/storage.json new file mode 100644 index 000000000..d863f8837 --- /dev/null +++ b/lib/pkgcloud/azure-v2/utils/templates/storage.json @@ -0,0 +1,7 @@ +{ + "location": "West Europe", + "tags": {}, + "sku": { + "name": "Standard_LRS" + } +} \ No newline at end of file diff --git a/package.json b/package.json index 9804308ee..c617ac9c3 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,8 @@ "async": "0.9.x", "aws-sdk": "^2.2.43", "azure-arm-resource": "^1.6.1-preview", + "azure-arm-storage": "^0.14.0-preview", + "azure-storage": "^1.4.0", "errs": "0.3.x", "eventemitter2": "0.4.x", "fast-json-patch": "0.5.x", From d49cfcdf95a2b5deea9b800df63370adaac51eda Mon Sep 17 00:00:00 2001 From: morsh Date: Mon, 2 Jan 2017 15:43:07 +0200 Subject: [PATCH 07/41] azure-v2 compute mgmt + moving to templates --- .../azure-v2/compute/client/flavors.js | 75 +++--- .../azure-v2/compute/client/images.js | 153 +++++++----- lib/pkgcloud/azure-v2/compute/client/index.js | 7 +- lib/pkgcloud/azure-v2/compute/client/keys.js | 98 -------- .../azure-v2/compute/client/servers.js | 234 ++++++++++-------- lib/pkgcloud/azure-v2/compute/flavor.js | 30 +-- lib/pkgcloud/azure-v2/compute/image.js | 19 +- lib/pkgcloud/azure-v2/compute/server.js | 9 +- .../azure-v2/storage/client/containers.js | 39 +-- lib/pkgcloud/azure-v2/storage/client/index.js | 7 +- .../templates/arm-compute-from-image.json | 183 ++++++++++++++ .../azure-v2/templates/arm-compute.json | 187 ++++++++++++++ .../azure-v2/templates/arm-storage.json | 22 ++ lib/pkgcloud/azure-v2/templates/index.js | 46 ++++ lib/pkgcloud/azure-v2/utils/azureApi.js | 18 +- lib/pkgcloud/azure-v2/utils/constants.js | 34 +-- .../azure-v2/utils/templates/index.js | 33 --- .../azure-v2/utils/templates/storage.json | 7 - .../azure-v2/utils/templates/vm.identity.json | 6 - .../azure-v2/utils/templates/vm.params.json | 37 --- package.json | 1 + 21 files changed, 790 insertions(+), 455 deletions(-) delete mode 100644 lib/pkgcloud/azure-v2/compute/client/keys.js create mode 100644 lib/pkgcloud/azure-v2/templates/arm-compute-from-image.json create mode 100644 lib/pkgcloud/azure-v2/templates/arm-compute.json create mode 100644 lib/pkgcloud/azure-v2/templates/arm-storage.json create mode 100644 lib/pkgcloud/azure-v2/templates/index.js delete mode 100644 lib/pkgcloud/azure-v2/utils/templates/index.js delete mode 100644 lib/pkgcloud/azure-v2/utils/templates/storage.json delete mode 100644 lib/pkgcloud/azure-v2/utils/templates/vm.identity.json delete mode 100644 lib/pkgcloud/azure-v2/utils/templates/vm.params.json diff --git a/lib/pkgcloud/azure-v2/compute/client/flavors.js b/lib/pkgcloud/azure-v2/compute/client/flavors.js index 144d3e1f4..3dbe50153 100644 --- a/lib/pkgcloud/azure-v2/compute/client/flavors.js +++ b/lib/pkgcloud/azure-v2/compute/client/flavors.js @@ -5,40 +5,57 @@ * */ -var pkgcloud = require('../../../../../lib/pkgcloud'), - base = require('../../../core/compute'), - compute = pkgcloud.providers.azure.compute; - -// -// ### function getFlavors (callback) -// #### @callback {function} f(err, flavors). `flavors` is an array that -// represents the flavors that are available to your account -// -// Lists all flavors available to your account. -// -exports.getFlavors = function getFlavors(callback) { +var _ = require('lodash'); +var async = require('async'); +var ComputeManagementClient = require('azure-arm-compute'); + +var azureApi = require('../../utils/azureApi'); + +/** + * Lists all flavors available to your account. + * @param {function} callback - cb(err, flavors). `flavors` is an array that + * represents the flavors that are available to your account + */ +function getFlavors(callback) { var self = this; - callback(null, Object.keys(compute.Flavor.options).map(function (name) { - return new compute.Flavor(self, { name: name }); - })); + async.waterfall([ + (next) => { + azureApi.setup(self, next); + }, + (next) => { + var client = new ComputeManagementClient(self.azure.credentials, self.config.subscriptionId); + client.virtualMachineSizes.list(self.azure.location, (err, results) => { + return err + ? next(err) + : next(null, results.map(res => new self.models.Flavor(self, res))); + }); + } + ], callback); }; -// -// ### function getFlavor (flavor, callback) -// #### @image {Flavor|String} Flavor ID or an Flavor -// #### @callback {function} f(err, flavor). `flavor` is an object that -// represents the flavor that was retrieved. -// -// Gets a specified flavor of AWS DataSets using the provided details -// object. -// -exports.getFlavor = function getFlavor(flavor, callback) { - var flavorId = flavor instanceof base.Flavor ? flavor.id : flavor; - - if (flavor instanceof base.Flavor) { +/** + * Gets a specified flavor of AWS DataSets using the provided details object. + * @param {Flavor|String} image - Flavor ID or an Flavor + * @param {function} callback cb(err, flavor). `flavor` is an object that + * represents the flavor that was retrieved. + */ +function getFlavor(flavor, callback) { + var self = this; + var flavorId = flavor instanceof self.models.Flavor ? flavor.id : flavor; + + if (flavor instanceof self.models.Flavor) { return callback(null, flavor); } - callback(null, new compute.Flavor(this, { id : flavorId })); + self.getFlavors((err, flavors) => { + return err ? + callback(err) : + callback(null, _.find(flavors, { id: flavorId })); + }); }; + +module.exports = { + getFlavors, + getFlavor +} \ No newline at end of file diff --git a/lib/pkgcloud/azure-v2/compute/client/images.js b/lib/pkgcloud/azure-v2/compute/client/images.js index a6b9c6cf8..ebbab868a 100644 --- a/lib/pkgcloud/azure-v2/compute/client/images.js +++ b/lib/pkgcloud/azure-v2/compute/client/images.js @@ -4,76 +4,99 @@ * (C) Microsoft Open Technologies, Inc. * */ -var pkgcloud = require('../../../../../lib/pkgcloud'), - base = require('../../../core/compute'), - compute = pkgcloud.providers.azure.compute, - azureApi = require('../../utils/azureApiV2'); - -// -// ### function getImages (callback) -// #### @callback {function} f(err, images). `images` is an array that -// represents the images that are available to your account -// -// Lists all images available to your account. -// +var _ = require('lodash'); +var async = require('async'); +var ComputeManagementClient = require('azure-arm-compute'); + +var azureApi = require('../../utils/azureApi'); +var constants = require('../../utils/constants'); + +/** + * Lists all images available to your account. + * @param {object} options **Optional** + * @param {string} options.publisher + * @param {string} options.offer + * @param {string} options.sku + * @param {function} callback - cb(err, images). `images` is an array that + * represents the images that are available to your account + */ exports.getImages = function getImages(options, callback) { + var self = this; + if (!callback && typeof options === 'function') { callback = options; options = null; } - var path = this.config.subscriptionId + '/services/images', - self = this; + options = options || {}; + var publisher = options.publisher || constants.DEFAULT_VM_IMAGE.PUBLISHER; + var offer = options.offer || constants.DEFAULT_VM_IMAGE.OFFER; + var sku = options.sku || constants.DEFAULT_VM_IMAGE.SKU; - return this.get(path, function (err, body, res) { - return err - ? callback(err) - : callback(null, self._toArray(body.OSImage).map(function (image) { - return new compute.Image(self, image); - }), res); - }); + async.waterfall([ + (next) => { + azureApi.setup(self, next); + }, + (next) => { + var client = new ComputeManagementClient(self.azure.credentials, self.config.subscriptionId); + client.virtualMachineImages.list(self.azure.location, publisher, offer, sku, (err, results) => { + return err + ? next(err) + : next(null, results.map(res => new self.models.Image(self, res, publisher, offer, sku))); + }); + } + ], callback); }; -// ### function getImage (image, callback) -// #### @image {Image|String} Image id or an Image -// #### @callback {function} f(err, image). `image` is an object that -// represents the image that was retrieved. -// -// Gets a specified image of Azure using the provided details -// object. -// -exports.getImage = function getImage(image, callback) { - var self = this, - imageId = image instanceof base.Image ? image.id : image, - path = this.config.subscriptionId + '/services/images/' + imageId; +/** + * Gets a specified image of Azure using the provided details object. + * @param {Image|String} image Image id or an Image + * @param {string} options.publisher + * @param {string} options.offer + * @param {string} options.sku + * @param {function} callback - cb(err, image). `image` is an object that + * represents the image that was retrieved. + */ +exports.getImage = function getImage(image , options, callback) { + var self = this; + var imageId = image instanceof self.models.Image ? image.id : image; + var version = image instanceof self.models.Image ? image.name : image; - this.get(path, function (err, body, res) { + if (!callback && typeof options === 'function') { + callback = options; + options = null; + } - if (err) { - return callback(err); - } + options = options || {}; + var publisher = options.publisher || constants.DEFAULT_VM_IMAGE.PUBLISHER; + var offer = options.offer || constants.DEFAULT_VM_IMAGE.OFFER; + var sku = options.sku || constants.DEFAULT_VM_IMAGE.SKU; - var result = null; - if (body) { - result = new compute.Image(self, body); + async.waterfall([ + (next) => { + azureApi.setup(self, next); + }, + (next) => { + var client = new ComputeManagementClient(self.azure.credentials, self.config.subscriptionId); + client.virtualMachineImages.get(self.azure.location, publisher, offer, sku, version, (err, result) => { + return err + ? next(err) + : next(null, new self.models.Image(self, result, publisher, offer, sku, version)); + }); } - - return result - ? callback(null, result, res) - : callback(new Error('Image not found')); - }); + ], callback); }; -// -// ### function createImage(options, callback) -// #### @id {Object} an object literal with options -// #### @name {String} String name of the image -// #### @server {Server} the server to use -// #### @callback {function} f(err, image). `image` is an object that -// represents the image that was created. -// -// Creates an image in Azure based on a server -// +/** + * ### function createImage(options, callback) + * #### @id {Object} an object literal with options + * #### @name {String} String name of the image + * #### @server {Server} the server to use + * #### @callback {function} f(err, image). `image` is an object that + * represents the image that was created. + * + * Creates an image in Azure based on a server + */ exports.createImage = function createImage(options, callback) { options || (options = {}); @@ -86,7 +109,7 @@ exports.createImage = function createImage(options, callback) { } var self = this, - serverId = options.server instanceof base.Server + serverId = options.server instanceof self.models.Server ? options.server.id : options.server; @@ -97,17 +120,17 @@ exports.createImage = function createImage(options, callback) { }); }; -// -// ### function destroyImage(image, callback) -// #### @image {Image|String} Image id or an Image -// #### @callback {function} f(err, image). `image` is an object that -// represents the image that was deleted. -// -// Destroys an image in Azure -// +/** + * ### function destroyImage(image, callback) + * #### @image {Image|String} Image id or an Image + * #### @callback {function} f(err, image). `image` is an object that + * represents the image that was deleted. + * + * Destroys an image in Azure + */ exports.destroyImage = function destroyImage(image, callback) { var self = this, - imageId = image instanceof base.Image ? image.id : image, + imageId = image instanceof self.models.Image ? image.id : image, path = self.config.subscriptionId + '/services/images/' + imageId; self._xmlRequest({ diff --git a/lib/pkgcloud/azure-v2/compute/client/index.js b/lib/pkgcloud/azure-v2/compute/client/index.js index ffd13964d..7528a3322 100644 --- a/lib/pkgcloud/azure-v2/compute/client/index.js +++ b/lib/pkgcloud/azure-v2/compute/client/index.js @@ -17,13 +17,16 @@ var util = require('util'), var Client = exports.Client = function (options) { azure.Client.call(this, options); + this.models = this.models || {}; + this.models.Flavor = require('../flavor').Flavor; + this.models.Image = require('../image').Image; + this.models.Server = require('../server').Server; + _.extend(this, require('./flavors')); _.extend(this, require('./images')); _.extend(this, require('./servers')); - _.extend(this, require('./keys')); this.serversUrl = options.serversUrl || azureApi.MANAGEMENT_ENDPOINT; - this.version = azureApi.MANAGEMENT_API_VERSION; this.subscriptionId = this.config.subscriptionId; this.azureKeys = { diff --git a/lib/pkgcloud/azure-v2/compute/client/keys.js b/lib/pkgcloud/azure-v2/compute/client/keys.js deleted file mode 100644 index 500008670..000000000 --- a/lib/pkgcloud/azure-v2/compute/client/keys.js +++ /dev/null @@ -1,98 +0,0 @@ -/* - * keys.js: Implementation of Azure SSH keys Client. - * - * (C) Microsoft Open Technologies, Inc. - * - */ - -var errs = require('errs'); - -// -// ### function listKeys (options, callback) -// #### @options {Object} **Optional** Filter parameters when listing keys -// #### @callback {function} Continuation to respond to when complete. -// -// Lists all EC2 Key Pairs matching the specified `options`. -// -exports.listKeys = function (options, callback) { - if (!callback && typeof options === 'function') { - callback = options; - options = {}; - } - - var self = this; - options = options || {}; - - return this._query('DescribeKeyPairs', options, function (err, body) { - return err - ? callback(err) - : callback(null, self._toArray(body.keySet.item)); - }); -}; - -// -// ### function getKey (name, callback) -// #### @name {string} Name of the EC2 key pair to get -// #### @callback {function} Continuation to respond to when complete. -// -// Gets the details of the EC2 Key Pair with the specified `name`. -// -exports.getKey = function (name, callback) { - return this.listKeys({ - 'KeyName.1': name - }, function (err, body) { - return err - ? callback(err) - : callback(null, body[0]); - }); -}; - -// -// ### function addKey (options, callback) -// #### @options {Object} SSH Public Key details -// #### @name {string} String name of the key -// #### @key {string} SSH Public Key -// #### @callback {function} Continuation to respond to when complete. -// -// Adds an EC2 Key Pair with the specified `options`. -// -exports.addKey = function (options, callback) { - if (!options || !options.key || !options.name) { - return errs.handle( - errs.create({ message: '`key` and `name` are required options.' }), - callback - ); - } - - return this._query( - 'ImportKeyPair', - { - KeyName: options.name, - PublicKeyMaterial: new Buffer(options.key).toString('base64') - }, - function (err) { - return err - ? callback(err) - : callback(null, true); - } - ); -}; - -// -// ### function getKey (name, callback) -// #### @name {string} Name of the EC2 key pair to destroy -// #### @callback {function} Continuation to respond to when complete. -// -// Destroys EC2 Key Pair with the specified `name`. -// -exports.destroyKey = function (name, callback) { - return this._query( - 'DeleteKeyPair', - { KeyName: name }, - function (err) { - return err - ? callback(err) - : callback(null, true); - } - ); -}; diff --git a/lib/pkgcloud/azure-v2/compute/client/servers.js b/lib/pkgcloud/azure-v2/compute/client/servers.js index e2eb960ea..77d140f8e 100644 --- a/lib/pkgcloud/azure-v2/compute/client/servers.js +++ b/lib/pkgcloud/azure-v2/compute/client/servers.js @@ -4,130 +4,145 @@ * (C) Microsoft Open Technologies, Inc. * */ -var base = require('../../../core/compute'), - pkgcloud = require('../../../../../lib/pkgcloud'), - errs = require('errs'), - azureApi = require('../../utils/azureApiV2'), - compute = require('../server'); +var base = require('../../../core/compute'); +var pkgcloud = require('../../../../../lib/pkgcloud'); +var errs = require('errs'); +var compute = require('../server'); -// -// ### function getVersion (callback) -// #### @callback {function} f(err, version). -// -// Gets the current API version -// -exports.getVersion = function getVersion(callback) { - callback(null, this.version); +var async = require('async'); +var ComputeManagementClient = require('azure-arm-compute'); + +var azureApi = require('../../utils/azureApi'); +var constants = require('../../utils/constants'); +var templates = require('../../templates'); + +/** + * Gets the current API version + * @param {function} callback cb(err, version). + */ +function getVersion(callback) { + callback(null, constants.MANAGEMENT_API_VERSION); }; -// -// ### function getLimits (callback) -// #### @callback {function} f(err, version). -// -// Gets the current API limits -// -exports.getLimits = function getLimits(callback) { +/** + * Gets the current API limits + * @param {function} callback - cb(err, version). + */ +function getLimits(callback) { return errs.handle( errs.create({ message: 'Azure\'s API is not rate limited' }), callback ); }; -// -// ### function getServers (callback) -// #### @callback {function} f(err, servers). `servers` is an array that -// represents the servers that are available to your account -// -// Lists all servers available to your account. -// -exports.getServers = function getServers(callback) { +/** + * Lists all servers available to your account. + * @param {function} callback - cb(err, servers). `servers` is an array that + * represents the servers that are available to your account + */ +function getServers(callback) { var self = this; - azureApi.getServers(this, function (err, results) { - if (err) { - return callback(err); + async.waterfall([ + (next) => { + azureApi.login(self, next); + }, + (credentials, next) => { + var client = new ComputeManagementClient(credentials, self.config.subscriptionId); + client.virtualMachines.list(self.config.resourceGroup, (err, results) => { + return err + ? next(err) + : next(null, results.map(res => new self.models.Server(self, res))); + }); } - - callback(null, results.map(function (server) { - return new compute.Server(self, server); - })); - }); + ], callback); }; -// -// ### function getServer(server, callback) -// #### @server {Server|String} Server id or a server -// #### @callback {Function} f(err, serverId). -// -// Gets a server in Azure. -// -exports.getServer = function getServer(server, callback) { - var self = this, - serverId = server instanceof base.Server ? server.name : server; - - // azure does not like multiple server status requests - // setWait() does not wait for result of previous query before - // issuing a new query. - if (server instanceof compute.Server) { - if (server.requestPending) { - return callback(null, new compute.Server(self, server)); +/** + * Gets a server in Azure. + * @param {Server|String} server Server id or a server + * @param {Function} callback cb(err, serverId). + */ +function getServer(server, callback) { + var self = this; + var serverId = server instanceof self.models.Server ? server.name : server; + + async.waterfall([ + (next) => { + azureApi.login(self, next); + }, + (credentials, next) => { + var client = new ComputeManagementClient(credentials, self.config.subscriptionId); + + // This will ensure returning of instances running status + var options = { expand: 'instanceView' }; + client.virtualMachines.get(self.config.resourceGroup, serverId, options, (err, result) => { + return err + ? next(err) + : next(null, new self.models.Server(self, result)); + }); } - } - - server.requestPending = true; - azureApi.getServer(this, serverId, function (err, result) { - server.requestPending = false; - return !err - ? callback(null, new compute.Server(self, result)) - : callback(err); - }); + ], callback); }; -// -// ### function createServer (options, callback) -// #### @opts {Object} **Optional** options -// #### @name {String} **Optional** the name of server -// #### @image {String|Image} the image (AMI) to use -// #### @flavor {String|Flavor} **Optional** flavor to use for this image -// #### @callback {Function} f(err, server). -// -// Creates a server with the specified options. The flavor -// properties of the options can be instances of Flavor -// OR ids to those entities in Azure. -// -exports.createServer = function createServer(options, callback) { +/** + * Creates a server with the specified options + * + * @description The flavor + * properties of the options can be instances of Flavor + * OR ids to those entities in Azure. + * + * @param {Object} options - **Optional** options + * @param {String} options.name - **Optional** the name of server + * @param {Function} callback cb(err, server). + */ +function createServer(options, callback) { var self = this; - if (typeof options === 'function') { - callback = options; - options = {}; + 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' }), + callback + ); } - options = options || {}; // no args - azureApi.createServer(this, options, function (err, server) { - return !err - ? callback(null, new compute.Server(self, server)) - : callback(err); + if (!options.flavor) { + return errs.handle( + errs.create({ message: 'When creating an azure server a flavor or an image need to be supplied' }), + callback + ); + } + + var templateName = 'compute' + (options.imageSourceUri ? '-from-image' : ''); + templates.deploy(self, templateName, options, (err, result) => { + return err ? + callback(err) : + self.getServer(options.name, callback); }); }; -// -// ### function destroyServer(server, callback) -// #### @server {Server|String} Server id or a server -// #### @callback {Function} f(err, serverId). -// -// Destroy a server in Azure. -// -exports.destroyServer = function destroyServer(server, callback) { - var serverName = server.name; - - azureApi.destroyServer(this, serverName, function (err) { - if (callback) { - return !err - ? callback && callback(null, { ok: serverName }) - : callback && callback(err); +/** + * Destroy a server in Azure. + * @param {Server|string} server Server id or a server + * @param {function} callback cb(err, serverId). + */ +function destroyServer(server, callback) { + var self = this; + var serverId = server && server.name || server; + + async.waterfall([ + (next) => { + azureApi.login(self, next); + }, + (credentials, next) => { + var client = new ComputeManagementClient(credentials, self.config.subscriptionId); + client.virtualMachines.deleteMethod(self.config.resourceGroup, serverId, (err, result) => { + return err + ? next(err) + : next(null, serverId); + }); } - }); + ]); }; // @@ -137,7 +152,7 @@ exports.destroyServer = function destroyServer(server, callback) { // // Destroy a server in Azure. // -exports.stopServer = function stopServer(server, callback) { +function stopServer(server, callback) { var serverId = server instanceof base.Server ? server.id : server; azureApi.stopServer(this, serverId, function (err) { @@ -154,7 +169,7 @@ exports.stopServer = function stopServer(server, callback) { // // Creates a Hosted Service in Azure. // -exports.createHostedService = function createHostedService(serviceName, callback) { +function createHostedService(serviceName, callback) { azureApi.createHostedService(this, serviceName, function (err, res) { return !err ? callback(null, res) @@ -169,7 +184,7 @@ exports.createHostedService = function createHostedService(serviceName, callback // // Reboots a server // -exports.rebootServer = function rebootServer(server, callback) { +function rebootServer(server, callback) { var serverId = server instanceof base.Server ? server.id : server; azureApi.rebootServer(this, serverId, function (err) { @@ -187,9 +202,22 @@ exports.rebootServer = function rebootServer(server, callback) { // // Renames a server // -exports.renameServer = function renameServer(server, name, callback) { +function renameServer(server, name, callback) { return errs.handle( errs.create({ message: 'Not supported by Azure.' }), callback ); }; + +module.exports = { + getVersion, + getLimits, + getServers, + getServer, + createServer, + destroyServer, + stopServer, + createHostedService, + rebootServer, + renameServer +}; diff --git a/lib/pkgcloud/azure-v2/compute/flavor.js b/lib/pkgcloud/azure-v2/compute/flavor.js index ee2f5f66d..bd9d8cb71 100644 --- a/lib/pkgcloud/azure-v2/compute/flavor.js +++ b/lib/pkgcloud/azure-v2/compute/flavor.js @@ -14,23 +14,23 @@ var Flavor = exports.Flavor = function Flavor(client, details) { util.inherits(Flavor, base.Flavor); -Flavor.options = { - 'ExtraSmall': { ram: 0.768 * 1024, disk: 20 }, - 'Small': { ram: 1.75 * 1024, disk: 50 }, - 'Medium': { ram: 3.5 * 1024, disk: 100 }, - 'Large': { ram: 7 * 1024, disk: 200 }, - 'ExtraLarge': { ram: 14 * 1024, disk: 400 } -}; - +/** + * Assign parameters for size specifications according to azure API + * @param {object} details + * @param {number} details.maxDataDiskCount + * @param {number} details.memoryInMB + * @param {string} details.name + * @param {number} details.numberOfCores + * @param {number} details.osDiskSizeInMB + * @param {number} details.resourceDiskSizeInMB + * + * Todo: Make sure paramters are assigned correctly + */ Flavor.prototype._setProperties = function (details) { - var id = details.name || details.id || 'ExtraSmall'; - - if (!Flavor.options[id]) { - throw new TypeError('No such Azure Flavor: ' + id); - } + var id = details.name; this.id = id; this.name = id; - this.ram = Flavor.options[id].ram; - this.disk = Flavor.options[id].disk; + this.ram = details.memoryInMB * 1024; + this.disk = details.maxDataDiskCount; }; diff --git a/lib/pkgcloud/azure-v2/compute/image.js b/lib/pkgcloud/azure-v2/compute/image.js index 06a1682d7..3398c8d4e 100644 --- a/lib/pkgcloud/azure-v2/compute/image.js +++ b/lib/pkgcloud/azure-v2/compute/image.js @@ -8,15 +8,20 @@ var util = require('util'), base = require('../../core/compute/image'); -var Image = exports.Image = function Image(client, details) { - base.Image.call(this, client, details); +var Image = exports.Image = function Image(client, details, publisher, offer, sku, version) { + base.Image.call(this, client, details, publisher, offer, sku, version); }; util.inherits(Image, base.Image); -Image.prototype._setProperties = function (details) { - this.id = details.Name; - this.name = details.Name; - this.created = new Date(0); - this.details = this.azure = details; +Image.prototype._setProperties = function (details, publisher, offer, sku, version) { + this.id = details.id; + this.name = details.name; + this.location = details.location; + this.publisher = publisher; + this.offer = offer; + this.sku = sku; + this.version = version; + this.created = new Date(0); + this.details = this.azure = details; }; diff --git a/lib/pkgcloud/azure-v2/compute/server.js b/lib/pkgcloud/azure-v2/compute/server.js index b7de1f062..d742ad6b3 100644 --- a/lib/pkgcloud/azure-v2/compute/server.js +++ b/lib/pkgcloud/azure-v2/compute/server.js @@ -16,11 +16,12 @@ var Server = exports.Server = function Server(client, details) { util.inherits(Server, base.Server); -Server.prototype._setProperties = function (details) { +Server.prototype._setProperties = function (details, statuses) { details = details || {}; - this.id = details.id || ''; - this.name = details.name || ''; + this.id = details.id || ''; + this.name = details.name || ''; + this.location = details.location; //console.log('Status: ' + details.Status + ' RoleInstanceList: ' + roleInstance ? roleInstance.InstanceStatus : 'UNKNOWN'); @@ -32,7 +33,7 @@ Server.prototype._setProperties = function (details) { // deployment states unless something goes wrong // TODO: there doesn't seem to be an ERROR or FAIL status in pkgcloud - var statuses = details.statuses || []; + var statuses = details.instanceView && details.instanceView.statuses || []; var provisioningStatus = _.find(statuses, status => status.code.startsWith('ProvisioningState/')) || {}; var powerStateStatus = _.find(statuses, status => status.code.startsWith('PowerState/')) || {}; diff --git a/lib/pkgcloud/azure-v2/storage/client/containers.js b/lib/pkgcloud/azure-v2/storage/client/containers.js index b5887af95..5ff9238df 100644 --- a/lib/pkgcloud/azure-v2/storage/client/containers.js +++ b/lib/pkgcloud/azure-v2/storage/client/containers.js @@ -10,6 +10,7 @@ var StorageManagementClient = require('azure-arm-storage'); var base = require('../../../core/storage'); var pkgcloud = require('../../../../../lib/pkgcloud'); +var templates = require('../../templates'); var azureApi = require('../../utils/azureApi'); var constants = require('../../utils/constants'); @@ -63,7 +64,9 @@ function getContainer(container, callback) { /** * Create a new storage account - * @param {string|storage.Container} container - container name of container configuration + * @param {string|object} options - container name of container configuration + * @param {string} options.name - storage account name + * @param {string} options.type - storage account type * @param {function} callback - Continuation to respond to when complete. * * From Azure docs: @@ -73,33 +76,15 @@ function getContainer(container, callback) { * of the same name during this cleanup period, your call returns an error immediately. */ function createContainer(options, callback) { + var self = this; var containerName = options instanceof base.Container ? options.name : options; - var client = this; + var parameters = typeof options == 'string' ? { name: options } : options; - async.waterfall([ - (next) => { - azureApi.login(client, next); - }, - (creds, next) => { - azureApi.setup(client, next); - }, - (next) => { - var parameters = { - location: options.location || client.azure.location, - sku: { - name: options.accountType || constants.DEFAULT_STORAGE_SKU, - }, - kind: 'storage' - }; - - var storageClient = new StorageManagementClient(client.azure.credentials, client.config.subscriptionId); - return storageClient.storageAccounts.create(client.config.resourceGroup, containerName, parameters, (err, result) => { - return err - ? next(err) - : next(null, new (storage.Container)(client, result)); - }); - } - ], callback); + templates.deploy(self, 'storage', options, (err, result) => { + return err ? + callback(err) : + self.getContainer(containerName, callback); + }) }; /** @@ -153,7 +138,7 @@ function getContainerKey (container, callback) { client.azure = client.azure || {}; client.azure.storageKeys = client.azure.storageKeys || {}; - + if (client.azure.storageKeys[containerName]) { return callback(null, client.azure.storageKeys[containerName]); } diff --git a/lib/pkgcloud/azure-v2/storage/client/index.js b/lib/pkgcloud/azure-v2/storage/client/index.js index b2cacbfbc..ec192daaa 100644 --- a/lib/pkgcloud/azure-v2/storage/client/index.js +++ b/lib/pkgcloud/azure-v2/storage/client/index.js @@ -13,10 +13,9 @@ var util = require('util'), var Client = exports.Client = function (options) { azure.Client.call(this, options); - this.models = { - Container: require('../container').Container, - File: require('../file').File - }; + this.models = this.models || {}; + this.models.Container = require('../container').Container; + this.models.File = require('../file').File; _.extend(this, require('./containers')); _.extend(this, require('./files')); diff --git a/lib/pkgcloud/azure-v2/templates/arm-compute-from-image.json b/lib/pkgcloud/azure-v2/templates/arm-compute-from-image.json new file mode 100644 index 000000000..49aa2169c --- /dev/null +++ b/lib/pkgcloud/azure-v2/templates/arm-compute-from-image.json @@ -0,0 +1,183 @@ +{ + "$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" }, + + "imageSourceUri": { "type": "string" }, + "imageOS": { "type": "string", "defaultValue": "Linux" }, + + "storageAccountName": { "type": "string", "defaultValue": "_NONE_" }, + "storageAccountType": { "type": "string", "defaultValue": "Standard_LRS" }, + "storageContainerName": { "type": "string", "defaultValue": "vhds" }, + "storageOSDiskName": { "type": "string", "defaultValue": "osdisk" }, + "storageDataDisk1VhdName": { "type": "string", "defaultValue": "datadisk1" }, + + "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" }, + + "nicName": { "type": "string", "defaultValue": "_NONE_" } + }, + "variables": { + "vmName": "[parameters('name')]", + "vmSize": "[parameters('flavor')]", + + "storageAccountName": "[replace(parameters('storageAccountName'), '_NONE_', concat(replace(variables('vmName'), '-', ''), 'store'))]", + "publicIPAddressName": "[replace(parameters('publicIPAddressName'), '_NONE_', concat(variables('vmName'), '-public-ip'))]", + "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'))]" + }, + "resources": [ + { + "type": "Microsoft.Storage/storageAccounts", + "name": "[variables('storageAccountName')]", + "apiVersion": "2016-01-01", + "location": "[resourceGroup().location]", + "sku": { + "name": "[parameters('storageAccountType')]" + }, + "kind": "Storage", + "properties": {} + }, + { + "apiVersion": "2016-09-01", + "type": "Microsoft.Network/publicIPAddresses", + "name": "[variables('publicIPAddressName')]", + "location": "[resourceGroup().location]", + "properties": { + "publicIPAllocationMethod": "[parameters('publicIPAddressType')]", + "dnsSettings": { + "domainNameLabel": "[variables('dnsLabelPrefix')]" + } + } + }, + { + "apiVersion": "2016-09-01", + "type": "Microsoft.Network/virtualNetworks", + "name": "[variables('vnetName')]", + "location": "[resourceGroup().location]", + "properties": { + "addressSpace": { + "addressPrefixes": [ + "[parameters('vnetAddressPrefix')]" + ] + }, + "subnets": [ + { + "name": "[parameters('vnetSubnetName')]", + "properties": { + "addressPrefix": "[parameters('vnetSubnetPrefix')]" + } + } + ] + } + }, + { + "apiVersion": "2016-09-01", + "type": "Microsoft.Network/networkInterfaces", + "name": "[variables('nicName')]", + "location": "[resourceGroup().location]", + "dependsOn": [ + "[resourceId('Microsoft.Network/publicIPAddresses/', variables('publicIPAddressName'))]", + "[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')]" + } + } + } + ] + } + }, + { + "apiVersion": "2016-03-30", + "type": "Microsoft.Compute/virtualMachines", + "name": "[variables('vmName')]", + "location": "[resourceGroup().location]", + "dependsOn": [ + "[resourceId('Microsoft.Storage/storageAccounts/', variables('storageAccountName'))]", + "[resourceId('Microsoft.Network/networkInterfaces/', variables('nicName'))]" + ], + "properties": { + "hardwareProfile": { + "vmSize": "[variables('vmSize')]" + }, + "osProfile": { + "computerName": "[variables('vmName')]", + "adminUsername": "[parameters('username')]", + "adminPassword": "[parameters('password')]" + }, + "storageProfile": { + "osDisk": { + "name": "osdisk", + "osType": "[parameters('imageOS')]", + "caching": "ReadWrite", + "createOption": "FromImage", + "image": { + "uri": "[parameters('imageSourceUri')]" + }, + "vhd": { + "uri": "[concat(reference(concat('Microsoft.Storage/storageAccounts/', variables('storageAccountName')), '2016-01-01').primaryEndpoints.blob, parameters('storageContainerName'),'/',parameters('storageOSDiskName'),'.vhd')]" + } + }, + "dataDisks": [ + { + "name": "datadisk1", + "diskSizeGB": "100", + "lun": 0, + "vhd": { + "uri": "[concat(reference(concat('Microsoft.Storage/storageAccounts/', variables('storageAccountName')), '2016-01-01').primaryEndpoints.blob, parameters('storageContainerName'),'/',parameters('storageDataDisk1VhdName'),'.vhd')]" + }, + "createOption": "Empty" + } + ] + }, + "networkProfile": { + "networkInterfaces": [ + { + "id": "[resourceId('Microsoft.Network/networkInterfaces',variables('nicName'))]" + } + ] + }, + "diagnosticsProfile": { + "bootDiagnostics": { + "enabled": "true", + "storageUri": "[concat(reference(concat('Microsoft.Storage/storageAccounts/', variables('storageAccountName')), '2016-01-01').primaryEndpoints.blob)]" + } + } + } + } + ], + "outputs": { + "hostname": { + "type": "string", + "value": "[reference(variables('publicIPAddressName')).dnsSettings.fqdn]" + }, + "sshCommand": { + "type": "string", + "value": "[concat('ssh ', parameters('username'), '@', reference(variables('publicIPAddressName')).dnsSettings.fqdn)]" + } + } +} \ 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 new file mode 100644 index 000000000..467e2524d --- /dev/null +++ b/lib/pkgcloud/azure-v2/templates/arm-compute.json @@ -0,0 +1,187 @@ +{ + "$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" }, + + "storageAccountName": { "type": "string", "defaultValue": "_NONE_" }, + "storageAccountType": { "type": "string", "defaultValue": "Standard_LRS" }, + "storageContainerName": { "type": "string", "defaultValue": "vhds" }, + "storageOSDiskName": { "type": "string", "defaultValue": "osdisk" }, + "storageDataDisk1VhdName": { "type": "string", "defaultValue": "datadisk1" }, + + "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" }, + + "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" } + }, + "variables": { + "vmName": "[parameters('name')]", + "vmSize": "[parameters('flavor')]", + + "storageAccountName": "[replace(parameters('storageAccountName'), '_NONE_', concat(replace(variables('vmName'), '-', ''), 'store'))]", + "publicIPAddressName": "[replace(parameters('publicIPAddressName'), '_NONE_', concat(variables('vmName'), '-public-ip'))]", + "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'))]" + }, + "resources": [ + { + "type": "Microsoft.Storage/storageAccounts", + "name": "[variables('storageAccountName')]", + "apiVersion": "2016-01-01", + "location": "[resourceGroup().location]", + "sku": { + "name": "[parameters('storageAccountType')]" + }, + "kind": "Storage", + "properties": {} + }, + { + "apiVersion": "2016-09-01", + "type": "Microsoft.Network/publicIPAddresses", + "name": "[variables('publicIPAddressName')]", + "location": "[resourceGroup().location]", + "properties": { + "publicIPAllocationMethod": "[parameters('publicIPAddressType')]", + "dnsSettings": { + "domainNameLabel": "[variables('dnsLabelPrefix')]" + } + } + }, + { + "apiVersion": "2016-09-01", + "type": "Microsoft.Network/virtualNetworks", + "name": "[variables('vnetName')]", + "location": "[resourceGroup().location]", + "properties": { + "addressSpace": { + "addressPrefixes": [ + "[parameters('vnetAddressPrefix')]" + ] + }, + "subnets": [ + { + "name": "[parameters('vnetSubnetName')]", + "properties": { + "addressPrefix": "[parameters('vnetSubnetPrefix')]" + } + } + ] + } + }, + { + "apiVersion": "2016-09-01", + "type": "Microsoft.Network/networkInterfaces", + "name": "[variables('nicName')]", + "location": "[resourceGroup().location]", + "dependsOn": [ + "[resourceId('Microsoft.Network/publicIPAddresses/', variables('publicIPAddressName'))]", + "[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')]" + } + } + } + ] + } + }, + { + "apiVersion": "2016-03-30", + "type": "Microsoft.Compute/virtualMachines", + "name": "[variables('vmName')]", + "location": "[resourceGroup().location]", + "dependsOn": [ + "[resourceId('Microsoft.Storage/storageAccounts/', variables('storageAccountName'))]", + "[resourceId('Microsoft.Network/networkInterfaces/', variables('nicName'))]" + ], + "properties": { + "hardwareProfile": { + "vmSize": "[variables('vmSize')]" + }, + "osProfile": { + "computerName": "[variables('vmName')]", + "adminUsername": "[parameters('username')]", + "adminPassword": "[parameters('password')]" + }, + "storageProfile": { + "imageReference": { + "publisher": "[parameters('imagePublisher')]", + "offer": "[parameters('imageOffer')]", + "sku": "[parameters('imageSku')]", + "version": "[parameters('imageVersion')]" + }, + "osDisk": { + "name": "osdisk", + "vhd": { + "uri": "[concat(reference(concat('Microsoft.Storage/storageAccounts/', variables('storageAccountName')), '2016-01-01').primaryEndpoints.blob, parameters('storageContainerName'),'/',parameters('storageOSDiskName'),'.vhd')]" + }, + "caching": "ReadWrite", + "createOption": "FromImage" + }, + "dataDisks": [ + { + "name": "datadisk1", + "diskSizeGB": "100", + "lun": 0, + "vhd": { + "uri": "[concat(reference(concat('Microsoft.Storage/storageAccounts/', variables('storageAccountName')), '2016-01-01').primaryEndpoints.blob, parameters('storageContainerName'),'/',parameters('storageDataDisk1VhdName'),'.vhd')]" + }, + "createOption": "Empty" + } + ] + }, + "networkProfile": { + "networkInterfaces": [ + { + "id": "[resourceId('Microsoft.Network/networkInterfaces',variables('nicName'))]" + } + ] + }, + "diagnosticsProfile": { + "bootDiagnostics": { + "enabled": "true", + "storageUri": "[concat(reference(concat('Microsoft.Storage/storageAccounts/', variables('storageAccountName')), '2016-01-01').primaryEndpoints.blob)]" + } + } + } + } + ], + "outputs": { + "hostname": { + "type": "string", + "value": "[reference(variables('publicIPAddressName')).dnsSettings.fqdn]" + }, + "sshCommand": { + "type": "string", + "value": "[concat('ssh ', parameters('username'), '@', reference(variables('publicIPAddressName')).dnsSettings.fqdn)]" + } + } +} \ No newline at end of file diff --git a/lib/pkgcloud/azure-v2/templates/arm-storage.json b/lib/pkgcloud/azure-v2/templates/arm-storage.json new file mode 100644 index 000000000..e05084559 --- /dev/null +++ b/lib/pkgcloud/azure-v2/templates/arm-storage.json @@ -0,0 +1,22 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "name": { "type": "string" }, + "type": { "type": "string", "defaultValue": "Standard_LRS" } + }, + "variables": { }, + "resources": [ + { + "type": "Microsoft.Storage/storageAccounts", + "name": "[parameters('name')]", + "apiVersion": "2016-01-01", + "location": "[resourceGroup().location]", + "sku": { + "name": "[parameters('type')]" + }, + "kind": "Storage", + "properties": {} + } + ] +} \ No newline at end of file diff --git a/lib/pkgcloud/azure-v2/templates/index.js b/lib/pkgcloud/azure-v2/templates/index.js new file mode 100644 index 000000000..bba8cc460 --- /dev/null +++ b/lib/pkgcloud/azure-v2/templates/index.js @@ -0,0 +1,46 @@ +var path = require('path'); +var fs = require("fs"); +var async = require('async'); + +var resourceManagement = require('azure-arm-resource'); + +var azureApi = require('../utils/azureApi'); + +function resolve(templateId) { + var templatePath = path.join(__dirname, '..', 'templates', 'arm-' + templateId + '.json'); + var contents = fs.readFileSync(templatePath); + return JSON.parse(contents); +} + +function deploy(client, templateName, options, callback) { + + async.waterfall([ + (next) => { + azureApi.login(client, next); + }, + (credentials, next) => { + var template = resolve(templateName); + var parameters = { + properties: { + template: template, + parameters: {}, + mode: 'Incremental' + } + }; + Object.keys(options).forEach(key => parameters.properties.parameters[key] = { value: options[key] }); + + var deploymentName = 'pkgc-' + (new Date()).toISOString().replace(/\:|Z|\.|\-/g, '').replace(/T/g, '-'); + var resourceClient = new resourceManagement.ResourceManagementClient(credentials, client.config.subscriptionId); + resourceClient.deployments.createOrUpdate(client.config.resourceGroup, deploymentName, parameters, (err, result) => { + return err + ? next(err) + : next(null, result); + }); + } + ], callback); +} + +module.exports = { + resolve, + deploy +} \ No newline at end of file diff --git a/lib/pkgcloud/azure-v2/utils/azureApi.js b/lib/pkgcloud/azure-v2/utils/azureApi.js index 75d15b4d9..e4e6d66e3 100644 --- a/lib/pkgcloud/azure-v2/utils/azureApi.js +++ b/lib/pkgcloud/azure-v2/utils/azureApi.js @@ -56,6 +56,12 @@ function login(client, callback) { }); } +/** + * Setting up an azure session including querying the default location from the resource group + * used by the current configuration + * @param {object} client object containing configuration. + * @param {requestCallback} callback to respond to when complete. + */ function setup(client, callback) { login(client, (err, credentials) => { @@ -123,10 +129,10 @@ function list(client, provider, callback) { /** * list all resources inside a resource group * @param {object} client - * @param {string} id * @param {object} provider * @param {string} provider.namespace * @param {string} provider.resourceType + * @param {string} id * @param {requestCallback} callback - callback with results */ function getById(client, provider, id, callback) { @@ -156,6 +162,16 @@ function getById(client, provider, id, callback) { } +/** + * Create a resource inside a resource group + * @param {object} client + * @param {object} provider + * @param {string} provider.namespace + * @param {string} provider.resourceType + * @param {string} id + * @param {object} parameters - parameters to be used during deployment + * @param {requestCallback} callback - callback with results + */ function create(client, provider, id, parameters, callback) { var config = client.config; diff --git a/lib/pkgcloud/azure-v2/utils/constants.js b/lib/pkgcloud/azure-v2/utils/constants.js index ef84653d6..49ee5bd53 100644 --- a/lib/pkgcloud/azure-v2/utils/constants.js +++ b/lib/pkgcloud/azure-v2/utils/constants.js @@ -14,26 +14,26 @@ */ var Constants = { - /** - * Uri endpoint for accessing blob storage - */ + /** Uri endpoint for accessing blob storage */ + MANAGEMENT_API_VERSION: '2016-03-30', + /** Endpoint for azure management */ + MANAGEMENT_ENDPOINT: 'management.core.windows.net', + /** Uri endpoint for accessing blob storage */ STORAGE_URI_SUFFIX: 'blob.core.windows.net', - /** - * Azure credentials refresh rate in milliseconds - */ + /** Azure credentials refresh rate in milliseconds */ CREDENTIALS_LIFESPAN: 5000, - /** - * default api version when querying ARM resrouces - */ - DEFAULT_API_VERSION: '2016-01-01', - /** - * Default size for new storage account - */ + /** default api version when querying ARM resrouces */ + DEFAULT_API_VERSION: '2016-03-30', + /** Default size for new storage account */ DEFAULT_STORAGE_SKU: 'Standard_LRS', - /** - * Default container to work with when none is specified - */ - DEFAULT_STORAGE_CONTAINER: 'pkgcloud-container' + /** Default container to work with when none is specified */ + DEFAULT_STORAGE_CONTAINER: 'pkgcloud-container', + /** Default Image details when creating VM from image */ + DEFAULT_VM_IMAGE: { + PUBLISHER: 'MicrosoftWindowsServer', + OFFER: 'WindowsServer', + SKU: '2012-R2-Datacenter' + } }; module.exports = Constants; \ No newline at end of file diff --git a/lib/pkgcloud/azure-v2/utils/templates/index.js b/lib/pkgcloud/azure-v2/utils/templates/index.js deleted file mode 100644 index fcdf0feba..000000000 --- a/lib/pkgcloud/azure-v2/utils/templates/index.js +++ /dev/null @@ -1,33 +0,0 @@ -var fs = require('fs'); -var path = require('path'); -var async = require('async'); -var _ = require('lodash'); - -exports.resolve = function (templateName, params, callback) { - async.waterfall([ - function (next) { - load(templateName + '.json', next); - }, - function (template, next) { - // compile template with params - var compiled = _.template(template); - var result = JSON.parse(compiled(params)); - next(null, result); - } - ], callback); -} - -var load = exports.load = function (templateName, callback) { - var templatePath = path.join(__dirname, templateName); - fs.readFile(templatePath, 'utf8', function (err, data) { - callback(err, data); - }); -}; - -var compile = exports.compile = function (name, params, callback) { - var path = PATH.join(__dirname, name); - fs.readFile(path, 'utf8', function (err, data) { - var compiled = _.template(data); - callback(err, compiled(params)); - }); -}; \ No newline at end of file diff --git a/lib/pkgcloud/azure-v2/utils/templates/storage.json b/lib/pkgcloud/azure-v2/utils/templates/storage.json deleted file mode 100644 index d863f8837..000000000 --- a/lib/pkgcloud/azure-v2/utils/templates/storage.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "location": "West Europe", - "tags": {}, - "sku": { - "name": "Standard_LRS" - } -} \ No newline at end of file diff --git a/lib/pkgcloud/azure-v2/utils/templates/vm.identity.json b/lib/pkgcloud/azure-v2/utils/templates/vm.identity.json deleted file mode 100644 index fff6e1d9a..000000000 --- a/lib/pkgcloud/azure-v2/utils/templates/vm.identity.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "resourceName": "<%= NAME %>", - "resourceProviderNamespace": "Microsoft.Compute", - "resourceType": "virtualMachines", - "resourceProviderApiVersion": "<%= API_VERSION %>" -} \ No newline at end of file diff --git a/lib/pkgcloud/azure-v2/utils/templates/vm.params.json b/lib/pkgcloud/azure-v2/utils/templates/vm.params.json deleted file mode 100644 index d939e0362..000000000 --- a/lib/pkgcloud/azure-v2/utils/templates/vm.params.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "location": "<%= LOCATION %>", - "properties": { - "hardwareProfile": { - "vmSize": "<%= VM_SIZE %>" - }, - "osProfile": { - "computerName": "<%= VM_NAME %>", - "adminUsername": "<%= USERNAME %>", - "adminPassword": "<%= PASSWORD %>" - }, - "storageProfile": { - "imageReference": { - "publisher": "Canonical", - "offer": "UbuntuServer", - "sku": "16.04.0-LTS", - "version": "latest" - }, - "osDisk": { - "name": "osdisk", - "vhd": { - "uri": "<%= OS_DISK_URI %>" - }, - "caching": "ReadWrite", - "createOption": "FromImage" - } - }, - "networkProfile": { - "networkInterfaces": [ - { - "id": "<%= NICK_ID %>", - "primary": true - } - ] - } - } -} \ No newline at end of file diff --git a/package.json b/package.json index c617ac9c3..c03fd267f 100644 --- a/package.json +++ b/package.json @@ -57,6 +57,7 @@ "dependencies": { "async": "0.9.x", "aws-sdk": "^2.2.43", + "azure-arm-compute": "^0.19.1", "azure-arm-resource": "^1.6.1-preview", "azure-arm-storage": "^0.14.0-preview", "azure-storage": "^1.4.0", From 69ee43613b6bfddcda396b21ea05845f01a37630 Mon Sep 17 00:00:00 2001 From: morsh Date: Mon, 2 Jan 2017 15:46:55 +0200 Subject: [PATCH 08/41] not including .private. files --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index a307754e3..c6f240b2a 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,5 @@ azure_error coverage.html .coveralls.yml pkgcloud.lcov* -node-jscoverage \ No newline at end of file +node-jscoverage +*.private.* \ No newline at end of file From 61b7b3ee0f4e6debda97db1a12d6dd5e261ed7b4 Mon Sep 17 00:00:00 2001 From: morsh Date: Mon, 2 Jan 2017 17:00:06 +0200 Subject: [PATCH 09/41] adjustments to code with self --- .vscode/launch.json | 6 +- lib/pkgcloud/azure-v2/azure-api.js | 102 +++ lib/pkgcloud/azure-v2/client.js | 9 +- .../azure-v2/compute/client/flavors.js | 26 +- .../azure-v2/compute/client/images.js | 61 +- lib/pkgcloud/azure-v2/compute/client/index.js | 17 +- .../azure-v2/compute/client/servers.js | 130 ++-- .../azure-v2/{utils => }/constants.js | 0 .../azure-v2/storage/client/containers.js | 159 ++-- lib/pkgcloud/azure-v2/storage/client/files.js | 79 +- lib/pkgcloud/azure-v2/storage/client/index.js | 7 +- .../azure-v2/storage/client/storage-api.js | 36 - lib/pkgcloud/azure-v2/templates/index.js | 53 +- lib/pkgcloud/azure-v2/utils/_azureApi.js | 709 ------------------ lib/pkgcloud/azure-v2/utils/azureApi.js | 214 ------ lib/pkgcloud/azure-v2/utils/azureApiV2.js | 337 --------- 16 files changed, 378 insertions(+), 1567 deletions(-) create mode 100644 lib/pkgcloud/azure-v2/azure-api.js rename lib/pkgcloud/azure-v2/{utils => }/constants.js (100%) delete mode 100644 lib/pkgcloud/azure-v2/storage/client/storage-api.js delete mode 100644 lib/pkgcloud/azure-v2/utils/_azureApi.js delete mode 100644 lib/pkgcloud/azure-v2/utils/azureApi.js delete mode 100644 lib/pkgcloud/azure-v2/utils/azureApiV2.js diff --git a/.vscode/launch.json b/.vscode/launch.json index f2eb25ef2..1aa77eba6 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -15,7 +15,7 @@ "type": "node", "request": "launch", "name": "Launch Azure V2 Destroy", - "program": "${workspaceRoot}//examples//compute//azure-v2-destroy.js", + "program": "${workspaceRoot}//examples//compute//azure-v2-destroy.private.js", "cwd": "${workspaceRoot}", "stopOnEntry": true }, @@ -23,7 +23,7 @@ "type": "node", "request": "launch", "name": "Launch Azure V2 Create", - "program": "${workspaceRoot}//examples//compute//azure-v2.js", + "program": "${workspaceRoot}//examples//compute//azure-v2.private.js", "cwd": "${workspaceRoot}", "stopOnEntry": true }, @@ -31,7 +31,7 @@ "type": "node", "request": "launch", "name": "Launch Azure V2 Storage", - "program": "${workspaceRoot}//examples//storage//azure-v2.js", + "program": "${workspaceRoot}//examples//storage//azure-v2.private.js", "cwd": "${workspaceRoot}", "stopOnEntry": true }, diff --git a/lib/pkgcloud/azure-v2/azure-api.js b/lib/pkgcloud/azure-v2/azure-api.js new file mode 100644 index 000000000..dcc97e232 --- /dev/null +++ b/lib/pkgcloud/azure-v2/azure-api.js @@ -0,0 +1,102 @@ +var errs = require('errs'); + +var msRestAzure = require('ms-rest-azure'); +var resourceManagement = require("azure-arm-resource"); + +var constants = require('./constants'); + +/** + * This callback type is called `requestCallback` and is displayed as a global symbol. + * + * @callback requestCallback + * @param {object} error + * @param {object} result + */ + +/** + * Request and save credentials for accessing azure ARM resources. + * @param {object} client object containing configuration. + * @param {boolean} setupLocation Should setup a location from the resource group. + * @param {requestCallback} callback to respond to when complete. + */ +function login(setupLocation, callback) { + + var client = this; + if (typeof setupLocation == 'function' && typeof callback === 'undefined') { + callback = setupLocation; + setupLocation = null; + } + + // Make sure credentials are refreshed by intervals + if (client.azure && client.azure.credentials && client.azure.lastRefresh) { + var now = new Date(); + if (now - client.azure.lastRefresh < constants.CREDENTIALS_LIFESPAN) { + return callback(null, client.azure.credentials); + } + } + + var config = client.config; + var servicePrincipal = config.servicePrincipal; + msRestAzure.loginWithServicePrincipalSecret( + servicePrincipal.clientId, + servicePrincipal.secret, + servicePrincipal.domain, + (err, credentials) => { + + if (err) { + errs.handle( + errs.create({ + message: 'There was a problem connecting to azure: ' + err + }), + callback + ); + } + + client.azure = client.azure || {}; + client.azure.credentials = credentials; + client.azure.lastRefresh = new Date(); + + if (setupLocation) { + return this.setup(credentials, err => callback(err, credentials)) + } else { + return callback(null, credentials); + } + }); +} + +/** + * Setting up an azure session including querying the default location from the resource group + * used by the current configuration + * @param {object} client object containing configuration. + * @param {requestCallback} callback to respond to when complete. + */ +function setup(credentials, callback) { + var client = this; + + if (err) { + return callback(err); + } + + client.azure = client.azure || {}; + client.azure.location = client.azure.location || client.config.location; + if (client.azure.location) { + return callback(); + } + + if (client.config.resourceGroup) { + var resourceClient = new resourceManagement.ResourceManagementClient(credentials, client.config.subscriptionId); + resourceClient.resourceGroups.get(client.config.resourceGroup, (err, result) => { + + if (err) { + return callback(err); + } + + client.azure.location = result.location; + return callback(); + }); + } +} + +module.exports = { + login +} \ No newline at end of file diff --git a/lib/pkgcloud/azure-v2/client.js b/lib/pkgcloud/azure-v2/client.js index d48920f55..3337b9a6a 100644 --- a/lib/pkgcloud/azure-v2/client.js +++ b/lib/pkgcloud/azure-v2/client.js @@ -5,8 +5,11 @@ * */ -var util = require('util'), - base = require('../core/base'); +var util = require('util'); + +var base = require('../core/base'); +var azureApi = require('./azure-api'); +var templates = require('./templates'); var Client = exports.Client = function (options) { base.Client.call(this, options); @@ -16,6 +19,8 @@ var Client = exports.Client = function (options) { // Allow overriding serversUrl in child classes this.provider = 'azure-v2'; this.protocol = options.protocol || 'https://'; + this.login = azureApi.login.bind(this); + this.deploy = templates.deploy.bind(this); if (!this.before) { this.before = []; diff --git a/lib/pkgcloud/azure-v2/compute/client/flavors.js b/lib/pkgcloud/azure-v2/compute/client/flavors.js index 3dbe50153..8fc76ed69 100644 --- a/lib/pkgcloud/azure-v2/compute/client/flavors.js +++ b/lib/pkgcloud/azure-v2/compute/client/flavors.js @@ -9,8 +9,6 @@ var _ = require('lodash'); var async = require('async'); var ComputeManagementClient = require('azure-arm-compute'); -var azureApi = require('../../utils/azureApi'); - /** * Lists all flavors available to your account. * @param {function} callback - cb(err, flavors). `flavors` is an array that @@ -19,19 +17,19 @@ var azureApi = require('../../utils/azureApi'); function getFlavors(callback) { var self = this; - async.waterfall([ - (next) => { - azureApi.setup(self, next); - }, - (next) => { - var client = new ComputeManagementClient(self.azure.credentials, self.config.subscriptionId); - client.virtualMachineSizes.list(self.azure.location, (err, results) => { - return err - ? next(err) - : next(null, results.map(res => new self.models.Flavor(self, res))); - }); + self.login(true, err => { + + if (err) { + return callback(err); } - ], callback); + + var client = new ComputeManagementClient(self.azure.credentials, self.config.subscriptionId); + client.virtualMachineSizes.list(self.azure.location, (err, results) => { + return err + ? callback(err) + : callback(null, results.map(res => new self.models.Flavor(self, res))); + }); + }); }; /** diff --git a/lib/pkgcloud/azure-v2/compute/client/images.js b/lib/pkgcloud/azure-v2/compute/client/images.js index ebbab868a..a7cdc85ef 100644 --- a/lib/pkgcloud/azure-v2/compute/client/images.js +++ b/lib/pkgcloud/azure-v2/compute/client/images.js @@ -8,8 +8,7 @@ var _ = require('lodash'); var async = require('async'); var ComputeManagementClient = require('azure-arm-compute'); -var azureApi = require('../../utils/azureApi'); -var constants = require('../../utils/constants'); +var constants = require('../../constants'); /** * Lists all images available to your account. @@ -33,19 +32,19 @@ exports.getImages = function getImages(options, callback) { var offer = options.offer || constants.DEFAULT_VM_IMAGE.OFFER; var sku = options.sku || constants.DEFAULT_VM_IMAGE.SKU; - async.waterfall([ - (next) => { - azureApi.setup(self, next); - }, - (next) => { - var client = new ComputeManagementClient(self.azure.credentials, self.config.subscriptionId); - client.virtualMachineImages.list(self.azure.location, publisher, offer, sku, (err, results) => { - return err - ? next(err) - : next(null, results.map(res => new self.models.Image(self, res, publisher, offer, sku))); - }); + self.login(true, err => { + + if (err) { + return callback(err); } - ], callback); + + var client = new ComputeManagementClient(self.azure.credentials, self.config.subscriptionId); + client.virtualMachineImages.list(self.azure.location, publisher, offer, sku, (err, results) => { + return err + ? callback(err) + : callback(null, results.map(res => new self.models.Image(self, res, publisher, offer, sku))); + }); + }); }; /** @@ -72,19 +71,19 @@ exports.getImage = function getImage(image , options, callback) { var offer = options.offer || constants.DEFAULT_VM_IMAGE.OFFER; var sku = options.sku || constants.DEFAULT_VM_IMAGE.SKU; - async.waterfall([ - (next) => { - azureApi.setup(self, next); - }, - (next) => { - var client = new ComputeManagementClient(self.azure.credentials, self.config.subscriptionId); - client.virtualMachineImages.get(self.azure.location, publisher, offer, sku, version, (err, result) => { - return err - ? next(err) - : next(null, new self.models.Image(self, result, publisher, offer, sku, version)); - }); + self.login(true, err => { + + if (err) { + return callback(err); } - ], callback); + + var client = new ComputeManagementClient(self.azure.credentials, self.config.subscriptionId); + client.virtualMachineImages.get(self.azure.location, publisher, offer, sku, version, (err, result) => { + return err + ? callback(err) + : callback(null, new self.models.Image(self, result, publisher, offer, sku, version)); + }); + }); }; /** @@ -113,11 +112,11 @@ exports.createImage = function createImage(options, callback) { ? options.server.id : options.server; - azureApi.createImage(this, serverId, options.name, function (err, result) { - return !err - ? self.getImage(result, callback) - : callback(err); - }); + // azureApi.createImage(this, serverId, options.name, function (err, result) { + // return !err + // ? self.getImage(result, callback) + // : callback(err); + // }); }; /** diff --git a/lib/pkgcloud/azure-v2/compute/client/index.js b/lib/pkgcloud/azure-v2/compute/client/index.js index 7528a3322..99ea582db 100644 --- a/lib/pkgcloud/azure-v2/compute/client/index.js +++ b/lib/pkgcloud/azure-v2/compute/client/index.js @@ -5,14 +5,13 @@ * */ -var util = require('util'), - urlJoin = require('url-join'), - https = require('https'), - auth = require('../../../common/auth'), - azureApi = require('../../utils/azureApiV2.js'), - //xml2JSON = require('../../utils/xml2json.js').xml2JSON, - azure = require('../../client'), - _ = require('lodash'); +var util = require('util'); +var urlJoin = require('url-join'); +var https = require('https'); +var auth = require('../../../common/auth'); +var constants = require('../../constants'); +var azure = require('../../client'); +var _ = require('lodash'); var Client = exports.Client = function (options) { azure.Client.call(this, options); @@ -26,7 +25,7 @@ var Client = exports.Client = function (options) { _.extend(this, require('./images')); _.extend(this, require('./servers')); - this.serversUrl = options.serversUrl || azureApi.MANAGEMENT_ENDPOINT; + this.serversUrl = options.serversUrl || constants.MANAGEMENT_ENDPOINT; this.subscriptionId = this.config.subscriptionId; this.azureKeys = { diff --git a/lib/pkgcloud/azure-v2/compute/client/servers.js b/lib/pkgcloud/azure-v2/compute/client/servers.js index 77d140f8e..26451b4f7 100644 --- a/lib/pkgcloud/azure-v2/compute/client/servers.js +++ b/lib/pkgcloud/azure-v2/compute/client/servers.js @@ -4,17 +4,11 @@ * (C) Microsoft Open Technologies, Inc. * */ -var base = require('../../../core/compute'); -var pkgcloud = require('../../../../../lib/pkgcloud'); -var errs = require('errs'); -var compute = require('../server'); - var async = require('async'); +var errs = require('errs'); var ComputeManagementClient = require('azure-arm-compute'); -var azureApi = require('../../utils/azureApi'); -var constants = require('../../utils/constants'); -var templates = require('../../templates'); +var constants = require('../../constants'); /** * Gets the current API version @@ -43,19 +37,19 @@ function getLimits(callback) { function getServers(callback) { var self = this; - async.waterfall([ - (next) => { - azureApi.login(self, next); - }, - (credentials, next) => { - var client = new ComputeManagementClient(credentials, self.config.subscriptionId); - client.virtualMachines.list(self.config.resourceGroup, (err, results) => { - return err - ? next(err) - : next(null, results.map(res => new self.models.Server(self, res))); - }); + self.login(err => { + + if (err) { + return callback(err); } - ], callback); + + var client = new ComputeManagementClient(credentials, self.config.subscriptionId); + client.virtualMachines.list(self.config.resourceGroup, (err, results) => { + return err + ? callback(err) + : callback(null, results.map(res => new self.models.Server(self, res))); + }); + }); }; /** @@ -67,22 +61,22 @@ function getServer(server, callback) { var self = this; var serverId = server instanceof self.models.Server ? server.name : server; - async.waterfall([ - (next) => { - azureApi.login(self, next); - }, - (credentials, next) => { - var client = new ComputeManagementClient(credentials, self.config.subscriptionId); - - // This will ensure returning of instances running status - var options = { expand: 'instanceView' }; - client.virtualMachines.get(self.config.resourceGroup, serverId, options, (err, result) => { - return err - ? next(err) - : next(null, new self.models.Server(self, result)); - }); + self.login(err => { + + if (err) { + return callback(err); } - ], callback); + + 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, (err, result) => { + return err + ? callback(err) + : callback(null, new self.models.Server(self, result)); + }); + }); }; /** @@ -92,9 +86,9 @@ function getServer(server, callback) { * properties of the options can be instances of Flavor * OR ids to those entities in Azure. * - * @param {Object} options - **Optional** options - * @param {String} options.name - **Optional** the name of server - * @param {Function} callback cb(err, server). + * @param {object} options - **Optional** options + * @param {string} options.name - **Optional** the name of server + * @param {function} callback cb(err, server). */ function createServer(options, callback) { var self = this; @@ -114,7 +108,7 @@ function createServer(options, callback) { } var templateName = 'compute' + (options.imageSourceUri ? '-from-image' : ''); - templates.deploy(self, templateName, options, (err, result) => { + self.deploy(templateName, options, (err, result) => { return err ? callback(err) : self.getServer(options.name, callback); @@ -130,19 +124,19 @@ function destroyServer(server, callback) { var self = this; var serverId = server && server.name || server; - async.waterfall([ - (next) => { - azureApi.login(self, next); - }, - (credentials, next) => { - var client = new ComputeManagementClient(credentials, self.config.subscriptionId); - client.virtualMachines.deleteMethod(self.config.resourceGroup, serverId, (err, result) => { - return err - ? next(err) - : next(null, serverId); - }); + self.login(err => { + + if (err) { + return callback(err); } - ]); + + var client = new ComputeManagementClient(credentials, self.config.subscriptionId); + client.virtualMachines.deleteMethod(self.config.resourceGroup, serverId, (err, result) => { + return err + ? callback(err) + : callback(null, serverId); + }); + }); }; // @@ -153,13 +147,13 @@ function destroyServer(server, callback) { // Destroy a server in Azure. // function stopServer(server, callback) { - var serverId = server instanceof base.Server ? server.id : server; + var serverId = server instanceof self.models.Server ? server.id : server; - azureApi.stopServer(this, serverId, function (err) { - return !err - ? callback(null, { ok: serverId }) - : callback(err); - }); + // azureApi.stopServer(this, serverId, function (err) { + // return !err + // ? callback(null, { ok: serverId }) + // : callback(err); + // }); }; // @@ -170,11 +164,11 @@ function stopServer(server, callback) { // Creates a Hosted Service in Azure. // function createHostedService(serviceName, callback) { - azureApi.createHostedService(this, serviceName, function (err, res) { - return !err - ? callback(null, res) - : callback(err); - }); + // azureApi.createHostedService(this, serviceName, function (err, res) { + // return !err + // ? callback(null, res) + // : callback(err); + // }); }; // @@ -185,13 +179,13 @@ function createHostedService(serviceName, callback) { // Reboots a server // function rebootServer(server, callback) { - var serverId = server instanceof base.Server ? server.id : server; + var serverId = server instanceof self.models.Server ? server.id : server; - azureApi.rebootServer(this, serverId, function (err) { - return !err - ? callback(null, { ok: serverId }) - : callback(err); - }); + // azureApi.rebootServer(this, serverId, function (err) { + // return !err + // ? callback(null, { ok: serverId }) + // : callback(err); + // }); }; // diff --git a/lib/pkgcloud/azure-v2/utils/constants.js b/lib/pkgcloud/azure-v2/constants.js similarity index 100% rename from lib/pkgcloud/azure-v2/utils/constants.js rename to lib/pkgcloud/azure-v2/constants.js diff --git a/lib/pkgcloud/azure-v2/storage/client/containers.js b/lib/pkgcloud/azure-v2/storage/client/containers.js index 5ff9238df..6c439e26a 100644 --- a/lib/pkgcloud/azure-v2/storage/client/containers.js +++ b/lib/pkgcloud/azure-v2/storage/client/containers.js @@ -7,35 +7,30 @@ var async = require('async'); var StorageManagementClient = require('azure-arm-storage'); +var azureStorage = require('azure-storage'); -var base = require('../../../core/storage'); -var pkgcloud = require('../../../../../lib/pkgcloud'); -var templates = require('../../templates'); -var azureApi = require('../../utils/azureApi'); -var constants = require('../../utils/constants'); - -var storage = pkgcloud.providers['azure-v2'].storage; +var constants = require('../../constants'); /** * list a collection of storage accounts under a resource group * @param {function} callback */ function getContainers(callback) { - var client = this; + var self = this; - async.waterfall([ - (next) => { - azureApi.login(client, next); - }, - (credentials, next) => { - var storageClient = new StorageManagementClient(credentials, client.config.subscriptionId); - storageClient.storageAccounts.listByResourceGroup(client.config.resourceGroup, (err, results) => { - return err - ? next(err) - : next(null, results.map(res => new (storage.Container)(client, res))); - }); + self.login(err => { + + if (err) { + return callback(err); } - ], callback); + + var storageClient = new StorageManagementClient(self.azure.credentials, self.config.subscriptionId); + storageClient.storageAccounts.listByResourceGroup(self.config.resourceGroup, (err, results) => { + return err + ? callback(err) + : callback(null, results.map(res => new self.models.Container(self, res))); + }); + }); }; /** @@ -44,22 +39,22 @@ function getContainers(callback) { * @param {function} callback - Continuation to respond to when complete. */ function getContainer(container, callback) { - var containerName = container instanceof storage.Container ? container.name : container; - var client = this; - - async.waterfall([ - (next) => { - azureApi.login(client, next); - }, - (credentials, next) => { - var storageClient = new StorageManagementClient(credentials, client.config.subscriptionId); - storageClient.storageAccounts.getProperties(client.config.resourceGroup, containerName, (err, result) => { - return err - ? next(err) - : next(null, new (storage.Container)(client, result)); - }); + var self = this; + var containerName = container instanceof self.models.Container ? container.name : container; + + self.login(err => { + + if (err) { + return callback(err); } - ], callback); + + var storageClient = new StorageManagementClient(self.azure.credentials, self.config.subscriptionId); + storageClient.storageAccounts.getProperties(self.config.resourceGroup, containerName, (err, result) => { + return err + ? callback(err) + : callback(null, new self.models.Container(self, result)); + }); + }); }; /** @@ -77,10 +72,10 @@ function getContainer(container, callback) { */ function createContainer(options, callback) { var self = this; - var containerName = options instanceof base.Container ? options.name : options; + var containerName = options instanceof self.models.Container ? options.name : options; var parameters = typeof options == 'string' ? { name: options } : options; - templates.deploy(self, 'storage', options, (err, result) => { + self.deploy('storage', parameters, (err, result) => { return err ? callback(err) : self.getContainer(containerName, callback); @@ -99,62 +94,84 @@ function createContainer(options, callback) { * of the same name during this cleanup period, your call returns an error immediately. */ function destroyContainer(container, callback) { - var containerName = container instanceof base.Container ? container.name : container; - var client = this; - - async.waterfall([ - (next) => { - azureApi.login(client, next); - }, - (credentials, next) => { - var storageClient = new StorageManagementClient(credentials, client.config.subscriptionId); - storageClient.storageAccounts.deleteMethod(client.config.resourceGroup, containerName, next); + var self = this; + var containerName = container instanceof self.models.Container ? container.name : container; + + self.login(err => { + + if (err) { + return callback(err); } - ], callback); + + var storageClient = new StorageManagementClient(self.azure.credentials, self.config.subscriptionId); + storageClient.storageAccounts.deleteMethod(self.config.resourceGroup, containerName, next); + }); }; function listContainerKeys(container, callback) { - var containerName = container instanceof storage.Container ? container.name : container; - var client = this; - - async.waterfall([ - (next) => { - azureApi.login(client, next); - }, - (credentials, next) => { - var storageClient = new StorageManagementClient(credentials, client.config.subscriptionId); - storageClient.storageAccounts.listKeys(client.config.resourceGroup, containerName, (err, result) => { - return err - ? next(err) - : next(null, result); - }); + var self = this; + var containerName = container instanceof self.models.Container ? container.name : container; + + self.login(err => { + + if (err) { + return callback(err); } - ], callback); + + var storageClient = new StorageManagementClient(self.azure.credentials, self.config.subscriptionId); + storageClient.storageAccounts.listKeys(self.config.resourceGroup, containerName, (err, result) => { + return err + ? callback(err) + : callback(null, result); + }); + }); } function getContainerKey (container, callback) { - var containerName = container instanceof this.models.Container ? container.name : container; - var client = this; + var self = this; + var containerName = container instanceof self.models.Container ? container.name : container; - client.azure = client.azure || {}; - client.azure.storageKeys = client.azure.storageKeys || {}; + self.azure = self.azure || {}; + self.azure.storageKeys = self.azure.storageKeys || {}; - if (client.azure.storageKeys[containerName]) { - return callback(null, client.azure.storageKeys[containerName]); + if (self.azure.storageKeys[containerName]) { + return callback(null, self.azure.storageKeys[containerName]); } - client.listContainerKeys(container, (err, result) => { + self.listContainerKeys(container, (err, result) => { if (err) { return callback(err); } var key = result.keys[0].value; - client.azure.storageKeys[containerName] = key; + self.azure.storageKeys[containerName] = key; return callback(null, key); }); } +function getBlobService(options, storageAccountName, callback) { + var self = this; + options = options || {}; + var azureContainer = typeof options == 'string' ? options : (options.container || constants.DEFAULT_STORAGE_CONTAINER); + + self.getContainerKey(storageAccountName, (err, containerKey) => { + if (err) { + return callback(err); + } + + var retryOperations = new azureStorage.ExponentialRetryPolicyFilter(); + var blobService = azureStorage.createBlobService(storageAccountName, containerKey).withFilter(retryOperations); + blobService.createContainerIfNotExists(azureContainer, null, function(error) { + return error ? + callback(error) : + callback(null, blobService); + }); + }); +}; + + module.exports = { + getBlobService, getContainers, getContainer, createContainer, diff --git a/lib/pkgcloud/azure-v2/storage/client/files.js b/lib/pkgcloud/azure-v2/storage/client/files.js index 66e3083b0..8d6293324 100644 --- a/lib/pkgcloud/azure-v2/storage/client/files.js +++ b/lib/pkgcloud/azure-v2/storage/client/files.js @@ -15,10 +15,7 @@ var urlJoin = require('url-join'); var StorageManagementClient = require('azure-arm-storage'); -var azureApi = require('../../utils/azureApi'); -var storageApi = require('./storage-api'); -var constants = require('../../utils/constants'); -var base = require('../../../core/storage'); +var constants = require('../../constants'); /** * client.removeFile @@ -179,41 +176,40 @@ exports.TODO_upload = function (options) { */ exports.getFiles = function (container, options, callback) { - var client = this; + var self = this; options = options || {}; var containerName = container instanceof this.models.Container ? container.name : container; var azureContainer = options.container || constants.DEFAULT_STORAGE_CONTAINER; var blobs = []; - async.waterfall([ - (next) => { - storageApi.getBlobService(client, azureContainer, containerName, next); - }, - (blobService, next) => { + self.getBlobService(azureContainer, containerName, (err, blobService) => { + + if (err) { + return callback(err); + } - var aggregateBlobs = (err, result, cb) => { - if (err) { - cb(err); + var aggregateBlobs = (err, result, cb) => { + if (err) { + cb(err); + } else { + blobs = blobs.concat(result.entries); + if (result.continuationToken !== null) { + blobService.listBlobsSegmented(azureContainer, result.continuationToken, aggregateBlobs); } else { - blobs = blobs.concat(result.entries); - if (result.continuationToken !== null) { - blobService.listBlobsSegmented(azureContainer, result.continuationToken, aggregateBlobs); - } else { - cb(null, blobs); - } + cb(null, blobs); } } + } - blobService.listBlobsSegmented(azureContainer, null, function(err, result) { - aggregateBlobs(err, result, (err, blobs) => { - return err ? - next(err) : - next(null, blobs.map(blob => new client.models.File(client, blob))); - }); + blobService.listBlobsSegmented(azureContainer, null, function(err, result) { + aggregateBlobs(err, result, (err, blobs) => { + return err ? + callback(err) : + callback(null, blobs.map(blob => new self.models.File(self, blob))); }); + }); - } - ], callback); + }); }; /** @@ -227,26 +223,25 @@ exports.getFiles = function (container, options, callback) { */ exports.getFile = function (container, file, options, callback) { + var self = this; options = options || {}; var containerName = container instanceof this.models.Container ? container.name : container; var azureContainerName = options.storage && options.storage.container || constants.DEFAULT_STORAGE_CONTAINER; var fileName = file instanceof this.models.File ? file.name : file; - var client = this; - async.waterfall([ - (next) => { - storageApi.getBlobService(client, azureContainerName, containerName, next); - }, - (blobService, next) => { + self.getBlobService(azureContainer, containerName, (err, blobService) => { - blobService.getBlobProperties(azureContainerName, fileName, (err, properties, status) => { - return err ? - next(err) : (!status || !status.isSuccessful) ? - next(new Error(`status is not successfull: ${JSON.stringify(status || null)}`)) : - next(null, new client.models.File(client, properties)); - }); + if (err) { + return callback(err); } - ], callback); + + blobService.getBlobProperties(azureContainerName, fileName, (err, properties, status) => { + return err ? + next(err) : (!status || !status.isSuccessful) ? + next(new Error(`status is not successfull: ${JSON.stringify(status || null)}`)) : + next(null, new self.models.File(self, properties)); + }); + }); }; /** @@ -266,7 +261,7 @@ exports.getFile = function (container, file, options, callback) { * @returns {request|*} */ exports.download = function (options, callback) { - var client = this; + var self = this; var container = options.container; var containerName = container instanceof this.models.Container ? container.name : container; var azureContainerName = options.storage && options.storage.container || constants.DEFAULT_STORAGE_CONTAINER; @@ -284,7 +279,7 @@ exports.download = function (options, callback) { var blobService; async.waterfall([ (next) => { - storageApi.getBlobService(client, azureContainerName, containerName, next); + self.getBlobService(azureContainerName, containerName, next); }, (_blobService, next) => { diff --git a/lib/pkgcloud/azure-v2/storage/client/index.js b/lib/pkgcloud/azure-v2/storage/client/index.js index ec192daaa..5b66a7137 100644 --- a/lib/pkgcloud/azure-v2/storage/client/index.js +++ b/lib/pkgcloud/azure-v2/storage/client/index.js @@ -5,10 +5,9 @@ * */ -var util = require('util'), - azureApi = require('../../utils/azureApi.js'), - azure = require('../../client'), - _ = require('lodash'); +var util = require('util'); +var azure = require('../../client'); +var _ = require('lodash'); var Client = exports.Client = function (options) { azure.Client.call(this, options); diff --git a/lib/pkgcloud/azure-v2/storage/client/storage-api.js b/lib/pkgcloud/azure-v2/storage/client/storage-api.js deleted file mode 100644 index 5880c82ea..000000000 --- a/lib/pkgcloud/azure-v2/storage/client/storage-api.js +++ /dev/null @@ -1,36 +0,0 @@ - -var async = require('async'); -var azureStorage = require('azure-storage'); - -var azureApi = require('../../utils/azureApi'); -var constants = require('../../utils/constants'); - -function getBlobService(client, options, storageAccountName, callback) { - - options = options || {}; - var azureContainer = typeof options == 'string' ? options : (options.container || constants.DEFAULT_STORAGE_CONTAINER); - - client.getContainerKey(storageAccountName, (err, containerKey) => { - if (err) { - return callback(err); - } - - var retryOperations = new azureStorage.ExponentialRetryPolicyFilter(); - var blobService = azureStorage.createBlobService(storageAccountName, containerKey).withFilter(retryOperations); - blobService.createContainerIfNotExists(azureContainer, null, function(error) { - return error ? - callback(error) : - callback(null, blobService); - }); - }); -}; - - -function listStorageAccountFiles(client, storageAccountName, options, callback) { - -} - -module.exports = { - getBlobService, - listStorageAccountFiles -} \ No newline at end of file diff --git a/lib/pkgcloud/azure-v2/templates/index.js b/lib/pkgcloud/azure-v2/templates/index.js index bba8cc460..ba5af0238 100644 --- a/lib/pkgcloud/azure-v2/templates/index.js +++ b/lib/pkgcloud/azure-v2/templates/index.js @@ -4,7 +4,6 @@ var async = require('async'); var resourceManagement = require('azure-arm-resource'); -var azureApi = require('../utils/azureApi'); function resolve(templateId) { var templatePath = path.join(__dirname, '..', 'templates', 'arm-' + templateId + '.json'); @@ -12,35 +11,35 @@ function resolve(templateId) { return JSON.parse(contents); } -function deploy(client, templateName, options, callback) { - - async.waterfall([ - (next) => { - azureApi.login(client, next); - }, - (credentials, next) => { - var template = resolve(templateName); - var parameters = { - properties: { - template: template, - parameters: {}, - mode: 'Incremental' - } - }; - Object.keys(options).forEach(key => parameters.properties.parameters[key] = { value: options[key] }); - - var deploymentName = 'pkgc-' + (new Date()).toISOString().replace(/\:|Z|\.|\-/g, '').replace(/T/g, '-'); - var resourceClient = new resourceManagement.ResourceManagementClient(credentials, client.config.subscriptionId); - resourceClient.deployments.createOrUpdate(client.config.resourceGroup, deploymentName, parameters, (err, result) => { - return err - ? next(err) - : next(null, result); - }); +function deploy(templateName, options, callback) { + var self = this; + + self.login(err => { + + if (err) { + return callback(err); } - ], callback); + + var template = resolve(templateName); + var parameters = { + properties: { + template: template, + parameters: {}, + mode: 'Incremental' + } + }; + Object.keys(options).forEach(key => parameters.properties.parameters[key] = { value: options[key] }); + + var deploymentName = 'pkgc-' + (new Date()).toISOString().replace(/\:|Z|\.|\-/g, '').replace(/T/g, '-'); + var resourceClient = new resourceManagement.ResourceManagementClient(self.azure.credentials, self.config.subscriptionId); + resourceClient.deployments.createOrUpdate(self.config.resourceGroup, deploymentName, parameters, (err, result) => { + return err + ? callback(err) + : callback(null, result); + }); + }); } module.exports = { - resolve, deploy } \ No newline at end of file diff --git a/lib/pkgcloud/azure-v2/utils/_azureApi.js b/lib/pkgcloud/azure-v2/utils/_azureApi.js deleted file mode 100644 index 8e3eb1adf..000000000 --- a/lib/pkgcloud/azure-v2/utils/_azureApi.js +++ /dev/null @@ -1,709 +0,0 @@ -/** - * (C) Microsoft Open Technologies, Inc. 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. - */ - -var HeaderConstants = require('./constants').HeaderConstants; -var async = require('async'); -var templates = require('../compute/templates/templates'); -var _ = require('lodash'); -var errs = require('errs'); -var URL = require('url'); -var cert = require('../utils/cert'); -var pkgcloud = require('../../../../../pkgcloud'); - -exports.MANAGEMENT_API_VERSION = '2012-03-01'; -exports.MANAGEMENT_ENDPOINT = 'management.core.windows.net'; -var STORAGE_ENDPOINT = exports.STORAGE_ENDPOINT = 'blob.core.windows.net'; -exports.STORAGE_API_VERSION = HeaderConstants.TARGET_STORAGE_VERSION; -exports.TABLES_ENDPOINT = 'table.core.windows.net'; -exports.TABLES_API_VERSION = '2012-02-12'; -var MINIMUM_POLL_INTERVAL = exports.MINIMUM_POLL_INTERVAL = 3000; - -// Declaring variables for helper functions defined later -var createVM, createLinuxVM, createWindowsVM, validateCreateOptions, getServer, - getServers, rebootServer, stopServer, deleteHostedService, destroyServer, createImage, - captureServer, deleteImage, destroyImage, getMediaLinkUrl, createEndpoints, - makeTemplateRequest, createHostedService, addCertificate, getHostedServices, - deleteServer, getOSImage, deleteOSDisk, deleteOSBlob, getServersFromServices, - getServersFromService, isVM, getHostedServiceProperties, pollRequestStatus, - getStorageInfoFromUri; - -/** - * createServer() - * - * In order to deploy a vm, Azure requires us to do the following - * before we can actually try to create the vm. - * 1. get or create a Hosted Service (we use the same name as the vm) - * 2. resolve the OSImage url to a container on the user's account - * 3. upload SSH certificate (if necessary) - * 4. create the VM - * - * Note: creating a VM on Azure will fail if one of the following is true - * 1. The VM (with the same name) already exists - * 2. The blob storage (with the same name) for the OSImage already exists - * 3. The VM disk (with the same name) for the OSImage already exists - * 4. The storage account is in a different azure location than the vm - * (East US, West US...) - * - * Note: createServer() must wait for Azure to respond if the createDeployment (vm) - * request succeeded. createServer() asynchronously polls Azure to get - * the result. Once the result is received, the callback function will be called - * with the server information or error. The state of returned server will most likely - * be PROVISIONING or STOPPED. Use server.setWait() to continue polling the server until - * its status is RUNNING. This entire process may take several minutes. - */ - -exports.createServer = function (client, options, callback) { - var vmOptions = {}, - ssh; - - // async execute the following tasks one by one and bail if there is an error - async.waterfall([ - function (next) { - // validate createServer options - validateCreateOptions(options, client.config, next); - }, - function (next) { - getHostedServiceProperties(client, options.name, next); - }, - function (service, next) { - // if the HostedService does not exist, create it - vmOptions.hostedService = service; - if (vmOptions.hostedService === null) { - createHostedService(client, options, function (err, service) { - if (err) { - next(err); - } else { - vmOptions.hostedService = service; - next(null); - } - }); - } else { - next(null); - } - }, - function (next) { - // get the server's OSImage info - getOSImage(client, options.image, function (err, res) { - if (err) { - next(err); - } else { - vmOptions.image = res; - next(null); - } - }); - }, - function (next) { - ssh = options.ssh; - if (ssh) { - vmOptions.sshCertInfo = cert.getAzureCertInfo(ssh.cert); - } - next(); - }, - function (next) { - // add the ssh certificate to the service - if (vmOptions.sshCertInfo) { - addCertificate(client, options.name, vmOptions.sshCertInfo.cert, ssh.pemPassword, function (err) { - next(err); - }); - } else { - next(null); - } - }, - function (next) { - // create the VM and wait for response - createVM(client, options, vmOptions, next); - }, - function (next) { - // now get the actual server info - getServer(client, options.name, next); - }], - function (err, result) { - if (err) { - callback(err); - } else { - // return the server info - callback(null, result); - } - } - ); -}; - -/** - * getServer - */ -getServer = exports.getServer = function (client, serverName, callback) { - getServersFromService(client, serverName, function (err, servers) { - return !err - ? callback(err, servers[0] ? servers[0] : null) - : callback(err); - }); -}; - -getServers = exports.getServers = function (client, callback) { - // async execute the following tasks one by one and bail if there is an error - async.waterfall([ - function (next) { - // get the list of Hosted Services - getHostedServices(client, next); - }, - function (hostedServices, next) { - // get the list of Servers from the Hosted Services - getServersFromServices(client, hostedServices, next); - }], - function (err, servers) { - callback(err, servers); - } - ); -}; - -makeTemplateRequest = function (client, path, templateName, params, callback) { - var headers = {}, - body; - - // async execute the following tasks one by one and bail if there is an error - async.waterfall([ - function (next) { - templates.load(templateName, next); - }, - function (template, next) { - // compile template with params - var compiled = _.template(template); - body = compiled(params); - headers['content-length'] = body.length; - headers['content-type'] = 'application/xml'; - headers['accept'] = 'application/xml'; - client._request({ - method: 'POST', - path: path, - body: body, - headers: headers - }, function (err, body, res) { - if (err) { - return next(err); - } - // poll azure for result of request - pollRequestStatus(client, res.headers['x-ms-request-id'], MINIMUM_POLL_INTERVAL, next); - }); - }], - function (err) { - callback(err); - } - ); -}; - -createHostedService = exports.createHostedService = function (client, options, callback) { - var path = client.subscriptionId + '/services/hostedservices'; - var params = { - NAME: options.name, - LABEL_BASE64: new Buffer(options.name).toString('base64'), - LOCATION: options.location - }; - - makeTemplateRequest(client, path, 'createHostedService.xml', params, callback); -}; - -/** - * rebootServer - * uses Restart Role - * POST https://management.core.windows.net//services/hostedservices//deployments//roleinstances//operations - * A successful operation returns status code 201 (Created). Need to poll for success? - */ -rebootServer = exports.rebootServer = function (client, serviceName, callback) { - var path = client.subscriptionId + '/services/hostedservices/' + - serviceName + '/deployments/' + - serviceName + '/roleInstances/' + - serviceName + '/Operations'; - - makeTemplateRequest(client, path, 'restartRole.xml', {}, callback); -}; - -/** - * stopServer - * uses Shutdown Role - * POST https://management.core.windows.net//services/hostedservices//deployments//roleinstances//operations - * A successful operation returns status code 201 (Created). Need to poll for success? - */ -stopServer = exports.stopServer = function (client, serviceName, callback) { - var path = client.subscriptionId + '/services/hostedservices/' + - serviceName + '/deployments/' + - serviceName + '/roleInstances/' + - serviceName + '/Operations'; - - makeTemplateRequest(client, path, 'shutdownRole.xml', {}, callback); -}; - -addCertificate = function (client, serviceName, cert, password, callback) { - var path = client.subscriptionId + '/services/hostedservices/' + - serviceName + '/certificates'; - - var params = { - CERT_BASE64: new Buffer(cert, 'utf8').toString('base64'), - PASSWORD: password - }; - - makeTemplateRequest(client, path, 'addCertificate.xml', params, callback); -}; - -deleteHostedService = exports.deleteHostedService = function (client, serviceName, callback) { - // DELETE https://management.core.windows.net//services/hostedservices/ - var path = client.subscriptionId + '/services/hostedservices/' + serviceName; - - client._request({ - method: 'DELETE', - path: path - }, function (err, body, res) { - if (err) { - return callback(err); - } - // poll azure for result of request - pollRequestStatus(client, res.headers['x-ms-request-id'], MINIMUM_POLL_INTERVAL, callback); - }); -}; - -getHostedServices = exports.getHostedServices = function (client, callback) { - var path = client.subscriptionId + '/services/hostedservices', - services = []; - - client.get(path, function (err, body) { - if (err) { - return callback(err); - } - if (body.HostedService) { - // need to check if azure returned an array or single object - if (Array.isArray(body.HostedService)) { - body.HostedService.forEach(function (service) { - services.push(service); - }); - } else { - services.push(body.HostedService); - } - } - - callback(null, services); - }); -}; - -/** - * destroyServer - * uses Delete Deployment - * DELETE https://management.core.windows.net//services/hostedservices//deployments/ - * Because Delete Deployment is an asynchronous operation, it always returns status code 202 (Accept). - * To determine the status code for the operation once it is complete, call Get Operation Status. - * Because Delete Deployment is an asynchronous operation, it always returns status code 202 (Accept). - */ -destroyServer = exports.destroyServer = function (client, serverName, callback) { - var server = null; - - // async execute the following tasks one by one and bail if there is an error - async.waterfall([ - function (next) { - // get the list of Hosted Services - getServer(client, serverName, next); - }, - function (result, next) { - server = result; - // get the list of Hosted Services - stopServer(client, serverName, next); - }, - function (next) { - deleteServer(client, serverName, next); - }, - function (next) { - deleteOSDisk(client, server, next); - }, - function (next) { - deleteOSBlob(client, server, next); - }, - function (next) { - deleteHostedService(client, serverName, next); - }], - function (err) { - callback(err, true); - } - ); -}; - -deleteServer = function (client, serverName, callback) { - var path = client.subscriptionId + '/services/hostedservices/' + serverName; - path += '/deployments/' + serverName; - - client._request({ - method: 'DELETE', - path: path - }, function (err, body, res) { - if (err) { - return callback(err); - } - // poll azure for result of request - pollRequestStatus(client, res.headers['x-ms-request-id'], MINIMUM_POLL_INTERVAL, callback); - }); -}; - -getOSImage = exports.getOSImage = function (client, imageName, callback) { - var path = '/' + client.subscriptionId + '/services/images/' + imageName; - - var onError = function (err) { - if (err.failCode === 'Item not found') { - callback(null, null); - - } else { - callback(err); - } - }; - - client.get(path, function (err, body) { - return err - ? onError(err) - : callback(null, body); - }); -}; - -deleteOSDisk = function (client, server, callback) { - var diskName = null, - path; - - if (server && server.RoleList && server.RoleList.Role) { - if (server.RoleList.Role.OSVirtualHardDisk) { - diskName = server.RoleList.Role.OSVirtualHardDisk.DiskName; - } - } - - if (diskName === null) { - callback(null); - return; - } - - // https://management.core.windows.net//services/disks/ - path = client.subscriptionId + '/services/disks/' + diskName; - - client._request({ - method: 'DELETE', - path: path - }, function (err, body, res) { - if (err) { - return callback(err); - } - // poll azure for result of request - pollRequestStatus(client, res.headers['x-ms-request-id'], MINIMUM_POLL_INTERVAL, callback); - }); -}; - -deleteOSBlob = function (client, server, callback) { - var blob = null; - - if (server && server.RoleList && server.RoleList.Role) { - if (server.RoleList.Role.OSVirtualHardDisk) { - blob = server.RoleList.Role.OSVirtualHardDisk.MediaLink; - } - } - - if (blob === null) { - callback(null); - return; - } - - getStorageInfoFromUri(blob, function (err, info) { - if (err) { - callback(err); - } else { - var storage = pkgcloud.storage.createClient(client.config); - storage.removeFile(info.container, info.file, function (err) { - callback(err); - }); - } - }); -}; - -/** - * getServersFromServices - * Retrieves all servers (VMs) from the list of services - */ -getServersFromServices = function (client, services, callback) { - var task = function (service, next) { - getServersFromService(client, service.ServiceName, function (err, servers) { - next(err, servers); - }); - }; - - // Check each service for deployed VMs. - async.concat(services, task, function (err, servers) { - callback(err, servers); - }); -}; - -/** - * getServersFromServices - * Retrieves all servers (VMs) from a Hosted Service - */ -getServersFromService = function (client, serviceName, callback) { - var servers = []; - getHostedServiceProperties(client, serviceName, function (err, result) { - if (err) { - return callback(err); - } - - if (result && result.Deployments && result.Deployments.Deployment) { - if (isVM(result.Deployments.Deployment)) { - servers.push(result.Deployments.Deployment); - } - } - - callback(null, servers); - }); -}; - -isVM = function (deployment) { - if (deployment.RoleList && deployment.RoleList.Role) { - if (deployment.RoleList.Role.RoleType === 'PersistentVMRole') { - return true; - } - } - - return false; -}; - -/** - Get Hosted Service Properties - GET https://management.core.windows.net//services/hostedservices/?embed-detail=true - A successful operation returns status code 200 (OK). - */ -getHostedServiceProperties = function (client, serviceName, callback) { - var path = client.subscriptionId + '/services/hostedservices/' + serviceName + '?embed-detail=true'; - - var onError = function (err) { - return err.failCode === 'Item not found' - ? callback(null, null) - : callback(err); - }; - - client.get(path, function (err, body) { - return err - ? onError(err) - : callback(null, body); - }); -}; - -/** - * pollRequestStatus - * uses Get Operation Status - * GET https://management.core.windows.net//operations/ - */ - -pollRequestStatus = function (client, requestId, interval, callback) { - var checkStatus = function () { - var path = client.subscriptionId + '/operations/' + requestId; - client.get(path, function (err, body) { - if (err) { - return callback(err); - } - switch (body.Status) { - case 'InProgress': - setTimeout(checkStatus, interval); - break; - case 'Failed': - callback(body.Error); - break; - case 'Succeeded': - callback(null); - break; - } - }); - }; - - checkStatus(); -}; - -getStorageInfoFromUri = exports.getStorageInfoFromUri = function (uri, callback) { - var u, tokens, path, - info = {}; - - u = URL.parse(uri); - if (!u.host || !u.path) { - return callback(errs.create({message: 'invalid Azure container or blob uri'})); - } - - tokens = u.host.split('.'); - info.storage = tokens[0]; - - path = u.path; - // if necessary, remove leading '/' from path - if (path.charAt(0) === '/') { - path = path.substr(1); - } - tokens = path.split('/'); - info.container = tokens.shift(); - info.file = tokens.join('/'); - - callback(null, info); -}; - -/** - * createImage() - * 1. Check if the server exists - * 2. stop server if it is running - * 3. capture server image - */ -createImage = exports.createImage = function (client, serverName, targetImageName, callback) { - async.waterfall([ - function (next) { - // stop the server - stopServer(client, serverName, next); - }, - function (next) { - // capture the server image - captureServer(client, serverName, targetImageName, next); - }], - function (err) { - callback(err, targetImageName); - } - ); -}; - -deleteImage = function (client, image, callback) { - // https://management.core.windows.net//services/images/ - var path = client.subscriptionId + '/services/images/' + image.Name; - - var configParams = { - LABEL: image.LABEL - }; - - makeTemplateRequest(client, path, 'deleteImage.xml', configParams, callback); -}; - -/** - * destroyImage() - * 1. get the requested image - * 2. delete the image using its label - */ -destroyImage = exports.destroyImage = function (client, imageName, callback) { - async.waterfall([ - function (next) { - // stop the server - client.getImage(client, imageName, next); - }, - function (image, next) { - deleteImage(client, image, next); - }], - function (err) { - callback(err, imageName); - } - ); -}; - -createVM = function (client, options, vmOptions, callback) { - // check OS type of image to determine if we are creating a linux or windows VM - switch (vmOptions.image.OS.toLowerCase()) { - case 'linux': - createLinuxVM(client, options, vmOptions, callback); - break; - case 'windows': - createWindowsVM(client, options, vmOptions, callback); - break; - default: - callback(errs.create({message: 'Unknown Image OS: ' + vmOptions.image.OS})); - break; - } -}; - -getMediaLinkUrl = function (storageAccount, fileName) { - return 'http://' + storageAccount + '.' + STORAGE_ENDPOINT + '/vhd/' + fileName; -}; - -createEndpoints = function (ports) { - var endPoints = '', - template = templates.loadSync('endpoint.xml'); - - (ports || []).forEach(function (port) { - endPoints += templates.compileSync(template, port); - }); - return endPoints; -}; - -createLinuxVM = function (client, options, vmOptions, callback) { - var path = client.subscriptionId + '/services/hostedservices/' + options.name + '/deployments'; - var mediaLink = getMediaLinkUrl(client.config.storageAccount, options.name + '.vhd'); - var label = new Buffer(options.name).toString('base64'); - - var configParams = { - NAME: options.name, - LABEL_BASE64: label, - USERNAME: options.username, - PASSWORD: options.password, - SSH_CERTIFICATE_FINGERPRINT: vmOptions.sshCertInfo.fingerprint, - PORT: options.ssh.port || '22', - LOCAL_PORT: options.ssh.localPort || '22', - ROLESIZE: options.flavor, - ENDPOINTS: createEndpoints(options.ports), - OS_SOURCE_IMAGE_NAME: vmOptions.image.Name, - OS_IMAGE_MEDIALINK: mediaLink - }; - - makeTemplateRequest(client, path, 'linuxDeployment.xml', configParams, callback); -}; - -createWindowsVM = function (client, options, vmOptions, callback) { - var path = client.subscriptionId + '/services/hostedservices/' + options.name + '/deployments'; - var mediaLink = getMediaLinkUrl(client.config.storageAccount, options.name + '.vhd'); - var label = new Buffer(options.name).toString('base64'); - - var configParams = { - NAME: options.name, - COMPUTER_NAME: options.computerName || options.name.slice(0, 15), - LABEL_BASE64: label, - PASSWORD: options.password, - ROLESIZE: options.flavor, - ENDPOINTS: createEndpoints(options.ports), - OS_SOURCE_IMAGE_NAME: vmOptions.image.Name, - OS_IMAGE_MEDIALINK: mediaLink - }; - - makeTemplateRequest(client, path, 'windowsDeployment.xml', configParams, callback); -}; - -captureServer = function (client, serverName, targetImageName, callback) { - // /services/hostedservices//deployments//roleinstances//operations - var path = client.subscriptionId + '/services/hostedservices/' + - serverName + '/deployments/' + - serverName + '/roleInstances/' + - serverName + '/Operations'; - - var configParams = { - NAME: targetImageName - }; - - makeTemplateRequest(client, path, 'captureRole.xml', configParams, callback); -}; - -validateCreateOptions = function (options, config, callback) { - if (typeof options === 'function') { - options = {}; - } - options = options || {}; // no args - - // check required options values - ['flavor', 'image', 'name', 'username', 'password', 'location'].forEach(function (member) { - if (!options[member]) { - errs.handle( - errs.create({ message: 'options.' + member + ' is a required argument.' }), - callback - ); - } - }); - callback(); -}; - -exports._updateMinimumPollInterval = function(interval) { - MINIMUM_POLL_INTERVAL = interval; -}; diff --git a/lib/pkgcloud/azure-v2/utils/azureApi.js b/lib/pkgcloud/azure-v2/utils/azureApi.js deleted file mode 100644 index e4e6d66e3..000000000 --- a/lib/pkgcloud/azure-v2/utils/azureApi.js +++ /dev/null @@ -1,214 +0,0 @@ -var async = require('async'); -var errs = require('errs'); -var _ = require('lodash'); - -var msRestAzure = require('ms-rest-azure'); -var resourceManagement = require("azure-arm-resource"); - -var constants = require('./constants'); -var templates = require('./templates'); - -/** - * This callback type is called `requestCallback` and is displayed as a global symbol. - * - * @callback requestCallback - * @param {object} error - * @param {object} result - */ - -/** - * Request and save credentials for accessing azure ARM resources. - * @param {object} client object containing configuration. - * @param {requestCallback} callback to respond to when complete. - */ -function login(client, callback) { - - // Make sure credentials are refreshed by intervals - if (client.azure && client.azure.credentials && client.azure.lastRefresh) { - var now = new Date(); - if (now - client.azure.lastRefresh < constants.CREDENTIALS_LIFESPAN) { - return callback(null, client.azure.credentials); - } - } - - var config = client.config; - var servicePrincipal = config.servicePrincipal; - msRestAzure.loginWithServicePrincipalSecret( - servicePrincipal.clientId, - servicePrincipal.secret, - servicePrincipal.domain, - (err, credentials) => { - - if (err) { - errs.handle( - errs.create({ - message: 'There was a problem connecting to azure: ' + err - }), - callback - ); - } - - client.azure = client.azure || {}; - client.azure.credentials = credentials; - client.azure.lastRefresh = new Date(); - - return callback(null, credentials); - }); -} - -/** - * Setting up an azure session including querying the default location from the resource group - * used by the current configuration - * @param {object} client object containing configuration. - * @param {requestCallback} callback to respond to when complete. - */ -function setup(client, callback) { - login(client, (err, credentials) => { - - if (err) { - return callback(err); - } - - client.azure = client.azure || {}; - client.azure.location = client.azure.location || client.config.location; - if (client.azure.location) { - return callback(); - } - - if (client.config.resourceGroup) { - var resourceClient = new resourceManagement.ResourceManagementClient(credentials, client.config.subscriptionId); - resourceClient.resourceGroups.get(client.config.resourceGroup, (err, result) => { - - if (err) { - return callback(err); - } - - client.azure.location = result.location; - return callback(); - }); - } - }); -} - -/** - * list all resources inside a resource group - * @param {object} client - * @param {object} provider - * @param {string} provider.namespace - * @param {string} provider.resourceType - * @param {requestCallback} callback - callback with results - */ -function list(client, provider, callback) { - var client = this; - var config = client.config; - - async.waterfall([ - function (next) { - azureApi.login(client, next); - }, - function (credentials, next) { - - var resourceClient = new resourceManagement.ResourceManagementClient(client.credentials, config.subscriptionId); - resourceClient.resourceGroups.listResources( - config.resourceGroup, - { - filter: `resourceType eq '${provider.namespace}/${provider.resourceType}'` - }, - function(err, result, request, response) { - if (err) { - return errs.handle(errs.create('A problem during resource creation: ' + err), callback); - } - - callback(null, result); - } - ); - } - ], callback); -} - -/** - * list all resources inside a resource group - * @param {object} client - * @param {object} provider - * @param {string} provider.namespace - * @param {string} provider.resourceType - * @param {string} id - * @param {requestCallback} callback - callback with results - */ -function getById(client, provider, id, callback) { - var config = client.config; - - login(client, (err, credentials) => { - - if (err) { - return errs.handle(errs.create('A problem gettinh resource: ' + err), callback); - } - - var resourceClient = new resourceManagement.ResourceManagementClient(credentials, config.subscriptionId); - resourceClient.resources.get( - config.resourceGroup, - provider.namespace, provider.resourceType, - id, '', - constants.DEFAULT_API_VERSION, - (err, result, request, response) => { - if (err) { - return errs.handle(errs.create('A problem during resource creation: ' + err), callback); - } - - callback(null, result); - } - ); - }); - -} - -/** - * Create a resource inside a resource group - * @param {object} client - * @param {object} provider - * @param {string} provider.namespace - * @param {string} provider.resourceType - * @param {string} id - * @param {object} parameters - parameters to be used during deployment - * @param {requestCallback} callback - callback with results - */ -function create(client, provider, id, parameters, callback) { - var config = client.config; - - var _params = null; - async.waterfall([ - (next) => { - templates.resolve('storage', {}, next); - }, - (params, next) => { - _params = params; - login(client, next); - }, - (credentials, next) => { - - var resourceClient = new resourceManagement.ResourceManagementClient(credentials, config.subscriptionId); - resourceClient.resources.createOrUpdate( - config.resourceGroup, - provider.namespace, provider.resourceType, - id, '', - constants.DEFAULT_API_VERSION, - _params, - (err, result, request, response) => { - if (err) { - return errs.handle(errs.create('A problem during resource creation: ' + err), callback); - } - - callback(null, result); - } - ); - } - ], callback); -} - -module.exports = { - login, - setup, - list, - getById, - create -} \ No newline at end of file diff --git a/lib/pkgcloud/azure-v2/utils/azureApiV2.js b/lib/pkgcloud/azure-v2/utils/azureApiV2.js deleted file mode 100644 index 368973293..000000000 --- a/lib/pkgcloud/azure-v2/utils/azureApiV2.js +++ /dev/null @@ -1,337 +0,0 @@ -var async = require('async'); -var _ = require('lodash'); -var errs = require('errs'); - -var msRestAzure = require('ms-rest-azure'); -var resourceManagement = require("azure-arm-resource"); - -var template = require('./templates'); - -/** - * createServer() - * - * In order to deploy a vm, Azure requires us to do the following - * before we can actually try to create the vm. - * 1. Login - * 2. resolve the OSImage url to a container on the user's account - * 3. upload SSH certificate (if necessary) - * 4. create the VM - * - * Note: creating a VM on Azure will fail if one of the following is true - * 1. The VM (with the same name) already exists - * 2. The blob storage (with the same name) for the OSImage already exists - * 3. The VM disk (with the same name) for the OSImage already exists - * 4. The storage account is in a different azure location than the vm - * (East US, West US...) - * - * Note: createServer() must wait for Azure to respond if the createDeployment (vm) - * request succeeded. createServer() asynchronously polls Azure to get - * the result. Once the result is received, the callback function will be called - * with the server information or error. The state of returned server will most likely - * be PROVISIONING or STOPPED. Use server.setWait() to continue polling the server until - * its status is RUNNING. This entire process may take several minutes. - */ -exports.createServer = function(client, options, callback) { - - var vmOptions = {}; - - // async execute the following tasks one by one and bail if there is an error - async.waterfall([ - function(next) { - validateCreateOptions(options, client.config, next); - }, - function(next) { - loginToAzure(client, next); - }, - function(next) { - createVM(client, options, vmOptions, next); - }, - function(server, next) { - // now get the actual server info - getServer(client, options, server, next); - } - ], - function(err, result) { - if (err) { - callback(err); - } else { - // return the server info - callback(null, result); - } - } - ); -} - -/** - * loginToAzure - * In order to perform any ARM action on azure, we first need to authenticate using user name and password. - * https://www.npmjs.com/package/ms-rest-azure - */ -var loginToAzure = function(client, callback) { - var config = client.config; - msRestAzure.loginWithServicePrincipalSecret(config.spClientId, config.spSecret, config.spDomain, function(err, credentials) { - - if (err) { - errs.handle( - errs.create({ - message: 'There was a problem connecting to azure: ' + err - }), - callback - ); - } - - client.credentials = credentials; - - return callback(); - }); -} - -var validateCreateOptions = function(options, config, callback) { - if (typeof options === 'function') { - options = {}; - } - options = options || {}; // no args - - // check required options values - ['flavor', 'image', 'name', 'username', 'password', 'location'].forEach(function(member) { - if (!options[member]) { - errs.handle( - errs.create({ - message: 'options.' + member + ' is a required argument.' - }), - callback - ); - } - }); - callback(); -}; - -var createVM = function(client, options, vmOptions, callback) { - // check OS type of image to determine if we are creating a linux or windows VM - switch (options.image.OS.toLowerCase()) { - case 'linux': - createLinuxVM(client, options, vmOptions, callback); - break; - case 'windows': - createWindowsVM(client, options, vmOptions, callback); - break; - default: - callback(errs.create({ - message: 'Unknown Image OS: ' + options.image.OS - })); - break; - } -}; - -var createLinuxVM = function(client, options, vmOptions, callback) { - - var configParams = { - API_VERSION: '2016-03-30', - NAME: options.name, - USERNAME: options.username, - PASSWORD: options.password, - VM_SIZE: options.flavor, - LOCATION: options.location, - VM_NAME: options.name, - OS_DISK_URI: options.image.uri, - NICK_ID: options.nic - }; - - makeTemplateRequest(client, 'vm', configParams, callback); -}; - -var createWindowsVM = function(client, options, vmOptions, callback) { - var path = client.subscriptionId + '/services/hostedservices/' + options.name + '/deployments'; - var mediaLink = getMediaLinkUrl(client.config.storageAccount, options.name + '.vhd'); - var label = new Buffer(options.name).toString('base64'); - - var configParams = { - NAME: options.name, - COMPUTER_NAME: options.computerName || options.name.slice(0, 15), - LABEL_BASE64: label, - PASSWORD: options.password, - ROLESIZE: options.flavor, - ENDPOINTS: createEndpoints(options.ports), - OS_SOURCE_IMAGE_NAME: vmOptions.image.Name, - OS_IMAGE_MEDIALINK: mediaLink - }; - - makeTemplateRequest(client, path, 'windowsDeployment.xml', configParams, callback); -}; - -exports.getServer = function(client, name, callback) { - - var configParams = { - API_VERSION: '2016-03-30', - NAME: name, - USERNAME: '', - PASSWORD: '', - VM_SIZE: '', - LOCATION: '', - VM_NAME: '', - OS_DISK_URI: '', - NICK_ID: '' - }; - - async.waterfall([ - function(next) { - loginToAzure(client, next); - }, - function(next) { - template.resolve('vm', configParams, function(err, identity, parameters) { - - var config = client.config; - var resourceClient = new resourceManagement.ResourceManagementClient(client.credentials, config.subscriptionId); - resourceClient.resources.get( - config.resourceGroup, - identity.resourceProviderNamespace, identity.resourceType, - identity.resourceName, - 'InstanceView', - identity.resourceProviderApiVersion, null, - function(err, result, request, response) { - if (err) { - errs.handle( - errs.create('A problem during resource creation: ' + err), - callback - ); - } else { - var jsonResult = JSON.parse(response.body); - jsonResult.name = name; - next(null, jsonResult); - } - } - ); - - }); - } - ], - function(err, result) { - if (err) { - callback(err); - } else { - // return the server info - callback(null, result); - } - } - ); -} - -getServer = function(client, options, server, callback) { - - var configParams = { - API_VERSION: '2016-03-30', - NAME: options.name, - USERNAME: options.username, - PASSWORD: options.password, - VM_SIZE: options.flavor, - LOCATION: options.location, - VM_NAME: options.name, - OS_DISK_URI: options.image.uri, - NICK_ID: options.nic - }; - - template.resolve('vm', configParams, function(err, identity, parameters) { - var config = client.config; - var resourceClient = new resourceManagement.ResourceManagementClient(client.credentials, config.subscriptionId); - resourceClient.resources.get( - config.resourceGroup, - identity.resourceProviderNamespace, identity.resourceType, - identity.resourceName, - 'InstanceView', - identity.resourceProviderApiVersion, null, - function(err, result, request, response) { - if (err) { - errs.handle( - errs.create('A problem during resource creation: ' + err), - callback - ); - } else { - var jsonResult = JSON.parse(response.body); - jsonResult = _.extend(jsonResult, server); - callback(null, jsonResult); - } - } - ); - }); -}; - -makeTemplateRequest = function(client, templateName, params, callback) { - - template.resolve(templateName, params, function(err, identity, parameters) { - - var config = client.config; - var resourceClient = new resourceManagement.ResourceManagementClient(client.credentials, config.subscriptionId); - resourceClient.resources.beginCreateOrUpdate( - config.resourceGroup, - identity.resourceProviderNamespace, '', - identity.resourceType, - identity.resourceName, - identity.resourceProviderApiVersion, - parameters, null, - function(err, result, request, response) { - if (err) { - errs.handle( - errs.create('A problem during resource creation: ' + err), - callback - ); - } - - callback(null, result); - } - ); - }); -}; - -var deleteServer = function(client, serverName, callback) { - var configParams = { - API_VERSION: '2016-03-30', - NAME: serverName, - USERNAME: ',', - PASSWORD: '', - VM_SIZE: '', - LOCATION: '', - VM_NAME: '', - OS_DISK_URI: '', - NICK_ID: '' - }; - - template.resolve('vm', configParams, function(err, identity, parameters) { - - var config = client.config; - var resourceClient = new resourceManagement.ResourceManagementClient(client.credentials, config.subscriptionId); - resourceClient.resources.beginDeleteMethod( - config.resourceGroup, - identity.resourceProviderNamespace, '', - identity.resourceType, - identity.resourceName, - identity.resourceProviderApiVersion, - parameters, - function(err, result, request, response) { - if (err) { - errs.handle( - errs.create('A problem during resource deletion: ' + err), - callback - ); - } - callback(); - } - ); - }); -}; - -exports.destroyServer = function(client, serverName, callback) { - var server = null; - - async.waterfall([ - function(next) { - loginToAzure(client, next); - }, - function(next) { - deleteServer(client, serverName, next); - } - ], - function(err) { - callback(err, true); - } - ); -}; From 6e51331eb08f1f1f57e165161a449939e86bb376 Mon Sep 17 00:00:00 2001 From: morsh Date: Mon, 2 Jan 2017 17:04:42 +0200 Subject: [PATCH 10/41] minor fixes --- lib/pkgcloud/azure-v2/azure-api.js | 6 +----- lib/pkgcloud/azure-v2/compute/client/servers.js | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/lib/pkgcloud/azure-v2/azure-api.js b/lib/pkgcloud/azure-v2/azure-api.js index dcc97e232..b7e7b973b 100644 --- a/lib/pkgcloud/azure-v2/azure-api.js +++ b/lib/pkgcloud/azure-v2/azure-api.js @@ -57,7 +57,7 @@ function login(setupLocation, callback) { client.azure.lastRefresh = new Date(); if (setupLocation) { - return this.setup(credentials, err => callback(err, credentials)) + return setup.call(client, credentials, err => callback(err, credentials)) } else { return callback(null, credentials); } @@ -73,10 +73,6 @@ function login(setupLocation, callback) { function setup(credentials, callback) { var client = this; - if (err) { - return callback(err); - } - client.azure = client.azure || {}; client.azure.location = client.azure.location || client.config.location; if (client.azure.location) { diff --git a/lib/pkgcloud/azure-v2/compute/client/servers.js b/lib/pkgcloud/azure-v2/compute/client/servers.js index 26451b4f7..916aa91a5 100644 --- a/lib/pkgcloud/azure-v2/compute/client/servers.js +++ b/lib/pkgcloud/azure-v2/compute/client/servers.js @@ -43,7 +43,7 @@ function getServers(callback) { return callback(err); } - var client = new ComputeManagementClient(credentials, self.config.subscriptionId); + var client = new ComputeManagementClient(self.azure.credentials, self.config.subscriptionId); client.virtualMachines.list(self.config.resourceGroup, (err, results) => { return err ? callback(err) From 1f0301bcb002ddb9c44c032712e660a0bb9a925b Mon Sep 17 00:00:00 2001 From: morsh Date: Tue, 3 Jan 2017 12:45:11 +0200 Subject: [PATCH 11/41] updating code according to travis-ci comments --- examples/compute/azure-v2-destroy.js | 3 +- examples/compute/azure-v2.js | 5 +- lib/pkgcloud/azure-v2/azure-api.js | 56 +++++++++++-------- lib/pkgcloud/azure-v2/client.js | 7 +-- .../azure-v2/compute/client/flavors.js | 7 +-- .../azure-v2/compute/client/images.js | 17 +++--- lib/pkgcloud/azure-v2/compute/client/index.js | 55 +----------------- .../azure-v2/compute/client/servers.js | 35 +++++++----- .../azure-v2/storage/client/containers.js | 17 +++--- lib/pkgcloud/azure-v2/storage/client/files.js | 15 ++--- lib/pkgcloud/azure-v2/templates/index.js | 6 +- 11 files changed, 88 insertions(+), 135 deletions(-) diff --git a/examples/compute/azure-v2-destroy.js b/examples/compute/azure-v2-destroy.js index 3e8ce27c6..978a6c11e 100644 --- a/examples/compute/azure-v2-destroy.js +++ b/examples/compute/azure-v2-destroy.js @@ -1,5 +1,4 @@ var pkgcloud = require('../../lib/pkgcloud'), - fs = require('fs'), client, options; @@ -33,6 +32,6 @@ client.destroyServer(options, function (err, server) { if (err) { console.log(err); } else { - console.log('Started DELETE of VM'); + console.log('Started DELETE of VM: ', server); } }); diff --git a/examples/compute/azure-v2.js b/examples/compute/azure-v2.js index caede7b0c..bf8643cda 100644 --- a/examples/compute/azure-v2.js +++ b/examples/compute/azure-v2.js @@ -1,7 +1,6 @@ var pkgcloud = require('../../lib/pkgcloud'), - fs = require('fs'), - client, - options; +var client, +var options; // // Create a pkgcloud compute instance diff --git a/lib/pkgcloud/azure-v2/azure-api.js b/lib/pkgcloud/azure-v2/azure-api.js index b7e7b973b..6e15d805e 100644 --- a/lib/pkgcloud/azure-v2/azure-api.js +++ b/lib/pkgcloud/azure-v2/azure-api.js @@ -1,8 +1,9 @@ var errs = require('errs'); var msRestAzure = require('ms-rest-azure'); -var resourceManagement = require("azure-arm-resource"); +var resourceManagement = require('azure-arm-resource'); +var templates = require('./templates'); var constants = require('./constants'); /** @@ -21,21 +22,21 @@ var constants = require('./constants'); */ function login(setupLocation, callback) { - var client = this; + var self = this; if (typeof setupLocation == 'function' && typeof callback === 'undefined') { callback = setupLocation; setupLocation = null; } // Make sure credentials are refreshed by intervals - if (client.azure && client.azure.credentials && client.azure.lastRefresh) { + if (self.azure && self.azure.credentials && self.azure.lastRefresh) { var now = new Date(); - if (now - client.azure.lastRefresh < constants.CREDENTIALS_LIFESPAN) { - return callback(null, client.azure.credentials); + if (now - self.azure.lastRefresh < constants.CREDENTIALS_LIFESPAN) { + return callback(null, self.azure.credentials); } } - var config = client.config; + var config = self.config; var servicePrincipal = config.servicePrincipal; msRestAzure.loginWithServicePrincipalSecret( servicePrincipal.clientId, @@ -52,12 +53,12 @@ function login(setupLocation, callback) { ); } - client.azure = client.azure || {}; - client.azure.credentials = credentials; - client.azure.lastRefresh = new Date(); + self.azure = self.azure || {}; + self.azure.credentials = credentials; + self.azure.lastRefresh = new Date(); if (setupLocation) { - return setup.call(client, credentials, err => callback(err, credentials)) + return self.setup(credentials, err => callback(err, credentials)); } else { return callback(null, credentials); } @@ -67,32 +68,43 @@ function login(setupLocation, callback) { /** * Setting up an azure session including querying the default location from the resource group * used by the current configuration - * @param {object} client object containing configuration. + * @param {object} credentials object containing azure credentials * @param {requestCallback} callback to respond to when complete. */ -function setup(credentials, callback) { - var client = this; +function setupLocation(credentials, callback) { + var self = this; - client.azure = client.azure || {}; - client.azure.location = client.azure.location || client.config.location; - if (client.azure.location) { + self.azure = self.azure || {}; + self.azure.location = self.azure.location || self.config.location; + if (clieselfnt.azure.location) { return callback(); } - if (client.config.resourceGroup) { - var resourceClient = new resourceManagement.ResourceManagementClient(credentials, client.config.subscriptionId); - resourceClient.resourceGroups.get(client.config.resourceGroup, (err, result) => { + if (self.config.resourceGroup) { + var resourceClient = new resourceManagement.ResourceManagementClient(credentials, self.config.subscriptionId); + resourceClient.resourceGroups.get(self.config.resourceGroup, (err, result) => { if (err) { return callback(err); } - client.azure.location = result.location; + self.azure.location = result.location; return callback(); }); } } +/** + * Binding common methods to be available to all clients + * @param {object} credentials object containing azure credentials + * @param {requestCallback} callback to respond to when complete. + */ +function bind(client) { + client.login = login.bind(client); + client.setupLocation = setupLocation.bind(client); + client.deploy = templates.deploy.bind(this); +} + module.exports = { - login -} \ No newline at end of file + bind +}; \ No newline at end of file diff --git a/lib/pkgcloud/azure-v2/client.js b/lib/pkgcloud/azure-v2/client.js index 3337b9a6a..f51efce8f 100644 --- a/lib/pkgcloud/azure-v2/client.js +++ b/lib/pkgcloud/azure-v2/client.js @@ -9,7 +9,6 @@ var util = require('util'); var base = require('../core/base'); var azureApi = require('./azure-api'); -var templates = require('./templates'); var Client = exports.Client = function (options) { base.Client.call(this, options); @@ -18,10 +17,8 @@ var Client = exports.Client = function (options) { // Allow overriding serversUrl in child classes this.provider = 'azure-v2'; - this.protocol = options.protocol || 'https://'; - this.login = azureApi.login.bind(this); - this.deploy = templates.deploy.bind(this); - + azureApi.bind(this); + if (!this.before) { this.before = []; } diff --git a/lib/pkgcloud/azure-v2/compute/client/flavors.js b/lib/pkgcloud/azure-v2/compute/client/flavors.js index 8fc76ed69..39db70aeb 100644 --- a/lib/pkgcloud/azure-v2/compute/client/flavors.js +++ b/lib/pkgcloud/azure-v2/compute/client/flavors.js @@ -6,7 +6,6 @@ */ var _ = require('lodash'); -var async = require('async'); var ComputeManagementClient = require('azure-arm-compute'); /** @@ -30,7 +29,7 @@ function getFlavors(callback) { : callback(null, results.map(res => new self.models.Flavor(self, res))); }); }); -}; +} /** * Gets a specified flavor of AWS DataSets using the provided details object. @@ -51,9 +50,9 @@ function getFlavor(flavor, callback) { callback(err) : callback(null, _.find(flavors, { id: flavorId })); }); -}; +} module.exports = { getFlavors, getFlavor -} \ No newline at end of file +}; \ No newline at end of file diff --git a/lib/pkgcloud/azure-v2/compute/client/images.js b/lib/pkgcloud/azure-v2/compute/client/images.js index a7cdc85ef..efcf5a48d 100644 --- a/lib/pkgcloud/azure-v2/compute/client/images.js +++ b/lib/pkgcloud/azure-v2/compute/client/images.js @@ -4,8 +4,6 @@ * (C) Microsoft Open Technologies, Inc. * */ -var _ = require('lodash'); -var async = require('async'); var ComputeManagementClient = require('azure-arm-compute'); var constants = require('../../constants'); @@ -58,7 +56,6 @@ exports.getImages = function getImages(options, callback) { */ exports.getImage = function getImage(image , options, callback) { var self = this; - var imageId = image instanceof self.models.Image ? image.id : image; var version = image instanceof self.models.Image ? image.name : image; if (!callback && typeof options === 'function') { @@ -107,11 +104,13 @@ exports.createImage = function createImage(options, callback) { throw new TypeError('`server` is a required option'); } - var self = this, - serverId = options.server instanceof self.models.Server - ? options.server.id - : options.server; + var self = this; + var serverId = options.server instanceof self.models.Server + ? options.server.id + : options.server; + console.log('creating image for server ', serverId); + return callback(new Error('method not implemented')); // azureApi.createImage(this, serverId, options.name, function (err, result) { // return !err // ? self.getImage(result, callback) @@ -129,8 +128,8 @@ exports.createImage = function createImage(options, callback) { */ exports.destroyImage = function destroyImage(image, callback) { var self = this, - imageId = image instanceof self.models.Image ? image.id : image, - path = self.config.subscriptionId + '/services/images/' + imageId; + var imageId = image instanceof self.models.Image ? image.id : image, + var path = self.config.subscriptionId + '/services/images/' + imageId; self._xmlRequest({ method: 'DELETE', diff --git a/lib/pkgcloud/azure-v2/compute/client/index.js b/lib/pkgcloud/azure-v2/compute/client/index.js index 99ea582db..15b91d0bb 100644 --- a/lib/pkgcloud/azure-v2/compute/client/index.js +++ b/lib/pkgcloud/azure-v2/compute/client/index.js @@ -49,57 +49,4 @@ var Client = exports.Client = function (options) { } }; -util.inherits(Client, azure.Client); - -Client.prototype._query = function query(action, query, callback) { - return this._request({ - method: 'POST', - headers: { }, - body: _.extend({ Action: action }, query) - }, function (err, body, res) { - if (err) { return callback(err); } - xml2JSON(body, function (err, data) { - return err - ? callback(err) - : callback(data, res); - }); - }); -}; - -Client.prototype.get = function get(action, callback) { - return this._request({ path: action }, function (err, body, res) { - if (err) { - return callback(err); - } - xml2JSON(body, function (err, data) { - return err - ? callback(err) - : callback(null, data, res); - }); - }); -}; - -Client.prototype._xmlRequest = function query(options, callback) { - - return this._request(options, function (err, body, res) { - if (err) { - return callback(err); - } - xml2JSON(body, function (err, data) { - return err ? - callback(err) : - callback(null, data, res); - }); - }); -}; - -Client.prototype._getUrl = function (options) { - options = options || {}; - - return urlJoin(this.protocol + this.serversUrl + '/', - (typeof options === 'string' - ? options - : options.path)); -}; - - +util.inherits(Client, azure.Client); \ No newline at end of file diff --git a/lib/pkgcloud/azure-v2/compute/client/servers.js b/lib/pkgcloud/azure-v2/compute/client/servers.js index 916aa91a5..7738a2701 100644 --- a/lib/pkgcloud/azure-v2/compute/client/servers.js +++ b/lib/pkgcloud/azure-v2/compute/client/servers.js @@ -4,7 +4,6 @@ * (C) Microsoft Open Technologies, Inc. * */ -var async = require('async'); var errs = require('errs'); var ComputeManagementClient = require('azure-arm-compute'); @@ -16,7 +15,7 @@ var constants = require('../../constants'); */ function getVersion(callback) { callback(null, constants.MANAGEMENT_API_VERSION); -}; +} /** * Gets the current API limits @@ -27,7 +26,7 @@ function getLimits(callback) { errs.create({ message: 'Azure\'s API is not rate limited' }), callback ); -}; +} /** * Lists all servers available to your account. @@ -50,7 +49,7 @@ function getServers(callback) { : callback(null, results.map(res => new self.models.Server(self, res))); }); }); -}; +} /** * Gets a server in Azure. @@ -77,7 +76,7 @@ function getServer(server, callback) { : callback(null, new self.models.Server(self, result)); }); }); -}; +} /** * Creates a server with the specified options @@ -108,12 +107,12 @@ function createServer(options, callback) { } var templateName = 'compute' + (options.imageSourceUri ? '-from-image' : ''); - self.deploy(templateName, options, (err, result) => { + self.deploy(templateName, options, (err) => { return err ? callback(err) : self.getServer(options.name, callback); }); -}; +} /** * Destroy a server in Azure. @@ -130,14 +129,14 @@ function destroyServer(server, callback) { return callback(err); } - var client = new ComputeManagementClient(credentials, self.config.subscriptionId); - client.virtualMachines.deleteMethod(self.config.resourceGroup, serverId, (err, result) => { + var client = new ComputeManagementClient(self.azure.credentials, self.config.subscriptionId); + client.virtualMachines.deleteMethod(self.config.resourceGroup, serverId, (err) => { return err ? callback(err) : callback(null, serverId); }); }); -}; +} // // ### function stopServer(server, callback) @@ -147,14 +146,17 @@ function destroyServer(server, callback) { // Destroy a server in Azure. // function stopServer(server, callback) { + var self = this; var serverId = server instanceof self.models.Server ? server.id : server; + console.log('Stopping', serverId, '...'); + return callback(new Error('not implemented yet')); // azureApi.stopServer(this, serverId, function (err) { // return !err // ? callback(null, { ok: serverId }) // : callback(err); // }); -}; +} // // ### function createHostedService(serviceName, callback) @@ -164,12 +166,14 @@ function stopServer(server, callback) { // Creates a Hosted Service in Azure. // function createHostedService(serviceName, callback) { + console.log('createing hosted service', serviceName, '...'); + return callback(new Error('not implemented yet')); // azureApi.createHostedService(this, serviceName, function (err, res) { // return !err // ? callback(null, res) // : callback(err); // }); -}; +} // // ### function rebootServer (server, options, callback) @@ -179,14 +183,17 @@ function createHostedService(serviceName, callback) { // Reboots a server // function rebootServer(server, callback) { + var self = this; var serverId = server instanceof self.models.Server ? server.id : server; + console.log('rebotting server', serverId, '...'); + return callback(new Error('not implemented yet')); // azureApi.rebootServer(this, serverId, function (err) { // return !err // ? callback(null, { ok: serverId }) // : callback(err); // }); -}; +} // // ### function renameServer(server, name, callback) @@ -201,7 +208,7 @@ function renameServer(server, name, callback) { errs.create({ message: 'Not supported by Azure.' }), callback ); -}; +} module.exports = { getVersion, diff --git a/lib/pkgcloud/azure-v2/storage/client/containers.js b/lib/pkgcloud/azure-v2/storage/client/containers.js index 6c439e26a..19a20e44c 100644 --- a/lib/pkgcloud/azure-v2/storage/client/containers.js +++ b/lib/pkgcloud/azure-v2/storage/client/containers.js @@ -5,7 +5,6 @@ * */ -var async = require('async'); var StorageManagementClient = require('azure-arm-storage'); var azureStorage = require('azure-storage'); @@ -31,7 +30,7 @@ function getContainers(callback) { : callback(null, results.map(res => new self.models.Container(self, res))); }); }); -}; +} /** * Responds with the azure stoarge account with the given name @@ -55,7 +54,7 @@ function getContainer(container, callback) { : callback(null, new self.models.Container(self, result)); }); }); -}; +} /** * Create a new storage account @@ -75,12 +74,12 @@ function createContainer(options, callback) { var containerName = options instanceof self.models.Container ? options.name : options; var parameters = typeof options == 'string' ? { name: options } : options; - self.deploy('storage', parameters, (err, result) => { + self.deploy('storage', parameters, (err) => { return err ? callback(err) : self.getContainer(containerName, callback); - }) -}; + }); +} /** * Destroy a new storage account @@ -104,7 +103,7 @@ function destroyContainer(container, callback) { } var storageClient = new StorageManagementClient(self.azure.credentials, self.config.subscriptionId); - storageClient.storageAccounts.deleteMethod(self.config.resourceGroup, containerName, next); + storageClient.storageAccounts.deleteMethod(self.config.resourceGroup, containerName, callback); }); }; @@ -167,7 +166,7 @@ function getBlobService(options, storageAccountName, callback) { callback(null, blobService); }); }); -}; +} module.exports = { @@ -178,4 +177,4 @@ module.exports = { destroyContainer, listContainerKeys, getContainerKey -} \ No newline at end of file +}; \ No newline at end of file diff --git a/lib/pkgcloud/azure-v2/storage/client/files.js b/lib/pkgcloud/azure-v2/storage/client/files.js index 8d6293324..471a71977 100644 --- a/lib/pkgcloud/azure-v2/storage/client/files.js +++ b/lib/pkgcloud/azure-v2/storage/client/files.js @@ -13,8 +13,6 @@ var through = require('through2'); var _ = require('lodash'); var urlJoin = require('url-join'); -var StorageManagementClient = require('azure-arm-storage'); - var constants = require('../../constants'); /** @@ -199,7 +197,7 @@ exports.getFiles = function (container, options, callback) { cb(null, blobs); } } - } + }; blobService.listBlobsSegmented(azureContainer, null, function(err, result) { aggregateBlobs(err, result, (err, blobs) => { @@ -229,7 +227,7 @@ exports.getFile = function (container, file, options, callback) { var azureContainerName = options.storage && options.storage.container || constants.DEFAULT_STORAGE_CONTAINER; var fileName = file instanceof this.models.File ? file.name : file; - self.getBlobService(azureContainer, containerName, (err, blobService) => { + self.getBlobService(azureContainerName, containerName, (err, blobService) => { if (err) { return callback(err); @@ -237,9 +235,9 @@ exports.getFile = function (container, file, options, callback) { blobService.getBlobProperties(azureContainerName, fileName, (err, properties, status) => { return err ? - next(err) : (!status || !status.isSuccessful) ? - next(new Error(`status is not successfull: ${JSON.stringify(status || null)}`)) : - next(null, new self.models.File(self, properties)); + callback(err) : (!status || !status.isSuccessful) ? + callback(new Error(`status is not successfull: ${JSON.stringify(status || null)}`)) : + callback(null, new self.models.File(self, properties)); }); }); }; @@ -267,8 +265,7 @@ exports.download = function (options, callback) { var azureContainerName = options.storage && options.storage.container || constants.DEFAULT_STORAGE_CONTAINER; var blobName = options.remote instanceof this.models.File ? options.remote.name : options.remote; var inputStream; - var apiStream; - + if (options.local) { inputStream = filed(options.local); } diff --git a/lib/pkgcloud/azure-v2/templates/index.js b/lib/pkgcloud/azure-v2/templates/index.js index ba5af0238..c4c768b8a 100644 --- a/lib/pkgcloud/azure-v2/templates/index.js +++ b/lib/pkgcloud/azure-v2/templates/index.js @@ -1,10 +1,8 @@ var path = require('path'); -var fs = require("fs"); -var async = require('async'); +var fs = require('fs'); var resourceManagement = require('azure-arm-resource'); - function resolve(templateId) { var templatePath = path.join(__dirname, '..', 'templates', 'arm-' + templateId + '.json'); var contents = fs.readFileSync(templatePath); @@ -42,4 +40,4 @@ function deploy(templateName, options, callback) { module.exports = { deploy -} \ No newline at end of file +}; \ No newline at end of file From 288c81eba1c034f570d6bd4929f53cf976ccddb9 Mon Sep 17 00:00:00 2001 From: morsh Date: Tue, 3 Jan 2017 13:04:44 +0200 Subject: [PATCH 12/41] travis-ci review #2 --- examples/compute/azure-v2.js | 4 ++-- lib/pkgcloud/azure-v2/azure-api.js | 2 +- lib/pkgcloud/azure-v2/compute/client/images.js | 4 ++-- lib/pkgcloud/azure-v2/compute/client/index.js | 1 - lib/pkgcloud/azure-v2/storage/client/containers.js | 2 +- 5 files changed, 6 insertions(+), 7 deletions(-) diff --git a/examples/compute/azure-v2.js b/examples/compute/azure-v2.js index bf8643cda..23a07408a 100644 --- a/examples/compute/azure-v2.js +++ b/examples/compute/azure-v2.js @@ -1,5 +1,5 @@ -var pkgcloud = require('../../lib/pkgcloud'), -var client, +var pkgcloud = require('../../lib/pkgcloud'); +var client; var options; // diff --git a/lib/pkgcloud/azure-v2/azure-api.js b/lib/pkgcloud/azure-v2/azure-api.js index 6e15d805e..839d3551e 100644 --- a/lib/pkgcloud/azure-v2/azure-api.js +++ b/lib/pkgcloud/azure-v2/azure-api.js @@ -76,7 +76,7 @@ function setupLocation(credentials, callback) { self.azure = self.azure || {}; self.azure.location = self.azure.location || self.config.location; - if (clieselfnt.azure.location) { + if (self.azure.location) { return callback(); } diff --git a/lib/pkgcloud/azure-v2/compute/client/images.js b/lib/pkgcloud/azure-v2/compute/client/images.js index efcf5a48d..7c418116f 100644 --- a/lib/pkgcloud/azure-v2/compute/client/images.js +++ b/lib/pkgcloud/azure-v2/compute/client/images.js @@ -127,8 +127,8 @@ exports.createImage = function createImage(options, callback) { * Destroys an image in Azure */ exports.destroyImage = function destroyImage(image, callback) { - var self = this, - var imageId = image instanceof self.models.Image ? image.id : image, + var self = this; + var imageId = image instanceof self.models.Image ? image.id : image; var path = self.config.subscriptionId + '/services/images/' + imageId; self._xmlRequest({ diff --git a/lib/pkgcloud/azure-v2/compute/client/index.js b/lib/pkgcloud/azure-v2/compute/client/index.js index 15b91d0bb..4457400bf 100644 --- a/lib/pkgcloud/azure-v2/compute/client/index.js +++ b/lib/pkgcloud/azure-v2/compute/client/index.js @@ -6,7 +6,6 @@ */ var util = require('util'); -var urlJoin = require('url-join'); var https = require('https'); var auth = require('../../../common/auth'); var constants = require('../../constants'); diff --git a/lib/pkgcloud/azure-v2/storage/client/containers.js b/lib/pkgcloud/azure-v2/storage/client/containers.js index 19a20e44c..59a673200 100644 --- a/lib/pkgcloud/azure-v2/storage/client/containers.js +++ b/lib/pkgcloud/azure-v2/storage/client/containers.js @@ -105,7 +105,7 @@ function destroyContainer(container, callback) { var storageClient = new StorageManagementClient(self.azure.credentials, self.config.subscriptionId); storageClient.storageAccounts.deleteMethod(self.config.resourceGroup, containerName, callback); }); -}; +} function listContainerKeys(container, callback) { var self = this; From 1eb444f7f9c1679bef1b17bd6422c65e34fddbc2 Mon Sep 17 00:00:00 2001 From: morsh Date: Wed, 4 Jan 2017 14:12:48 +0200 Subject: [PATCH 13/41] adding first tests --- .vscode/launch.json | 29 +++++ examples/compute/azure-v2.js | 16 +-- .../azure-v2/compute/client/servers.js | 95 +++++++--------- lib/pkgcloud/azure-v2/compute/server.js | 2 +- package.json | 1 + .../compute/client/test-getServers.js | 63 +++++++++++ test/azure-v2/mock-requests.js | 40 +++++++ test/configs/mock/azure-v2.json | 9 ++ .../azure-v2/authentication-certificate.json | 9 ++ test/fixtures/azure-v2/server.json | 104 ++++++++++++++++++ test/fixtures/azure-v2/servers.json | 60 ++++++++++ test/fixtures/azure-v2/subscriptions.json | 15 +++ test/helpers/index.js | 4 +- 13 files changed, 382 insertions(+), 65 deletions(-) create mode 100644 test/azure-v2/compute/client/test-getServers.js create mode 100644 test/azure-v2/mock-requests.js create mode 100644 test/configs/mock/azure-v2.json create mode 100644 test/fixtures/azure-v2/authentication-certificate.json create mode 100644 test/fixtures/azure-v2/server.json create mode 100644 test/fixtures/azure-v2/servers.json create mode 100644 test/fixtures/azure-v2/subscriptions.json diff --git a/.vscode/launch.json b/.vscode/launch.json index 1aa77eba6..7ac21d41b 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -35,6 +35,35 @@ "cwd": "${workspaceRoot}", "stopOnEntry": true }, + { + "type": "node", + "request": "launch", + "name": "Test Azure V2 Server", + "program": "${workspaceRoot}//test//azure-v2//compute//client//test-getServers.js", + "cwd": "${workspaceRoot}", + "stopOnEntry": true, + "env": { + "MOCK": "on" + } + }, + { + // Name of configuration; appears in the launch configuration drop down menu. + "name": "Run mocha", + // Type of configuration. Possible values: "node", "mono". + "type": "node", + // Workspace relative or absolute path to the program. + "program": "${workspaceRoot}//node_modules//mocha//bin//_mocha", + // Automatically stop program after launch. + "stopOnEntry": false, + // Command line arguments passed to the program. + "args": ["${workspaceRoot}//test//azure-v2//compute//client//test-getServers.js"], + // Workspace relative or absolute path to the working directory of the program being debugged. Default is the current workspace. + "cwd": "${workspaceRoot}", + // Workspace relative or absolute path to the runtime executable to be used. Default is the runtime executable on the PATH. + "runtimeExecutable": null, + // Environment variables passed to the program. + "env": { "MOCK": "on" } + }, { "type": "node", "request": "attach", diff --git a/examples/compute/azure-v2.js b/examples/compute/azure-v2.js index 23a07408a..331d94192 100644 --- a/examples/compute/azure-v2.js +++ b/examples/compute/azure-v2.js @@ -6,15 +6,15 @@ var options; // Create a pkgcloud compute instance // options = { - resourceGroup: '{resourceGroup}', provider: 'azure-v2', - storageAccount: '{storeName}', - storageAccessKey: '{storeKey}', - subscriptionId: '{subscriptionId}', - spClientId: '{spClientId}', - spSecret: '{spSecret}', - spDomain: '{spDomain}', - spSubscriptionId: '{spSubscriptionId}' + subscriptionId: "{subscriptionId}", + resourceGroup: "{resourceGroup}", + + servicePrincipal: { + clientId: "{spClientId}", + secret: "{spSecret}", + domain: "{spDomain}" + } }; client = pkgcloud.compute.createClient(options); diff --git a/lib/pkgcloud/azure-v2/compute/client/servers.js b/lib/pkgcloud/azure-v2/compute/client/servers.js index 7738a2701..f8a186c28 100644 --- a/lib/pkgcloud/azure-v2/compute/client/servers.js +++ b/lib/pkgcloud/azure-v2/compute/client/servers.js @@ -138,72 +138,60 @@ function destroyServer(server, callback) { }); } -// -// ### function stopServer(server, callback) -// #### @server {Server|String} Server id or a server -// #### @callback {Function} f(err, serverId). -// -// Destroy a server in Azure. -// +/** + * Stop a server in Azure. + * @param {Server|string} server Server id or a server + * @param {function} callback cb(err, serverId). + */ function stopServer(server, callback) { var self = this; var serverId = server instanceof self.models.Server ? server.id : server; - console.log('Stopping', serverId, '...'); - return callback(new Error('not implemented yet')); - // azureApi.stopServer(this, serverId, function (err) { - // return !err - // ? callback(null, { ok: serverId }) - // : callback(err); - // }); -} + self.login(err => { + + if (err) { + return callback(err); + } -// -// ### function createHostedService(serviceName, callback) -// #### @serviceName {String} name of the Hosted Service -// #### @callback {Function} f(err, serverId). -// -// Creates a Hosted Service in Azure. -// -function createHostedService(serviceName, callback) { - console.log('createing hosted service', serviceName, '...'); - return callback(new Error('not implemented yet')); - // azureApi.createHostedService(this, serviceName, function (err, res) { - // return !err - // ? callback(null, res) - // : callback(err); - // }); + var client = new ComputeManagementClient(self.azure.credentials, self.config.subscriptionId); + client.virtualMachines.powerOff(self.config.resourceGroup, serverId, (err) => { + return err + ? callback(err) + : callback(null, serverId); + }); + }); } -// -// ### function rebootServer (server, options, callback) -// #### @server {Server|String} The server to reboot -// #### @callback {Function} f(err, server). -// -// Reboots a server -// +/** + * Restart a server in Azure. + * @param {Server|string} server Server id or a server + * @param {function} callback cb(err, serverId). + */ function rebootServer(server, callback) { var self = this; var serverId = server instanceof self.models.Server ? server.id : server; - console.log('rebotting server', serverId, '...'); - return callback(new Error('not implemented yet')); - // azureApi.rebootServer(this, serverId, function (err) { - // return !err - // ? callback(null, { ok: serverId }) - // : callback(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, (err) => { + return err + ? callback(err) + : callback(null, serverId); + }); + }); } -// -// ### function renameServer(server, name, callback) -// #### @server {Server|String} Server id or a server -// #### @name {String} New name to apply to the server -// #### @callback {Function} f(err, server). -// -// Renames a server -// -function renameServer(server, name, callback) { +/** + * Rename a server in Azure. + * @param {Server|string} server Server id or a server + * @param {function} callback cb(err, serverId). + */ +function renameServer(server, callback) { return errs.handle( errs.create({ message: 'Not supported by Azure.' }), callback @@ -218,7 +206,6 @@ module.exports = { createServer, destroyServer, stopServer, - createHostedService, rebootServer, renameServer }; diff --git a/lib/pkgcloud/azure-v2/compute/server.js b/lib/pkgcloud/azure-v2/compute/server.js index d742ad6b3..2d0066055 100644 --- a/lib/pkgcloud/azure-v2/compute/server.js +++ b/lib/pkgcloud/azure-v2/compute/server.js @@ -16,7 +16,7 @@ var Server = exports.Server = function Server(client, details) { util.inherits(Server, base.Server); -Server.prototype._setProperties = function (details, statuses) { +Server.prototype._setProperties = function (details) { details = details || {}; this.id = details.id || ''; diff --git a/package.json b/package.json index c03fd267f..3e3e8de23 100644 --- a/package.json +++ b/package.json @@ -84,6 +84,7 @@ "jshint": "~2.7.0", "mocha": "1.21.x", "mocha-lcov-reporter": "0.0.1", + "nock": "^9.0.2", "should": "4.0.x" }, "main": "./lib/pkgcloud", diff --git a/test/azure-v2/compute/client/test-getServers.js b/test/azure-v2/compute/client/test-getServers.js new file mode 100644 index 000000000..98ad5b37a --- /dev/null +++ b/test/azure-v2/compute/client/test-getServers.js @@ -0,0 +1,63 @@ +//TODO: Make this a vows test + +var mockRequests = require('../../mock-requests'); +var helpers = require('../../../helpers'); +var should = require('should'); +var mock = !!process.env.MOCK; + +// client.getServer('azure-vm-server', function (err, result) { +// if (err) { +// console.error(err); +// } else { +// console.dir(result); +// } +// }); + +// client.getServers(function (err, result) { +// if (err) { +// console.error(err); +// } else { +// console.dir(result); +// } +// }); + +describe('pkgcloud/amazon/groups', function () { + + var client; + + before(function (done) { + debugger; + client = helpers.createClient('azure-v2', 'compute'); + + if (!mock) { + return done(); + } + + mockRequests.prepare(); + }); + + it('add SecurityGroup should succeed', function(done) { + + debugger; + client.getServer('azure-vm-server', (err, server) => { + should.not.exist(err); + should.exist(server) + server.status.should.equal('RUNNING'); + }); + + }); + + after(function(done) { + if (!mock) { + return done(); + } + + //server.close(done); + }); +}); + + + + + + diff --git a/test/azure-v2/mock-requests.js b/test/azure-v2/mock-requests.js new file mode 100644 index 000000000..8ec54bb54 --- /dev/null +++ b/test/azure-v2/mock-requests.js @@ -0,0 +1,40 @@ +var path = require('path'); +var nock = require('nock'); +var helpers = require('../helpers'); + +const azureAuthUri = 'https://login.microsoftonline.com'; +const azureManagementUri = 'https://management.azure.com'; +const requestId = 'b67cc525-ecc5-4661-8fd6-fb3e57d724f5'; +const apiVersion = '2016-03-30'; + +function loadFixture(name) { + return helpers.loadFixture(path.join('azure-v2', name)); +} + +function prepare() { + + var config = helpers.loadConfig('azure-v2'); + var sp = config.servicePrincipal; + + + // Nock authentication requests + nock(`${azureAuthUri}`) + .post(`/${sp.domain}/oauth2/token?api-version=1.0`) + .reply(200, loadFixture('authentication-certificate.json')); + + // Subscriptions + nock(`${azureManagementUri}`) + .get('/subscriptions?api-version=2015-11-01') + .reply(200, loadFixture('subscriptions.json')); + + // Servers + nock(`${azureManagementUri}/subscriptions/${config.subscriptionId}/resourceGroups/${config.resourceGroup}/providers/Microsoft.Compute`) + .get(`/virtualMachines?api-version=${apiVersion}`) + .reply(200, loadFixture('servers.json')) + .get(`/virtualMachines/azure-vm-server?$expand=instanceView&api-version=${apiVersion}`) + .reply(200, loadFixture('server.json')) +} + +module.exports = { + prepare +}; \ No newline at end of file diff --git a/test/configs/mock/azure-v2.json b/test/configs/mock/azure-v2.json new file mode 100644 index 000000000..ee401e17d --- /dev/null +++ b/test/configs/mock/azure-v2.json @@ -0,0 +1,9 @@ +{ + "subscriptionId": "azure-account-subscription-id", + "resourceGroup": "resource-group", + "servicePrincipal": { + "clientId": "sp-client-id", + "secret": "sp-secret", + "domain": "sp-domain" + } +} diff --git a/test/fixtures/azure-v2/authentication-certificate.json b/test/fixtures/azure-v2/authentication-certificate.json new file mode 100644 index 000000000..365b7bf1a --- /dev/null +++ b/test/fixtures/azure-v2/authentication-certificate.json @@ -0,0 +1,9 @@ +{ + "token_type":"Bearer", + "expires_in":"3600", + "ext_expires_in":"10800", + "expires_on":"1483452282", + "not_before":"1483448382", + "resource":"https://management.core.windows.net/", + "access_token":"XXXXXXXX" +} \ No newline at end of file diff --git a/test/fixtures/azure-v2/server.json b/test/fixtures/azure-v2/server.json new file mode 100644 index 000000000..b456dae63 --- /dev/null +++ b/test/fixtures/azure-v2/server.json @@ -0,0 +1,104 @@ +{ + "id": "/subscriptions/subscriptionId/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachines/vmName", + "name":"vmName", + "type":"Microsoft.Compute/virtualMachines", + "location":"somelocation", + "properties" : { + "hardwareProfile": { + "vmSize":"Standard_SIZE" + }, + "storageProfile": { + "imageReference": { + "publisher":"PUBLISHER", + "offer":"OFFER", + "sku":"SKU", + "version":"VERSION" + }, + "osDisk": { + "osType":"SOME_OS", + "name":"osdisk", + "vhd": { "uri":"https://azurestorage.blob.core.windows.net/vhds/osdisk.vhd" }, + "caching":"ReadWrite", + "createOption":"FromImage" + }, + "dataDisks": [ + { + "lun":0, + "name":"datadisk1", + "vhd": { "uri":"https://azurestorage.blob.core.windows.net/vhds/datadisk1.vhd" }, + "caching":"None", + "createOption":"Empty", + "diskSizeGB":100 + } + ] + }, + "osProfile": { + "computerName":"vmName", + "adminUsername":"pkgcloud", + "linuxConfiguration": { "disablePasswordAuthentication":false }, + "secrets":[] + }, + "networkProfile": { + "networkInterfaces":[ + { + "id":"/subscriptions/subscriptionId/resourceGroups/resource-group/providers/Microsoft.Network/networkInterfaces/nicName" + } + ] + }, + "diagnosticsProfile": { + "bootDiagnostics": { + "enabled":true, + "storageUri":"https://azurestorage.blob.core.windows.net/" + } + }, + "provisioningState": "Succeeded", + "instanceView": { + "vmAgent": { + "vmAgentVersion": "2.1.3", + "extensionHandlers": [], + "statuses": [{ + "code": "ProvisioningState/succeeded", + "level": "Info", + "displayStatus": "Ready", + "message": "Guest Agent is running", + "time": "2017-01-03T15:14:40.000Z" + }] + }, + "disks": [{ + "name": "osdisk", + "statuses": [{ + "code": "ProvisioningState/succeeded", + "level": "Info", + "displayStatus": "Provisioning succeeded", + "time": "2017-01-02T14:57:17.094Z" + }] + }, + { + "name": "datadisk1", + "statuses": [{ + "code": "ProvisioningState/succeeded", + "level": "Info", + "displayStatus": "Provisioning succeeded", + "time": "2017-01-02T14:57:17.094Z" + }] + }], + "bootDiagnostics": { + "consoleScreenshotBlobUri": "https://azurestorage.blob.core.windows.net/bootdiagnostics/vmName.screenshot.bmp", + "serialConsoleLogBlobUri": "https://azurestorage.blob.core.windows.net/bootdiagnostics/vmName.serialconsole.log" + }, + "statuses": [{ + "code": "ProvisioningState/succeeded", + "level": "Info", + "displayStatus": "Provisioning succeeded", + "time": "2017-01-02T14:57:55.844Z" + }, + { + "code": "PowerState/running", + "level": "Info", + "displayStatus": "VM running" + }] + }, + "vmId":"VM_ID_GUID" + } +} + diff --git a/test/fixtures/azure-v2/servers.json b/test/fixtures/azure-v2/servers.json new file mode 100644 index 000000000..c0b6a2564 --- /dev/null +++ b/test/fixtures/azure-v2/servers.json @@ -0,0 +1,60 @@ +{ + "value": [ + { + "id": "/subscriptions/subscriptionId/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachines/vmName", + "name":"vmName", + "type":"Microsoft.Compute/virtualMachines", + "location":"somelocation", + "hardwareProfile": { + "vmSize":"Standard_SIZE" + }, + "storageProfile": { + "imageReference": { + "publisher":"PUBLISHER", + "offer":"OFFER", + "sku":"SKU", + "version":"VERSION" + }, + "osDisk": { + "osType":"SOME_OS", + "name":"osdisk", + "vhd": { "uri":"https://azurestorage.blob.core.windows.net/vhds/osdisk.vhd" }, + "caching":"ReadWrite", + "createOption":"FromImage" + }, + "dataDisks": [ + { + "lun":0, + "name":"datadisk1", + "vhd": { "uri":"https://azurestorage.blob.core.windows.net/vhds/datadisk1.vhd" }, + "caching":"None", + "createOption":"Empty", + "diskSizeGB":100 + } + ] + }, + "osProfile": { + "computerName":"vmName", + "adminUsername":"pkgcloud", + "linuxConfiguration": { "disablePasswordAuthentication":false }, + "secrets":[] + }, + "networkProfile": { + "networkInterfaces":[ + { + "id":"/subscriptions/subscriptionId/resourceGroups/resource-group/providers/Microsoft.Network/networkInterfaces/nicName" + } + ] + }, + "diagnosticsProfile": { + "bootDiagnostics": { + "enabled":true, + "storageUri":"https://azurestorage.blob.core.windows.net/" + } + }, + "provisioningState": "Succeeded", + "vmId":"VM_ID_GUID" + } + ] +} + diff --git a/test/fixtures/azure-v2/subscriptions.json b/test/fixtures/azure-v2/subscriptions.json new file mode 100644 index 000000000..330059a9d --- /dev/null +++ b/test/fixtures/azure-v2/subscriptions.json @@ -0,0 +1,15 @@ +{ + "value": [ + { + "id":"/subscriptions/azure-account-subscription-id", + "subscriptionId":"azure-account-subscription-id", + "displayName":"Subscription Name", + "state":"Enabled", + "subscriptionPolicies": { + "locationPlacementId":"Internal_2014-09-01", + "quotaId":"Internal_2014-09-01", + "spendingLimit":"Off" + } + } + ] +} \ No newline at end of file diff --git a/test/helpers/index.js b/test/helpers/index.js index 830bb7975..b9dc2ae44 100644 --- a/test/helpers/index.js +++ b/test/helpers/index.js @@ -67,8 +67,8 @@ helpers.loadConfig = function loadConfig(provider) { return JSON.parse(content); }; -helpers.fixturePath = function fixturePath(path) { - return __dirname + '/../fixtures/' + path; +helpers.fixturePath = function fixturePath(fpath) { + return path.join(__dirname, '..', 'fixtures', fpath); }; helpers.loadFixture = function loadFixture(path, json) { From 186f0d01b589bde0f2d860f2eee2ba573d917fbb Mon Sep 17 00:00:00 2001 From: morsh Date: Wed, 4 Jan 2017 14:17:50 +0200 Subject: [PATCH 14/41] update test name --- .../compute/client/test-getServers.js | 20 ++----------------- 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/test/azure-v2/compute/client/test-getServers.js b/test/azure-v2/compute/client/test-getServers.js index 98ad5b37a..c5700676d 100644 --- a/test/azure-v2/compute/client/test-getServers.js +++ b/test/azure-v2/compute/client/test-getServers.js @@ -5,23 +5,7 @@ var helpers = require('../../../helpers'); var should = require('should'); var mock = !!process.env.MOCK; -// client.getServer('azure-vm-server', function (err, result) { -// if (err) { -// console.error(err); -// } else { -// console.dir(result); -// } -// }); - -// client.getServers(function (err, result) { -// if (err) { -// console.error(err); -// } else { -// console.dir(result); -// } -// }); - -describe('pkgcloud/amazon/groups', function () { +describe('pkgcloud/azure-v2/servers', function () { var client; @@ -36,7 +20,7 @@ describe('pkgcloud/amazon/groups', function () { mockRequests.prepare(); }); - it('add SecurityGroup should succeed', function(done) { + it('Get a single server with RUNNING state', function(done) { debugger; client.getServer('azure-vm-server', (err, server) => { From c586d4260c38e7b53a93b0154610b9d63ffba5e4 Mon Sep 17 00:00:00 2001 From: morsh Date: Wed, 4 Jan 2017 16:04:28 +0200 Subject: [PATCH 15/41] fix bind --- lib/pkgcloud/azure-v2/azure-api.js | 2 +- test/azure-v2/compute/client/test-getServers.js | 17 +++++++++++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/lib/pkgcloud/azure-v2/azure-api.js b/lib/pkgcloud/azure-v2/azure-api.js index 839d3551e..a62976acc 100644 --- a/lib/pkgcloud/azure-v2/azure-api.js +++ b/lib/pkgcloud/azure-v2/azure-api.js @@ -102,7 +102,7 @@ function setupLocation(credentials, callback) { function bind(client) { client.login = login.bind(client); client.setupLocation = setupLocation.bind(client); - client.deploy = templates.deploy.bind(this); + client.deploy = templates.deploy.bind(client); } module.exports = { diff --git a/test/azure-v2/compute/client/test-getServers.js b/test/azure-v2/compute/client/test-getServers.js index c5700676d..be4187f2f 100644 --- a/test/azure-v2/compute/client/test-getServers.js +++ b/test/azure-v2/compute/client/test-getServers.js @@ -6,11 +6,10 @@ var should = require('should'); var mock = !!process.env.MOCK; describe('pkgcloud/azure-v2/servers', function () { - + this.timeout(200000); var client; before(function (done) { - debugger; client = helpers.createClient('azure-v2', 'compute'); if (!mock) { @@ -18,6 +17,7 @@ describe('pkgcloud/azure-v2/servers', function () { } mockRequests.prepare(); + done(); }); it('Get a single server with RUNNING state', function(done) { @@ -27,6 +27,19 @@ describe('pkgcloud/azure-v2/servers', function () { should.not.exist(err); should.exist(server) server.status.should.equal('RUNNING'); + done(); + }); + + }); + + it('Get a single server with RUNNING state 2', function(done) { + + debugger; + client.getServer('azure-vm-server', (err, server) => { + should.not.exist(err); + should.exist(server) + server.status.should.equal('RUNNING'); + done(); }); }); From 6ae7635f12eb232d5bb87fc1eb78845b06b51658 Mon Sep 17 00:00:00 2001 From: morsh Date: Wed, 4 Jan 2017 17:23:13 +0200 Subject: [PATCH 16/41] update test for multple calls --- .../compute/client/test-getServers.js | 31 ++++--------------- 1 file changed, 6 insertions(+), 25 deletions(-) diff --git a/test/azure-v2/compute/client/test-getServers.js b/test/azure-v2/compute/client/test-getServers.js index be4187f2f..b849cbeee 100644 --- a/test/azure-v2/compute/client/test-getServers.js +++ b/test/azure-v2/compute/client/test-getServers.js @@ -5,28 +5,16 @@ var helpers = require('../../../helpers'); var should = require('should'); var mock = !!process.env.MOCK; -describe('pkgcloud/azure-v2/servers', function () { - this.timeout(200000); - var client; - - before(function (done) { - client = helpers.createClient('azure-v2', 'compute'); +client = helpers.createClient('azure-v2', 'compute'); - if (!mock) { - return done(); - } - - mockRequests.prepare(); - done(); - }); +describe('pkgcloud/azure-v2/servers', function () { it('Get a single server with RUNNING state', function(done) { - debugger; - client.getServer('azure-vm-server', (err, server) => { + mockRequests.prepare(); + client.getServers((err, servers) => { should.not.exist(err); - should.exist(server) - server.status.should.equal('RUNNING'); + should(servers).be.instanceOf(Array).and.have.lengthOf(1); done(); }); @@ -34,7 +22,7 @@ describe('pkgcloud/azure-v2/servers', function () { it('Get a single server with RUNNING state 2', function(done) { - debugger; + mockRequests.prepare(); client.getServer('azure-vm-server', (err, server) => { should.not.exist(err); should.exist(server) @@ -44,13 +32,6 @@ describe('pkgcloud/azure-v2/servers', function () { }); - after(function(done) { - if (!mock) { - return done(); - } - - //server.close(done); - }); }); From ee05c2d09cbb0446bf41a5c8243da09cc676fb45 Mon Sep 17 00:00:00 2001 From: morsh Date: Sun, 8 Jan 2017 11:18:43 +0200 Subject: [PATCH 17/41] destroying dependencies when destroying a linux VM --- .../azure-v2/compute/client/servers.js | 142 +++++++++++++++++- 1 file changed, 134 insertions(+), 8 deletions(-) diff --git a/lib/pkgcloud/azure-v2/compute/client/servers.js b/lib/pkgcloud/azure-v2/compute/client/servers.js index f8a186c28..013ba8dfd 100644 --- a/lib/pkgcloud/azure-v2/compute/client/servers.js +++ b/lib/pkgcloud/azure-v2/compute/client/servers.js @@ -4,7 +4,10 @@ * (C) Microsoft Open Technologies, Inc. * */ +var async = require('async'); var errs = require('errs'); + +var resourceManagement = require('azure-arm-resource'); var ComputeManagementClient = require('azure-arm-compute'); var constants = require('../../constants'); @@ -117,25 +120,148 @@ function createServer(options, callback) { /** * Destroy a server in Azure. * @param {Server|string} server Server id or a server + * @param {object} options optional | options for deletion + * @param {boolean} options.destroyDependencies should destroy + * dependencies (not including storage account) + * @param {boolean} options.destroyStorage should destroy storage account also * @param {function} callback cb(err, serverId). */ -function destroyServer(server, callback) { +function destroyServer(server, options, callback) { var self = this; var serverId = server && server.name || server; - self.login(err => { + if (typeof options === 'function' && typeof callback === 'undefined') { + callback = options; + options = {}; + } + + options = options || {}; + + var resourceClient; + var serverDetails; + var nicsIds; + var nicsDetails; + + var vnets; + var publicIPs; + + async.waterfall([ + (next) => { + self.login(next); + }, + (credentials, next) => { + self.getServer(serverId, next); + }, + (_server, next) => { + serverDetails = _server; + 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); + } + ], (err) => { if (err) { return callback(err); } - var client = new ComputeManagementClient(self.azure.credentials, self.config.subscriptionId); - client.virtualMachines.deleteMethod(self.config.resourceGroup, serverId, (err) => { - return err - ? callback(err) - : callback(null, serverId); - }); + if (!options.destroyDependencies) { + return callback(); + } + + async.waterfall([ + (next) => { + // Deleting the nics + 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, (nic, cb) => { + + nicsDetails = []; + async.waterfall([ + (nx) => { + resourceClient.resources.getById(nic.id, constants.MANAGEMENT_API_VERSION, nx); + }, + (nicDetails, request, response, nx) => { + nicsDetails.push(nicDetails); + resourceClient.resources.deleteById(nic.id, constants.MANAGEMENT_API_VERSION, nx); + } + ], cb); + + }, next); + }, + (next) => { + // Collecting public ips and vnet ids + publicIPs = []; + vnets = []; + nicsDetails.forEach((nic) => { + + var configs = nic && nic.properties && nic.properties.ipConfigurations || []; + + // Collecting + configs.forEach((config) => { + var props = config && config.properties || {}; + if (props.publicIPAddress && props.publicIPAddress.id) { + publicIPs.push(props.publicIPAddress.id); + } + + if (props.subnet && props.subnet.id && props.subnet.id.indexOf('/subnets/') >= 0) { + vnets.push(props.subnet.id.substr(0, props.subnet.id.indexOf('/subnets/'))); + } + }); + + }); + next(); + }, + (next) => { + // Deleting public ips + async.eachSeries(publicIPs, (publicIP, cb) => { + resourceClient.resources.deleteById(publicIP, constants.MANAGEMENT_API_VERSION, cb); + }, next); + }, + (next) => { + // Deleting vnets + async.eachSeries(vnets, (vnet, cb) => { + resourceClient.resources.deleteById(vnet, constants.MANAGEMENT_API_VERSION, cb); + }, 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; + + if (!storageUri || !storageUri.startsWith('https://')) { + return next(); + } + + var storageName = storageUri.substr('https://'.length); + storageName = storageName.substr(0, storageName.indexOf('.')); + + // Presuming the storage account is in the same resource group as the vm + resourceClient.resources.deleteMethod( + self.config.resourceGroup, + 'Microsoft.Storage', + 'storageAccounts', + storageName, + '', '2016-01-01', next); + } + ], callback); }); + } /** From 424d6b8929db8b4852aa708c87ec4251e6693f13 Mon Sep 17 00:00:00 2001 From: morsh Date: Sun, 8 Jan 2017 19:13:23 +0200 Subject: [PATCH 18/41] adding tests to containers for azure-v2 --- .vscode/launch.json | 22 ++---- .../compute/client/test-getServers.js | 41 ----------- test/azure-v2/compute/client/test-servers.js | 73 +++++++++++++++++++ test/azure-v2/mock-requests.js | 42 +++++++++++ test/azure-v2/storage/client/test-storage.js | 53 ++++++++++++++ test/fixtures/azure-v2/container-keys.json | 14 ++++ test/fixtures/azure-v2/container.json | 25 +++++++ test/fixtures/azure-v2/nic.json | 37 ++++++++++ test/fixtures/azure-v2/server.json | 4 +- 9 files changed, 253 insertions(+), 58 deletions(-) delete mode 100644 test/azure-v2/compute/client/test-getServers.js create mode 100644 test/azure-v2/compute/client/test-servers.js create mode 100644 test/azure-v2/storage/client/test-storage.js create mode 100644 test/fixtures/azure-v2/container-keys.json create mode 100644 test/fixtures/azure-v2/container.json create mode 100644 test/fixtures/azure-v2/nic.json diff --git a/.vscode/launch.json b/.vscode/launch.json index 7ac21d41b..abc9980d0 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -14,7 +14,7 @@ { "type": "node", "request": "launch", - "name": "Launch Azure V2 Destroy", + "name": "Azure V2 Compute - Destroy", "program": "${workspaceRoot}//examples//compute//azure-v2-destroy.private.js", "cwd": "${workspaceRoot}", "stopOnEntry": true @@ -22,7 +22,7 @@ { "type": "node", "request": "launch", - "name": "Launch Azure V2 Create", + "name": "Azure V2 Compute", "program": "${workspaceRoot}//examples//compute//azure-v2.private.js", "cwd": "${workspaceRoot}", "stopOnEntry": true @@ -30,21 +30,10 @@ { "type": "node", "request": "launch", - "name": "Launch Azure V2 Storage", + "name": "Azure V2 Storage", "program": "${workspaceRoot}//examples//storage//azure-v2.private.js", "cwd": "${workspaceRoot}", "stopOnEntry": true - }, - { - "type": "node", - "request": "launch", - "name": "Test Azure V2 Server", - "program": "${workspaceRoot}//test//azure-v2//compute//client//test-getServers.js", - "cwd": "${workspaceRoot}", - "stopOnEntry": true, - "env": { - "MOCK": "on" - } }, { // Name of configuration; appears in the launch configuration drop down menu. @@ -56,7 +45,10 @@ // Automatically stop program after launch. "stopOnEntry": false, // Command line arguments passed to the program. - "args": ["${workspaceRoot}//test//azure-v2//compute//client//test-getServers.js"], + "args": [ + "${workspaceRoot}//test//azure-v2//compute//client//test-servers.js", + "${workspaceRoot}//test//azure-v2//storage//client//test-storage.js" + ], // Workspace relative or absolute path to the working directory of the program being debugged. Default is the current workspace. "cwd": "${workspaceRoot}", // Workspace relative or absolute path to the runtime executable to be used. Default is the runtime executable on the PATH. diff --git a/test/azure-v2/compute/client/test-getServers.js b/test/azure-v2/compute/client/test-getServers.js deleted file mode 100644 index b849cbeee..000000000 --- a/test/azure-v2/compute/client/test-getServers.js +++ /dev/null @@ -1,41 +0,0 @@ -//TODO: Make this a vows test - -var mockRequests = require('../../mock-requests'); -var helpers = require('../../../helpers'); -var should = require('should'); -var mock = !!process.env.MOCK; - -client = helpers.createClient('azure-v2', 'compute'); - -describe('pkgcloud/azure-v2/servers', function () { - - it('Get a single server with RUNNING state', function(done) { - - mockRequests.prepare(); - client.getServers((err, servers) => { - should.not.exist(err); - should(servers).be.instanceOf(Array).and.have.lengthOf(1); - done(); - }); - - }); - - it('Get a single server with RUNNING state 2', function(done) { - - mockRequests.prepare(); - client.getServer('azure-vm-server', (err, server) => { - should.not.exist(err); - should.exist(server) - server.status.should.equal('RUNNING'); - done(); - }); - - }); - -}); - - - - - - diff --git a/test/azure-v2/compute/client/test-servers.js b/test/azure-v2/compute/client/test-servers.js new file mode 100644 index 000000000..dea47314a --- /dev/null +++ b/test/azure-v2/compute/client/test-servers.js @@ -0,0 +1,73 @@ +//TODO: Make this a vows test + +var mockRequests = require('../../mock-requests'); +var helpers = require('../../../helpers'); +var should = require('should'); +var mock = !!process.env.MOCK; + +var createParams = { + name: 'azure-vm-server', + flavor: 'DEFAULT', + username: 'username', + password: 'password', + + imagePublisher: "Canonical", + imageOffer: "UbuntuServer", + imageSku: "16.04.0-LTS", + imageVersion: "latest" +}; +var client = helpers.createClient('azure-v2', 'compute'); + +describe('pkgcloud/azure-v2/servers', function () { + + it('Get multiple servers', function(done) { + + mockRequests.prepare(); + client.getServers((err, servers) => { + should.not.exist(err); + should(servers).be.instanceOf(Array).and.have.lengthOf(1); + done(); + }); + + }); + + it('Get a single server with RUNNING state', function(done) { + + mockRequests.prepare(); + client.getServer('azure-vm-server', (err, server) => { + should.not.exist(err); + should.exist(server) + server.status.should.equal('RUNNING'); + done(); + }); + + }); + + + it('Creating a new server', function(done) { + + mockRequests.prepare(); + client.createServer(createParams, (err, server) => { + should.not.exist(err); + should.exist(server); + server.status.should.equal('RUNNING'); + done(); + }); + + }); + + it('Deleting a VM with dependencies', function (done) { + mockRequests.prepare(); + client.destroyServer(createParams, { destroyDependencies: true, destroyStorage: true }, (err, serverId) => { + should.not.exist(err); + done(); + }); + }) + +}); + + + + + + diff --git a/test/azure-v2/mock-requests.js b/test/azure-v2/mock-requests.js index 8ec54bb54..5d0ee29cb 100644 --- a/test/azure-v2/mock-requests.js +++ b/test/azure-v2/mock-requests.js @@ -33,6 +33,48 @@ function prepare() { .reply(200, loadFixture('servers.json')) .get(`/virtualMachines/azure-vm-server?$expand=instanceView&api-version=${apiVersion}`) .reply(200, loadFixture('server.json')) + .delete(`/virtualMachines/azure-vm-server?api-version=${apiVersion}`) + .reply(204, ''); + + // Nicks + //https://management.azure.com//subscriptions/subscriptionId/resourceGroups/resource-group/providers/Microsoft.Network/networkInterfaces/nicName?api-version=2016-03-30 + nock(`${azureManagementUri}/subscriptions/${config.subscriptionId}/resourceGroups/${config.resourceGroup}/providers/Microsoft.Network`) + .get(`/networkInterfaces/nicName?api-version=${apiVersion}`) + .reply(200, loadFixture('nic.json')) + .delete(`/networkInterfaces/nicName?api-version=${apiVersion}`) + .reply(204, ''); + + // Public IPs + nock(`${azureManagementUri}/subscriptions/${config.subscriptionId}/resourceGroups/${config.resourceGroup}/providers/Microsoft.Network`) + .delete(`/publicIPAddresses/publicIPName?api-version=${apiVersion}`) + .reply(204, ''); + + // VNET + nock(`${azureManagementUri}/subscriptions/${config.subscriptionId}/resourceGroups/${config.resourceGroup}/providers/Microsoft.Network`) + .delete(`/virtualNetworks/vnetName?api-version=${apiVersion}`) + .reply(204, ''); + + // Storage + // https://management.azure.com/subscriptions/azure-account-subscription-id/resourceGroups/resource-group/providers/Microsoft.Storage/storageAccounts/test-storage?api-version=2016-05-01 + nock(`${azureManagementUri}/subscriptions/${config.subscriptionId}/resourcegroups/${config.resourceGroup}/providers/microsoft.storage`) + .filteringPath((path) => { return path.toLowerCase(); }) + .get(`/storageaccounts/azurestorage?api-version=2016-05-01`) + .reply(200, loadFixture('container.json')) + .post(`/storageaccounts/azurestorage/listkeys?api-version=2016-05-01`) + .reply(200, loadFixture('container-keys.json')) + .delete(`/storageaccounts/azurestorage/?api-version=2016-01-01`) + .reply(204, ''); + + // url:"https://management.azure.com/subscriptions/73a4ea93-d914-424d-9e64-28adf397e8e3/resourceGroups/morshe-noobaa2/providers/Microsoft.Storage/storageAccounts/boobaavmstore3/listKeys?api-version=2016-05-01" + + // Deployments + nock(`${azureManagementUri}/subscriptions/${config.subscriptionId}/resourcegroups/${config.resourceGroup}/providers/microsoft.resources/deployments`) + .filteringPath((path) => { + path = path.toLowerCase() + return path.substr(0, path.indexOf('/deployments/pkgc-')) + '/deployments/pkgc-test'; + }) + .put('/pkgc-test') + .reply(200, {}); } module.exports = { diff --git a/test/azure-v2/storage/client/test-storage.js b/test/azure-v2/storage/client/test-storage.js new file mode 100644 index 000000000..c322641e6 --- /dev/null +++ b/test/azure-v2/storage/client/test-storage.js @@ -0,0 +1,53 @@ +//TODO: Make this a vows test + +var mockRequests = require('../../mock-requests'); +var helpers = require('../../../helpers'); +var should = require('should'); +var mock = !!process.env.MOCK; + +var client = helpers.createClient('azure-v2', 'storage'); + +describe('pkgcloud/azure-v2/storage', function () { + + it('Create container', function(done) { + + mockRequests.prepare(); + client.createContainer('azurestorage', (err, container) => { + should.not.exist(err); + should.exist(container); + should(container.name).be.exactly('azurestorage'); + done(); + }); + }); + + it('Get container', function(done) { + + mockRequests.prepare(); + client.getContainer('azurestorage', (err, container) => { + should.not.exist(err); + should.exist(container); + should(container.name).be.exactly('azurestorage'); + done(); + }); + }); + + // Todo: + // Find out if can test download file + // it('Get files in container', function(done) { + + // mockRequests.prepare(); + // client.getFiles('azurestorage', { container: 'container' }, (err, files) => { + // should.not.exist(err); + // should.exist(container); + // should(container.name).be.exactly('azurestorage'); + // done(); + // }); + // }); + +}); + + + + + + diff --git a/test/fixtures/azure-v2/container-keys.json b/test/fixtures/azure-v2/container-keys.json new file mode 100644 index 000000000..e2677ace7 --- /dev/null +++ b/test/fixtures/azure-v2/container-keys.json @@ -0,0 +1,14 @@ +{ + "keys": [ + { + "keyName": "key1", + "permissions":"Full", + "value":"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX==" + }, + { + "keyName": "key2", + "permissions":"Full", + "value":"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX==" + } + ] +} \ No newline at end of file diff --git a/test/fixtures/azure-v2/container.json b/test/fixtures/azure-v2/container.json new file mode 100644 index 000000000..bed276f20 --- /dev/null +++ b/test/fixtures/azure-v2/container.json @@ -0,0 +1,25 @@ +{ + "id": "/subscriptions/azure-account-subscription-id/resourceGroups/resource-group/providers/Microsoft.Storage/storageAccounts/azurestorage", + "kind": "Storage", + "location": "westeurope", + "name": "azurestorage", + "properties": { + "creationTime": "2017-01-08T16:29:06.2738884Z", + "primaryEndpoints": { + "blob": "https://azurestorage.blob.core.windows.net/", + "file": "https://azurestorage.file.core.windows.net/", + "queue": "https://azurestorage.queue.core.windows.net/", + "table": "https://azurestorage.table.core.windows.net/" + }, + "primaryLocation": "westeurope", + "provisioningState": "Succeeded", + "statusOfPrimary": "available" + }, + "sku": { + "name": "Standard_LRS", + "tier": "Standard" + }, + "tags": { + }, + "type": "Microsoft.Storage/storageAccounts" +} \ No newline at end of file diff --git a/test/fixtures/azure-v2/nic.json b/test/fixtures/azure-v2/nic.json new file mode 100644 index 000000000..3c2dbe0b5 --- /dev/null +++ b/test/fixtures/azure-v2/nic.json @@ -0,0 +1,37 @@ +{ + "name": "nickName", + "id": "/subscriptions/azure-account-subscription-id/resourceGroups/resource-group/providers/Microsoft.Network/networkInterfaces/nickName", + "etag": "W/\"00000000-0000-0000-0000-000000000000\"", + "location": "westeurope", + "properties": { + "provisioningState": "Succeeded", + "resourceGuid": "00000000-0000-0000-0000-000000000000", + "ipConfigurations": [ + { + "name": "ipconfig1", + "id": "subscriptions/azure-account-subscription-id/resourceGroups/resource-group/providers/Microsoft.Network/networkInterfaces/nickName/ipConfigurations/ipconfig1", + "etag": "W/\"00000000-0000-0000-0000-000000000000\"", + "properties": { + "provisioningState": "Succeeded", + "privateIPAddress": "10.0.0.4", + "privateIPAllocationMethod": "Dynamic", + "publicIPAddress": { + "id": "subscriptions/azure-account-subscription-id/resourceGroups/resource-group/providers/Microsoft.Network/publicIPAddresses/publicIPName" + }, + "subnet": { + "id": "subscriptions/azure-account-subscription-id/resourceGroups/resource-group/providers/Microsoft.Network/virtualNetworks/vnetName/subnets/Subnet" + }, + "primary": true, + "privateIPAddressVersion": "IPv4" + } + } + ], + "dnsSettings": { + "dnsServers": [], + "appliedDnsServers": [] + }, + "macAddress": "00-00-00-00-00-00", + "enableIPForwarding": false + }, + "type": "Microsoft.Network/networkInterfaces" +} \ No newline at end of file diff --git a/test/fixtures/azure-v2/server.json b/test/fixtures/azure-v2/server.json index b456dae63..d03b15405 100644 --- a/test/fixtures/azure-v2/server.json +++ b/test/fixtures/azure-v2/server.json @@ -1,5 +1,5 @@ { - "id": "/subscriptions/subscriptionId/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachines/vmName", + "id": "/subscriptions/azure-account-subscription-id/resourceGroups/resource-group/providers/Microsoft.Compute/virtualMachines/vmName", "name":"vmName", "type":"Microsoft.Compute/virtualMachines", "location":"somelocation", @@ -41,7 +41,7 @@ "networkProfile": { "networkInterfaces":[ { - "id":"/subscriptions/subscriptionId/resourceGroups/resource-group/providers/Microsoft.Network/networkInterfaces/nicName" + "id":"subscriptions/azure-account-subscription-id/resourceGroups/resource-group/providers/Microsoft.Network/networkInterfaces/nicName" } ] }, From 23986257b728361b4480d9d6d11a303ab647d39a Mon Sep 17 00:00:00 2001 From: morsh Date: Sun, 8 Jan 2017 19:18:52 +0200 Subject: [PATCH 19/41] travis review --- examples/compute/azure-v2.js | 10 +++++----- test/azure-v2/compute/client/test-servers.js | 15 +++++++-------- test/azure-v2/mock-requests.js | 3 +-- test/azure-v2/storage/client/test-storage.js | 1 - 4 files changed, 13 insertions(+), 16 deletions(-) diff --git a/examples/compute/azure-v2.js b/examples/compute/azure-v2.js index 331d94192..8350b4f3a 100644 --- a/examples/compute/azure-v2.js +++ b/examples/compute/azure-v2.js @@ -7,13 +7,13 @@ var options; // options = { provider: 'azure-v2', - subscriptionId: "{subscriptionId}", - resourceGroup: "{resourceGroup}", + subscriptionId: '{subscriptionId}', + resourceGroup: '{resourceGroup}', servicePrincipal: { - clientId: "{spClientId}", - secret: "{spSecret}", - domain: "{spDomain}" + clientId: '{spClientId}', + secret: '{spSecret}', + domain: '{spDomain}' } }; client = pkgcloud.compute.createClient(options); diff --git a/test/azure-v2/compute/client/test-servers.js b/test/azure-v2/compute/client/test-servers.js index dea47314a..b29347524 100644 --- a/test/azure-v2/compute/client/test-servers.js +++ b/test/azure-v2/compute/client/test-servers.js @@ -3,7 +3,6 @@ var mockRequests = require('../../mock-requests'); var helpers = require('../../../helpers'); var should = require('should'); -var mock = !!process.env.MOCK; var createParams = { name: 'azure-vm-server', @@ -11,10 +10,10 @@ var createParams = { username: 'username', password: 'password', - imagePublisher: "Canonical", - imageOffer: "UbuntuServer", - imageSku: "16.04.0-LTS", - imageVersion: "latest" + imagePublisher: 'Canonical', + imageOffer: 'UbuntuServer', + imageSku: '16.04.0-LTS', + imageVersion: 'latest' }; var client = helpers.createClient('azure-v2', 'compute'); @@ -36,7 +35,7 @@ describe('pkgcloud/azure-v2/servers', function () { mockRequests.prepare(); client.getServer('azure-vm-server', (err, server) => { should.not.exist(err); - should.exist(server) + should.exist(server); server.status.should.equal('RUNNING'); done(); }); @@ -58,11 +57,11 @@ describe('pkgcloud/azure-v2/servers', function () { it('Deleting a VM with dependencies', function (done) { mockRequests.prepare(); - client.destroyServer(createParams, { destroyDependencies: true, destroyStorage: true }, (err, serverId) => { + client.destroyServer(createParams, { destroyDependencies: true, destroyStorage: true }, (err) => { should.not.exist(err); done(); }); - }) + }); }); diff --git a/test/azure-v2/mock-requests.js b/test/azure-v2/mock-requests.js index 5d0ee29cb..b7968fbbb 100644 --- a/test/azure-v2/mock-requests.js +++ b/test/azure-v2/mock-requests.js @@ -4,7 +4,6 @@ var helpers = require('../helpers'); const azureAuthUri = 'https://login.microsoftonline.com'; const azureManagementUri = 'https://management.azure.com'; -const requestId = 'b67cc525-ecc5-4661-8fd6-fb3e57d724f5'; const apiVersion = '2016-03-30'; function loadFixture(name) { @@ -70,7 +69,7 @@ function prepare() { // Deployments nock(`${azureManagementUri}/subscriptions/${config.subscriptionId}/resourcegroups/${config.resourceGroup}/providers/microsoft.resources/deployments`) .filteringPath((path) => { - path = path.toLowerCase() + path = path.toLowerCase(); return path.substr(0, path.indexOf('/deployments/pkgc-')) + '/deployments/pkgc-test'; }) .put('/pkgc-test') diff --git a/test/azure-v2/storage/client/test-storage.js b/test/azure-v2/storage/client/test-storage.js index c322641e6..d67961696 100644 --- a/test/azure-v2/storage/client/test-storage.js +++ b/test/azure-v2/storage/client/test-storage.js @@ -3,7 +3,6 @@ var mockRequests = require('../../mock-requests'); var helpers = require('../../../helpers'); var should = require('should'); -var mock = !!process.env.MOCK; var client = helpers.createClient('azure-v2', 'storage'); From 32f957e02fe49503dc45fe88fd2fb66c97cbba92 Mon Sep 17 00:00:00 2001 From: morsh Date: Sun, 8 Jan 2017 19:29:06 +0200 Subject: [PATCH 20/41] removing launch.json --- .gitignore | 3 ++- .vscode/launch.json | 66 --------------------------------------------- 2 files changed, 2 insertions(+), 67 deletions(-) delete mode 100644 .vscode/launch.json diff --git a/.gitignore b/.gitignore index c6f240b2a..fb1890114 100644 --- a/.gitignore +++ b/.gitignore @@ -13,4 +13,5 @@ coverage.html .coveralls.yml pkgcloud.lcov* node-jscoverage -*.private.* \ No newline at end of file +*.private.* +*.vscode/launch.json \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index abc9980d0..000000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,66 +0,0 @@ -{ - // Use IntelliSense to learn about possible Node.js debug attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "type": "node", - "request": "launch", - "name": "Launch Program", - "program": "${workspaceRoot}\\lib\\pkgcloud", - "cwd": "${workspaceRoot}" - }, - { - "type": "node", - "request": "launch", - "name": "Azure V2 Compute - Destroy", - "program": "${workspaceRoot}//examples//compute//azure-v2-destroy.private.js", - "cwd": "${workspaceRoot}", - "stopOnEntry": true - }, - { - "type": "node", - "request": "launch", - "name": "Azure V2 Compute", - "program": "${workspaceRoot}//examples//compute//azure-v2.private.js", - "cwd": "${workspaceRoot}", - "stopOnEntry": true - }, - { - "type": "node", - "request": "launch", - "name": "Azure V2 Storage", - "program": "${workspaceRoot}//examples//storage//azure-v2.private.js", - "cwd": "${workspaceRoot}", - "stopOnEntry": true - }, - { - // Name of configuration; appears in the launch configuration drop down menu. - "name": "Run mocha", - // Type of configuration. Possible values: "node", "mono". - "type": "node", - // Workspace relative or absolute path to the program. - "program": "${workspaceRoot}//node_modules//mocha//bin//_mocha", - // Automatically stop program after launch. - "stopOnEntry": false, - // Command line arguments passed to the program. - "args": [ - "${workspaceRoot}//test//azure-v2//compute//client//test-servers.js", - "${workspaceRoot}//test//azure-v2//storage//client//test-storage.js" - ], - // Workspace relative or absolute path to the working directory of the program being debugged. Default is the current workspace. - "cwd": "${workspaceRoot}", - // Workspace relative or absolute path to the runtime executable to be used. Default is the runtime executable on the PATH. - "runtimeExecutable": null, - // Environment variables passed to the program. - "env": { "MOCK": "on" } - }, - { - "type": "node", - "request": "attach", - "name": "Attach to Process", - "port": 5858 - } - ] -} \ No newline at end of file From b37ed6c8d09e74894244b217ca001891ecadeaff Mon Sep 17 00:00:00 2001 From: morsh Date: Sun, 8 Jan 2017 19:34:01 +0200 Subject: [PATCH 21/41] updating examples --- examples/compute/azure-v2-destroy.js | 37 --------------- examples/compute/azure-v2.js | 35 ++++++-------- examples/storage/azure-v2.js | 70 ++++++++++++++++++++++++++++ 3 files changed, 85 insertions(+), 57 deletions(-) delete mode 100644 examples/compute/azure-v2-destroy.js create mode 100644 examples/storage/azure-v2.js diff --git a/examples/compute/azure-v2-destroy.js b/examples/compute/azure-v2-destroy.js deleted file mode 100644 index 978a6c11e..000000000 --- a/examples/compute/azure-v2-destroy.js +++ /dev/null @@ -1,37 +0,0 @@ -var pkgcloud = require('../../lib/pkgcloud'), - client, - options; - -// -// Create a pkgcloud compute instance -// -options = { - resourceGroup: '{resourceGroup}', - provider: 'azure-v2', - storageAccount: '{storeName}', - storageAccessKey: '{storeKey}', - subscriptionId: '{subscriptionId}', - spClientId: '{spClientId}', - spSecret: '{spSecret}', - spDomain: '{spDomain}', - spSubscriptionId: '{spSubscriptionId}' -}; -client = pkgcloud.compute.createClient(options); - -// -// Create a server. -// This may take several minutes. -// -options = { - name: 'ms-pkgc-vm-test', // name of the server -}; - -console.log('deleting server...'); - -client.destroyServer(options, function (err, server) { - if (err) { - console.log(err); - } else { - console.log('Started DELETE of VM: ', server); - } -}); diff --git a/examples/compute/azure-v2.js b/examples/compute/azure-v2.js index 8350b4f3a..aa5d88a3d 100644 --- a/examples/compute/azure-v2.js +++ b/examples/compute/azure-v2.js @@ -22,37 +22,32 @@ client = pkgcloud.compute.createClient(options); // Create a server. // This may take several minutes. // -options = { - // pkgcloud compute properties - name: 'ms-pkgc-vm-test', // name of the server - flavor: 'Standard_D1', // azure vm size - //image: '5112500ae3b842c8b9c604889f8753c3__OpenLogic-CentOS63DEC20121220', // OS Image to use - image: { - uri: 'https://{storename}.blob.core.windows.net/osdiks/ms-pkgc-test-os2.vhd', - OS: 'linux' - }, - nic: '/subscriptions/{subscriptionId}/resourceGroups/{resourceGroup}/providers/Microsoft.Network/networkInterfaces/{nicName}', +var createVMOfferOptions = { + name: 'ms-pkgc-vm-test', + flavor: 'Standard_D1', + username: 'pkgcloud', + password: 'Pkgcloud!!', + + storageOSDiskName: "osdisk", + storageDataDisk1VhdName: "datadisk1", - // Azure vm properties - location: 'West Europe', // Azure location for server - username: 'pkgcloud', // Username for server - password: 'Pkgcloud!!' // Password for server + imagePublisher: "Canonical", + imageOffer: "UbuntuServer", + imageSku: "16.04.0-LTS", + imageVersion: "latest" }; console.log('creating server...'); -client.createServer(options, function (err, server) { +client.createServer(createVMOfferOptions, function (err, server) { if (err) { console.log(err); } else { - // Wait for the server to reach the RUNNING state. - // This may take several minutes. - console.log('waiting for server RUNNING state...'); - server.setWait({ status: server.STATUS.running }, 10000, function (err, server) { + client.destroyServer(createVMFlavorOptions, { destroyDependencies: true, destroyStorage: true }, (err, serverId) => { if (err) { console.log(err); } else { - console.dir(server); + console.log('deleted successfully'); } }); } diff --git a/examples/storage/azure-v2.js b/examples/storage/azure-v2.js new file mode 100644 index 000000000..8fa1a4fdc --- /dev/null +++ b/examples/storage/azure-v2.js @@ -0,0 +1,70 @@ +var fs = require('fs'); +var path = require('path'); +var pkgcloud = require('../../lib/pkgcloud'); + +var client = pkgcloud.storage.createClient({ + provider: 'azure-v2', + subscriptionId: '{subscriptionId}', + resourceGroup: '{resourceGroup}', + + servicePrincipal: { + clientId: '{spClientId}', + secret: '{spSecret}', + domain: '{spDomain}' + } +}); + +// client.getFiles('storageacountname', null, (err, files) => { + +// var file = files[0]; +// client.getFile('storageacountname', file, null, (err, file) => { +// console.dir(file); +// }) +// }); + +client.getFiles('storageacountname', { container: 'container-name' }, (err, files) => { + + var file = files[0]; + var download = client.download({ + container: 'storageacountname', + storage: { container: 'container-name' }, + remote: 'file.name.to.download.ext', + local: path.join(__dirname, 'file.name.to.download.ext') + }, err => { + return err ? console.dir(err) : null; + }); + + download.on('error', function(err) { + console.error(err); + }); + + download.on('end', function(file) { + console.log('file write has ended'); + }); + + download.on('data', function(data) { + console.log(data && data.length); + }); +}); + +// client.createContainer('storageacountname', (err, container) => { +// console.log('created: ', container.toJSON()); + +// client.getContainer('storageacountname', (err, container) => { +// console.log('found: ', container.toJSON()); +// }); +// }); + +// client.getContainers((err, containers) => { +// if (err) { +// console.error(err); +// } + +// client.getContainer(containers[0], (err, container) => { +// console.log('found: ', container.toJSON()); +// }); + +// containers.forEach(function (container) { +// console.log(container.toJSON()); +// }); +// }); From 07cdf66bc64a17f6aa36edc564694cba5e3e2727 Mon Sep 17 00:00:00 2001 From: morsh Date: Wed, 11 Jan 2017 18:12:46 +0200 Subject: [PATCH 22/41] changing destroy server to enable different dependencies --- examples/compute/azure-v2.js | 7 +++++- .../azure-v2/compute/client/servers.js | 25 ++++++++++++++++--- test/azure-v2/compute/client/test-servers.js | 9 ++++--- test/configs/providers.json | 2 +- 4 files changed, 34 insertions(+), 9 deletions(-) diff --git a/examples/compute/azure-v2.js b/examples/compute/azure-v2.js index aa5d88a3d..b2adbf6e5 100644 --- a/examples/compute/azure-v2.js +++ b/examples/compute/azure-v2.js @@ -43,7 +43,12 @@ client.createServer(createVMOfferOptions, function (err, server) { if (err) { console.log(err); } else { - client.destroyServer(createVMFlavorOptions, { destroyDependencies: true, destroyStorage: true }, (err, serverId) => { + client.destroyServer(createVMFlavorOptions, { + destroyNics: true, + destroyPublicIP: true, + destroyVnet: true, + destroyStorage: true + }, (err, serverId) => { if (err) { console.log(err); } else { diff --git a/lib/pkgcloud/azure-v2/compute/client/servers.js b/lib/pkgcloud/azure-v2/compute/client/servers.js index 013ba8dfd..d8b4d1367 100644 --- a/lib/pkgcloud/azure-v2/compute/client/servers.js +++ b/lib/pkgcloud/azure-v2/compute/client/servers.js @@ -121,8 +121,9 @@ function createServer(options, callback) { * Destroy a server in Azure. * @param {Server|string} server Server id or a server * @param {object} options optional | options for deletion - * @param {boolean} options.destroyDependencies should destroy - * dependencies (not including storage account) + * @param {boolean} options.destroyNics should destroy nics also + * @param {boolean} options.destroyPublicIP should destroy public ip also + * @param {boolean} options.destroyVnet should destroy vnet also * @param {boolean} options.destroyStorage should destroy storage account also * @param {function} callback cb(err, serverId). */ @@ -168,7 +169,10 @@ function destroyServer(server, options, callback) { return callback(err); } - if (!options.destroyDependencies) { + if (!options.destroyNics && + !options.destroyPublicIP && + !options.destroyVnet && + !options.destroyStorage) { return callback(); } @@ -190,7 +194,10 @@ function destroyServer(server, options, callback) { }, (nicDetails, request, response, nx) => { nicsDetails.push(nicDetails); - resourceClient.resources.deleteById(nic.id, constants.MANAGEMENT_API_VERSION, nx); + + if (options.destroyNics) { + resourceClient.resources.deleteById(nic.id, constants.MANAGEMENT_API_VERSION, nx); + } } ], cb); @@ -220,12 +227,22 @@ function destroyServer(server, options, callback) { next(); }, (next) => { + + if (!options.destroyPublicIP) { + return next(); + } + // Deleting public ips async.eachSeries(publicIPs, (publicIP, cb) => { resourceClient.resources.deleteById(publicIP, constants.MANAGEMENT_API_VERSION, cb); }, next); }, (next) => { + + if (!options.destroyVnet) { + return next(); + } + // Deleting vnets async.eachSeries(vnets, (vnet, cb) => { resourceClient.resources.deleteById(vnet, constants.MANAGEMENT_API_VERSION, cb); diff --git a/test/azure-v2/compute/client/test-servers.js b/test/azure-v2/compute/client/test-servers.js index b29347524..b2ea9fd35 100644 --- a/test/azure-v2/compute/client/test-servers.js +++ b/test/azure-v2/compute/client/test-servers.js @@ -1,5 +1,3 @@ -//TODO: Make this a vows test - var mockRequests = require('../../mock-requests'); var helpers = require('../../../helpers'); var should = require('should'); @@ -57,7 +55,12 @@ describe('pkgcloud/azure-v2/servers', function () { it('Deleting a VM with dependencies', function (done) { mockRequests.prepare(); - client.destroyServer(createParams, { destroyDependencies: true, destroyStorage: true }, (err) => { + client.destroyServer(createParams, { + destroyNics: true, + destroyPublicIP: true, + destroyVnet: true, + destroyStorage: true + }, (err) => { should.not.exist(err); done(); }); diff --git a/test/configs/providers.json b/test/configs/providers.json index 96c4a7971..378e4b04a 100644 --- a/test/configs/providers.json +++ b/test/configs/providers.json @@ -1 +1 @@ -["rackspace", "openstack", "joyent", "amazon", "azure", "digitalocean", "hp", "google"] \ No newline at end of file +["rackspace", "openstack", "joyent", "amazon", "azure", "digitalocean", "hp", "google", "azure-v2"] \ No newline at end of file From 5fa39a75b1a8d0715dc600f47ae199ee3b85f207 Mon Sep 17 00:00:00 2001 From: morsh Date: Thu, 9 Feb 2017 21:39:30 +0200 Subject: [PATCH 23/41] adding remote extension for windows vms --- .../azure-v2/compute/client/servers.js | 113 +++++++++++++++++- lib/pkgcloud/azure-v2/compute/server.js | 1 + .../templates/arm-compute-from-image.json | 8 ++ .../azure-v2/templates/arm-compute.json | 8 ++ lib/pkgcloud/azure-v2/templates/index.js | 6 +- 5 files changed, 129 insertions(+), 7 deletions(-) diff --git a/lib/pkgcloud/azure-v2/compute/client/servers.js b/lib/pkgcloud/azure-v2/compute/client/servers.js index d8b4d1367..2a21e581e 100644 --- a/lib/pkgcloud/azure-v2/compute/client/servers.js +++ b/lib/pkgcloud/azure-v2/compute/client/servers.js @@ -59,10 +59,15 @@ function getServers(callback) { * @param {Server|String} server Server id or a server * @param {Function} callback cb(err, serverId). */ -function getServer(server, callback) { +function getServer(server, hostname, callback) { var self = this; var serverId = server instanceof self.models.Server ? server.name : server; + if (typeof hostname === 'function' && typeof callback === 'undefined') { + callback = hostname; + hostname = null; + } + self.login(err => { if (err) { @@ -74,9 +79,52 @@ function getServer(server, callback) { // This will ensure returning of instances running status var options = { expand: 'instanceView' }; client.virtualMachines.get(self.config.resourceGroup, serverId, options, (err, result) => { - return err - ? callback(err) - : callback(null, new self.models.Server(self, result)); + + if (err) { + return callback(err); + } + + // Get public dns url + 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, (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) { + 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, (err, publicIP) => { + if (err) { + return callback(err); + } + + if (!publicIP.properties.dnsSettings || !publicIP.properties.dnsSettings.fqdn) { + return callback(null, new self.models.Server(self, result)); + } + + result = result || {}; + result.hostname = publicIP.properties.dnsSettings.fqdn; + + return callback(null, new self.models.Server(self, result)); + }); + }); }); }); } @@ -110,10 +158,63 @@ function createServer(options, callback) { } var templateName = 'compute' + (options.imageSourceUri ? '-from-image' : ''); - self.deploy(templateName, options, (err) => { + 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 (options.osType === 'Windows') { + 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.4", + "type": "CustomScriptExtension", + "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": `powershell -ExecutionPolicy Unrestricted -file ConfigureWinRM.ps1 '*.${hostnameSuffix}'` + } + } + }, + (err, result) => { + return err + ? callback(err) + : self.getServer(options.name, hostname, callback); + } + ); + } + return err ? callback(err) : - self.getServer(options.name, callback); + self.getServer(options.name, hostname, callback); }); } diff --git a/lib/pkgcloud/azure-v2/compute/server.js b/lib/pkgcloud/azure-v2/compute/server.js index 2d0066055..99c0a1917 100644 --- a/lib/pkgcloud/azure-v2/compute/server.js +++ b/lib/pkgcloud/azure-v2/compute/server.js @@ -22,6 +22,7 @@ Server.prototype._setProperties = function (details) { this.id = details.id || ''; this.name = details.name || ''; this.location = details.location; + this.hostname = details.hostname || ''; //console.log('Status: ' + details.Status + ' RoleInstanceList: ' + roleInstance ? roleInstance.InstanceStatus : 'UNKNOWN'); diff --git a/lib/pkgcloud/azure-v2/templates/arm-compute-from-image.json b/lib/pkgcloud/azure-v2/templates/arm-compute-from-image.json index 49aa2169c..8e01b21bc 100644 --- a/lib/pkgcloud/azure-v2/templates/arm-compute-from-image.json +++ b/lib/pkgcloud/azure-v2/templates/arm-compute-from-image.json @@ -178,6 +178,14 @@ "sshCommand": { "type": "string", "value": "[concat('ssh ', parameters('username'), '@', reference(variables('publicIPAddressName')).dnsSettings.fqdn)]" + }, + "location": { + "type": "string", + "value": "[resourceGroup().location]" + }, + "vmID": { + "type": "string", + "value": "[resourceId('Microsoft.Compute/virtualMachines/', variables('vmName'))]" } } } \ 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 467e2524d..603f4ee19 100644 --- a/lib/pkgcloud/azure-v2/templates/arm-compute.json +++ b/lib/pkgcloud/azure-v2/templates/arm-compute.json @@ -182,6 +182,14 @@ "sshCommand": { "type": "string", "value": "[concat('ssh ', parameters('username'), '@', reference(variables('publicIPAddressName')).dnsSettings.fqdn)]" + }, + "location": { + "type": "string", + "value": "[resourceGroup().location]" + }, + "vmID": { + "type": "string", + "value": "[resourceId('Microsoft.Compute/virtualMachines/', variables('vmName'))]" } } } \ No newline at end of file diff --git a/lib/pkgcloud/azure-v2/templates/index.js b/lib/pkgcloud/azure-v2/templates/index.js index c4c768b8a..f1d323fe5 100644 --- a/lib/pkgcloud/azure-v2/templates/index.js +++ b/lib/pkgcloud/azure-v2/templates/index.js @@ -26,7 +26,11 @@ function deploy(templateName, options, callback) { mode: 'Incremental' } }; - Object.keys(options).forEach(key => parameters.properties.parameters[key] = { value: options[key] }); + Object.keys(options).forEach((key) => { + if (template.parameters[key]) { + parameters.properties.parameters[key] = { value: options[key] } + } + }); var deploymentName = 'pkgc-' + (new Date()).toISOString().replace(/\:|Z|\.|\-/g, '').replace(/T/g, '-'); var resourceClient = new resourceManagement.ResourceManagementClient(self.azure.credentials, self.config.subscriptionId); From 56072707552ccadbf62b69d96c1206f7cb82fa2c Mon Sep 17 00:00:00 2001 From: morsh Date: Tue, 14 Feb 2017 15:39:48 +0200 Subject: [PATCH 24/41] adding remove file functionality --- .../azure-v2/compute/client/servers.js | 4 ++- lib/pkgcloud/azure-v2/storage/client/files.js | 28 +++++++++++-------- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/lib/pkgcloud/azure-v2/compute/client/servers.js b/lib/pkgcloud/azure-v2/compute/client/servers.js index 2a21e581e..aa9732f59 100644 --- a/lib/pkgcloud/azure-v2/compute/client/servers.js +++ b/lib/pkgcloud/azure-v2/compute/client/servers.js @@ -377,7 +377,9 @@ function destroyServer(server, options, callback) { storageName, '', '2016-01-01', next); } - ], callback); + ], function (error) { + callback(error, serverDetails); + }); }); } diff --git a/lib/pkgcloud/azure-v2/storage/client/files.js b/lib/pkgcloud/azure-v2/storage/client/files.js index 471a71977..b52c37f6a 100644 --- a/lib/pkgcloud/azure-v2/storage/client/files.js +++ b/lib/pkgcloud/azure-v2/storage/client/files.js @@ -24,20 +24,24 @@ var constants = require('../../constants'); * @param {String|object} file the file or fileName to delete * @param callback */ -exports.TODO_removeFile = function (container, file, callback) { - var containerName = container instanceof this.models.Container ? container.name : container, - fileName = file instanceof this.models.File ? file.name : file; +exports.removeFile = function (container, file, options, callback) { + options = options || {}; + var containerName = container instanceof this.models.Container ? container.name : container; + var fileName = file instanceof this.models.File ? file.name : file; + var azureContainerName = options.storage && options.storage.container || constants.DEFAULT_STORAGE_CONTAINER; - this._request({ - method: 'DELETE', - container: containerName, - path: fileName - }, function(err) { - return err - ? callback(err) - : callback(null, true); + this.getBlobService(azureContainerName, containerName, (err, blobService) => { + + if (err) { + return callback(err); } - ); + + blobService.deleteBlob(azureContainerName, fileName, function (err) { + return err ? + callback(err) : + callback(null, true); + }); + }); }; /** From 227467c7ab603508f204e892dbf268969fc2be1f Mon Sep 17 00:00:00 2001 From: morsh Date: Tue, 14 Feb 2017 16:16:11 +0200 Subject: [PATCH 25/41] updating relative pkgcloud path --- lib/pkgcloud/azure/utils/azureApi.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pkgcloud/azure/utils/azureApi.js b/lib/pkgcloud/azure/utils/azureApi.js index 8e3eb1adf..56285f9c7 100644 --- a/lib/pkgcloud/azure/utils/azureApi.js +++ b/lib/pkgcloud/azure/utils/azureApi.js @@ -20,7 +20,7 @@ var _ = require('lodash'); var errs = require('errs'); var URL = require('url'); var cert = require('../utils/cert'); -var pkgcloud = require('../../../../../pkgcloud'); +var pkgcloud = require('../../../../lib/pkgcloud'); exports.MANAGEMENT_API_VERSION = '2012-03-01'; exports.MANAGEMENT_ENDPOINT = 'management.core.windows.net'; From 8de41e918d023d3d0343d057ccb51ee4a31f5b5c Mon Sep 17 00:00:00 2001 From: morsh Date: Wed, 15 Feb 2017 14:09:18 +0200 Subject: [PATCH 26/41] adding dynamic data disk creation --- examples/compute/azure-v2.js | 2 +- .../azure-v2/compute/client/servers.js | 114 ++++++++++-------- .../templates/arm-compute-from-image.json | 16 +-- .../azure-v2/templates/arm-compute.json | 16 +-- lib/pkgcloud/azure-v2/templates/index.js | 8 +- package.json | 4 +- 6 files changed, 82 insertions(+), 78 deletions(-) diff --git a/examples/compute/azure-v2.js b/examples/compute/azure-v2.js index b2adbf6e5..0cd08cc59 100644 --- a/examples/compute/azure-v2.js +++ b/examples/compute/azure-v2.js @@ -29,7 +29,7 @@ var createVMOfferOptions = { password: 'Pkgcloud!!', storageOSDiskName: "osdisk", - storageDataDisk1VhdName: "datadisk1", + storageDataDiskNames: [ "datadisk1" ], imagePublisher: "Canonical", imageOffer: "UbuntuServer", diff --git a/lib/pkgcloud/azure-v2/compute/client/servers.js b/lib/pkgcloud/azure-v2/compute/client/servers.js index aa9732f59..d5369d469 100644 --- a/lib/pkgcloud/azure-v2/compute/client/servers.js +++ b/lib/pkgcloud/azure-v2/compute/client/servers.js @@ -6,6 +6,7 @@ */ var async = require('async'); var errs = require('errs'); +var _ = require('lodash'); var resourceManagement = require('azure-arm-resource'); var ComputeManagementClient = require('azure-arm-compute'); @@ -157,67 +158,84 @@ function createServer(options, callback) { ); } - 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 || {}))); + 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" + }); + }); } + // If this is a windows machine, add an extension that enables remote connection via WinRM if (options.osType === 'Windows') { - 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.4", - "type": "CustomScriptExtension", - "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": `powershell -ExecutionPolicy Unrestricted -file ConfigureWinRM.ps1 '*.${hostnameSuffix}'` - } + 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'))]" } - }, - (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, (err, result) => { return err ? callback(err) : self.getServer(options.name, hostname, callback); }); } +function addDataDiskToVM(vm, size, storageAccountName, callback) { + console.log('Adding DataDisk to Virtual Machine: ' + vm); + this.computeClient.virtualMachines.get(this.resourceGroupName, vm, function (error, machine_info){ + + if (!machine_info.storageProfile.dataDisks) { + machine_info.storageProfile.dataDisks = []; + } + var disk_number = machine_info.storageProfile.dataDisks.length + 1; + machine_info.storageProfile.dataDisks.push({ + name: 'dataDisk' + disk_number, + diskSizeGB: size, + lun: disk_number - 1, + vhd: { + uri: 'https://' + storageAccountName + '.blob.core.windows.net/datadisks/' + vm + '-data' + disk_number + '.vhd' + }, + createOption: 'Empty' + }); + return P.fromCallback(callback => this.computeClient.virtualMachines.createOrUpdate(this.resourceGroupName, + vm, machine_info, callback)); + }) +} + /** * Destroy a server in Azure. * @param {Server|string} server Server id or a server diff --git a/lib/pkgcloud/azure-v2/templates/arm-compute-from-image.json b/lib/pkgcloud/azure-v2/templates/arm-compute-from-image.json index 8e01b21bc..4c2ce5743 100644 --- a/lib/pkgcloud/azure-v2/templates/arm-compute-from-image.json +++ b/lib/pkgcloud/azure-v2/templates/arm-compute-from-image.json @@ -14,7 +14,6 @@ "storageAccountType": { "type": "string", "defaultValue": "Standard_LRS" }, "storageContainerName": { "type": "string", "defaultValue": "vhds" }, "storageOSDiskName": { "type": "string", "defaultValue": "osdisk" }, - "storageDataDisk1VhdName": { "type": "string", "defaultValue": "datadisk1" }, "publicIPAddressName": { "type": "string", "defaultValue": "_NONE_" }, "publicIPAddressType": { "type": "string", "defaultValue": "Dynamic" }, @@ -38,7 +37,8 @@ "nicName": "[replace(parameters('nicName'), '_NONE_', concat(variables('vmName'), '-nic'))]", "vnetID": "[resourceId('Microsoft.Network/virtualNetworks',variables('vnetName'))]", - "subnetRef": "[concat(variables('vnetID'),'/subnets/',parameters('vnetSubnetName'))]" + "subnetRef": "[concat(variables('vnetID'),'/subnets/',parameters('vnetSubnetName'))]", + "hostDNSNameScriptArgument": "[concat('*.',resourceGroup().location,'.cloudapp.azure.com')]" }, "resources": [ { @@ -142,17 +142,7 @@ "uri": "[concat(reference(concat('Microsoft.Storage/storageAccounts/', variables('storageAccountName')), '2016-01-01').primaryEndpoints.blob, parameters('storageContainerName'),'/',parameters('storageOSDiskName'),'.vhd')]" } }, - "dataDisks": [ - { - "name": "datadisk1", - "diskSizeGB": "100", - "lun": 0, - "vhd": { - "uri": "[concat(reference(concat('Microsoft.Storage/storageAccounts/', variables('storageAccountName')), '2016-01-01').primaryEndpoints.blob, parameters('storageContainerName'),'/',parameters('storageDataDisk1VhdName'),'.vhd')]" - }, - "createOption": "Empty" - } - ] + "dataDisks": [] }, "networkProfile": { "networkInterfaces": [ diff --git a/lib/pkgcloud/azure-v2/templates/arm-compute.json b/lib/pkgcloud/azure-v2/templates/arm-compute.json index 603f4ee19..990ae8c9a 100644 --- a/lib/pkgcloud/azure-v2/templates/arm-compute.json +++ b/lib/pkgcloud/azure-v2/templates/arm-compute.json @@ -11,7 +11,6 @@ "storageAccountType": { "type": "string", "defaultValue": "Standard_LRS" }, "storageContainerName": { "type": "string", "defaultValue": "vhds" }, "storageOSDiskName": { "type": "string", "defaultValue": "osdisk" }, - "storageDataDisk1VhdName": { "type": "string", "defaultValue": "datadisk1" }, "publicIPAddressName": { "type": "string", "defaultValue": "_NONE_" }, "publicIPAddressType": { "type": "string", "defaultValue": "Dynamic" }, @@ -40,7 +39,8 @@ "nicName": "[replace(parameters('nicName'), '_NONE_', concat(variables('vmName'), '-nic'))]", "vnetID": "[resourceId('Microsoft.Network/virtualNetworks',variables('vnetName'))]", - "subnetRef": "[concat(variables('vnetID'),'/subnets/',parameters('vnetSubnetName'))]" + "subnetRef": "[concat(variables('vnetID'),'/subnets/',parameters('vnetSubnetName'))]", + "hostDNSNameScriptArgument": "[concat('*.',resourceGroup().location,'.cloudapp.azure.com')]" }, "resources": [ { @@ -146,17 +146,7 @@ "caching": "ReadWrite", "createOption": "FromImage" }, - "dataDisks": [ - { - "name": "datadisk1", - "diskSizeGB": "100", - "lun": 0, - "vhd": { - "uri": "[concat(reference(concat('Microsoft.Storage/storageAccounts/', variables('storageAccountName')), '2016-01-01').primaryEndpoints.blob, parameters('storageContainerName'),'/',parameters('storageDataDisk1VhdName'),'.vhd')]" - }, - "createOption": "Empty" - } - ] + "dataDisks": [] }, "networkProfile": { "networkInterfaces": [ diff --git a/lib/pkgcloud/azure-v2/templates/index.js b/lib/pkgcloud/azure-v2/templates/index.js index f1d323fe5..230ce5007 100644 --- a/lib/pkgcloud/azure-v2/templates/index.js +++ b/lib/pkgcloud/azure-v2/templates/index.js @@ -9,9 +9,14 @@ function resolve(templateId) { return JSON.parse(contents); } -function deploy(templateName, options, callback) { +function deploy(templateName, options, templateProcess, callback) { var self = this; + if (templateProcess && !callback) { + callback = templateProcess; + templateProcess = function (template) { return template; }; + } + self.login(err => { if (err) { @@ -19,6 +24,7 @@ function deploy(templateName, options, callback) { } var template = resolve(templateName); + template = templateProcess(template); var parameters = { properties: { template: template, diff --git a/package.json b/package.json index 3e3e8de23..bbcc67a40 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { - "name": "pkgcloud", + "name": "pkgcloud-with-arm", "description": "An infrastructure-as-a-service agnostic cloud library for node.js", - "version": "1.4.0", + "version": "1.4.3", "author": "Charlie Robbins ", "contributors": [ { From a2ce4f021bec885623459a7a7d292d0ffe01cdd2 Mon Sep 17 00:00:00 2001 From: morsh Date: Wed, 15 Feb 2017 14:14:12 +0200 Subject: [PATCH 27/41] reverting package to right name/version --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index bbcc67a40..3e3e8de23 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { - "name": "pkgcloud-with-arm", + "name": "pkgcloud", "description": "An infrastructure-as-a-service agnostic cloud library for node.js", - "version": "1.4.3", + "version": "1.4.0", "author": "Charlie Robbins ", "contributors": [ { From 63f27a519a32957d26310d8b18556356fcf1deca Mon Sep 17 00:00:00 2001 From: morsh Date: Wed, 15 Feb 2017 15:46:01 +0200 Subject: [PATCH 28/41] fixing jshint #1 --- examples/compute/azure-v2.js | 12 +-- examples/storage/azure-v2.js | 6 +- .../azure-v2/compute/client/servers.js | 79 ++++++------------- 3 files changed, 36 insertions(+), 61 deletions(-) diff --git a/examples/compute/azure-v2.js b/examples/compute/azure-v2.js index 0cd08cc59..899544d18 100644 --- a/examples/compute/azure-v2.js +++ b/examples/compute/azure-v2.js @@ -28,13 +28,13 @@ var createVMOfferOptions = { username: 'pkgcloud', password: 'Pkgcloud!!', - storageOSDiskName: "osdisk", - storageDataDiskNames: [ "datadisk1" ], + storageOSDiskName: 'osdisk', + storageDataDiskNames: [ 'datadisk1' ], - imagePublisher: "Canonical", - imageOffer: "UbuntuServer", - imageSku: "16.04.0-LTS", - imageVersion: "latest" + imagePublisher: 'Canonical', + imageOffer: 'UbuntuServer', + imageSku: '16.04.0-LTS', + imageVersion: 'latest' }; console.log('creating server...'); diff --git a/examples/storage/azure-v2.js b/examples/storage/azure-v2.js index 8fa1a4fdc..61d9edc7d 100644 --- a/examples/storage/azure-v2.js +++ b/examples/storage/azure-v2.js @@ -1,4 +1,3 @@ -var fs = require('fs'); var path = require('path'); var pkgcloud = require('../../lib/pkgcloud'); @@ -25,6 +24,8 @@ var client = pkgcloud.storage.createClient({ client.getFiles('storageacountname', { container: 'container-name' }, (err, files) => { var file = files[0]; + console.dir(file); + var download = client.download({ container: 'storageacountname', storage: { container: 'container-name' }, @@ -39,7 +40,8 @@ client.getFiles('storageacountname', { container: 'container-name' }, (err, file }); download.on('end', function(file) { - console.log('file write has ended'); + console.log('file write has ended:'); + console.dir(file); }); download.on('data', function(data) { diff --git a/lib/pkgcloud/azure-v2/compute/client/servers.js b/lib/pkgcloud/azure-v2/compute/client/servers.js index d5369d469..7308ddaf9 100644 --- a/lib/pkgcloud/azure-v2/compute/client/servers.js +++ b/lib/pkgcloud/azure-v2/compute/client/servers.js @@ -60,15 +60,10 @@ function getServers(callback) { * @param {Server|String} server Server id or a server * @param {Function} callback cb(err, serverId). */ -function getServer(server, hostname, callback) { +function getServer(server, callback) { var self = this; var serverId = server instanceof self.models.Server ? server.name : server; - if (typeof hostname === 'function' && typeof callback === 'undefined') { - callback = hostname; - hostname = null; - } - self.login(err => { if (err) { @@ -160,19 +155,19 @@ function createServer(options, callback) { var adjustVMTemplate = function (template) { - var vmIndex = _.findIndex(template.resources, { "type": "Microsoft.Compute/virtualMachines" }); + 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')]" + '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" + 'createOption': 'Empty' }); }); } @@ -180,24 +175,24 @@ function createServer(options, callback) { // 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'))]" + '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" + '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'))]" + 'commandToExecute': '[concat(\'powershell -ExecutionPolicy Unrestricted -file ConfigureWinRM.ps1 \',variables(\'hostDNSNameScriptArgument\'))]' } } }]; @@ -207,35 +202,13 @@ function createServer(options, callback) { }; var templateName = 'compute' + (options.imageSourceUri ? '-from-image' : ''); - self.deploy(templateName, options, adjustVMTemplate, (err, result) => { + self.deploy(templateName, options, adjustVMTemplate, function (err) { return err ? callback(err) : - self.getServer(options.name, hostname, callback); + self.getServer(options.name, callback); }); } -function addDataDiskToVM(vm, size, storageAccountName, callback) { - console.log('Adding DataDisk to Virtual Machine: ' + vm); - this.computeClient.virtualMachines.get(this.resourceGroupName, vm, function (error, machine_info){ - - if (!machine_info.storageProfile.dataDisks) { - machine_info.storageProfile.dataDisks = []; - } - var disk_number = machine_info.storageProfile.dataDisks.length + 1; - machine_info.storageProfile.dataDisks.push({ - name: 'dataDisk' + disk_number, - diskSizeGB: size, - lun: disk_number - 1, - vhd: { - uri: 'https://' + storageAccountName + '.blob.core.windows.net/datadisks/' + vm + '-data' + disk_number + '.vhd' - }, - createOption: 'Empty' - }); - return P.fromCallback(callback => this.computeClient.virtualMachines.createOrUpdate(this.resourceGroupName, - vm, machine_info, callback)); - }) -} - /** * Destroy a server in Azure. * @param {Server|string} server Server id or a server From e030cd3bda2b5bf1a9e4ff13749993235556b503 Mon Sep 17 00:00:00 2001 From: morsh Date: Wed, 15 Feb 2017 15:51:20 +0200 Subject: [PATCH 29/41] jshint fixes #2 --- examples/compute/azure-v2.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/examples/compute/azure-v2.js b/examples/compute/azure-v2.js index 899544d18..16d69df6a 100644 --- a/examples/compute/azure-v2.js +++ b/examples/compute/azure-v2.js @@ -40,19 +40,24 @@ var createVMOfferOptions = { console.log('creating server...'); client.createServer(createVMOfferOptions, function (err, server) { + + console.log('servre created successfully:'); + console.dir(server); + if (err) { console.log(err); } else { - client.destroyServer(createVMFlavorOptions, { + client.destroyServer(createVMOfferOptions, { destroyNics: true, destroyPublicIP: true, destroyVnet: true, destroyStorage: true - }, (err, serverId) => { + }, (err, server) => { if (err) { console.log(err); } else { console.log('deleted successfully'); + console.dir(server); } }); } From 3113e82085a139dac12fa24cff9fe7ff5ec53c5c Mon Sep 17 00:00:00 2001 From: morsh Date: Wed, 15 Feb 2017 15:55:35 +0200 Subject: [PATCH 30/41] jshint fix #3 --- lib/pkgcloud/azure-v2/templates/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pkgcloud/azure-v2/templates/index.js b/lib/pkgcloud/azure-v2/templates/index.js index 230ce5007..e7ab0eace 100644 --- a/lib/pkgcloud/azure-v2/templates/index.js +++ b/lib/pkgcloud/azure-v2/templates/index.js @@ -34,7 +34,7 @@ function deploy(templateName, options, templateProcess, callback) { }; Object.keys(options).forEach((key) => { if (template.parameters[key]) { - parameters.properties.parameters[key] = { value: options[key] } + parameters.properties.parameters[key] = { value: options[key] }; } }); From 1e44b67b06c6c8de0d57e3a8cca8394f6387e968 Mon Sep 17 00:00:00 2001 From: morsh Date: Wed, 15 Feb 2017 17:42:14 +0200 Subject: [PATCH 31/41] removing all arrow functions --- examples/compute/azure-v2.js | 2 +- examples/storage/azure-v2.js | 16 ++--- lib/pkgcloud/azure-v2/azure-api.js | 8 ++- .../azure-v2/compute/client/flavors.js | 10 ++-- .../azure-v2/compute/client/images.js | 12 ++-- .../azure-v2/compute/client/servers.js | 58 ++++++++++--------- lib/pkgcloud/azure-v2/compute/server.js | 8 ++- .../azure-v2/storage/client/containers.js | 24 ++++---- lib/pkgcloud/azure-v2/storage/client/files.js | 24 ++++---- lib/pkgcloud/azure-v2/templates/index.js | 6 +- package.json | 4 +- test/azure-v2/compute/client/test-servers.js | 8 +-- test/azure-v2/mock-requests.js | 4 +- test/azure-v2/storage/client/test-storage.js | 6 +- 14 files changed, 103 insertions(+), 87 deletions(-) diff --git a/examples/compute/azure-v2.js b/examples/compute/azure-v2.js index 16d69df6a..0150d9ea0 100644 --- a/examples/compute/azure-v2.js +++ b/examples/compute/azure-v2.js @@ -52,7 +52,7 @@ client.createServer(createVMOfferOptions, function (err, server) { destroyPublicIP: true, destroyVnet: true, destroyStorage: true - }, (err, server) => { + }, function (err, server) { if (err) { console.log(err); } else { diff --git a/examples/storage/azure-v2.js b/examples/storage/azure-v2.js index 61d9edc7d..65d5a6a93 100644 --- a/examples/storage/azure-v2.js +++ b/examples/storage/azure-v2.js @@ -13,15 +13,15 @@ var client = pkgcloud.storage.createClient({ } }); -// client.getFiles('storageacountname', null, (err, files) => { +// client.getFiles('storageacountname', null, function (err, files) (function (err) { // var file = files[0]; -// client.getFile('storageacountname', file, null, (err, file) => { +// client.getFile('storageacountname', file, null, function (err, file) (function (err) { // console.dir(file); // }) // }); -client.getFiles('storageacountname', { container: 'container-name' }, (err, files) => { +client.getFiles('storageacountname', { container: 'container-name' }, function (err, files) (function (err) { var file = files[0]; console.dir(file); @@ -31,7 +31,7 @@ client.getFiles('storageacountname', { container: 'container-name' }, (err, file storage: { container: 'container-name' }, remote: 'file.name.to.download.ext', local: path.join(__dirname, 'file.name.to.download.ext') - }, err => { + }, function (err) { return err ? console.dir(err) : null; }); @@ -49,20 +49,20 @@ client.getFiles('storageacountname', { container: 'container-name' }, (err, file }); }); -// client.createContainer('storageacountname', (err, container) => { +// client.createContainer('storageacountname', function (err, container) { // console.log('created: ', container.toJSON()); -// client.getContainer('storageacountname', (err, container) => { +// client.getContainer('storageacountname', function (err, container) { // console.log('found: ', container.toJSON()); // }); // }); -// client.getContainers((err, containers) => { +// client.getContainers(function (err, containers) { // if (err) { // console.error(err); // } -// client.getContainer(containers[0], (err, container) => { +// client.getContainer(containers[0], function (err, container) { // console.log('found: ', container.toJSON()); // }); diff --git a/lib/pkgcloud/azure-v2/azure-api.js b/lib/pkgcloud/azure-v2/azure-api.js index a62976acc..f39f595d7 100644 --- a/lib/pkgcloud/azure-v2/azure-api.js +++ b/lib/pkgcloud/azure-v2/azure-api.js @@ -42,7 +42,7 @@ function login(setupLocation, callback) { servicePrincipal.clientId, servicePrincipal.secret, servicePrincipal.domain, - (err, credentials) => { + function (err, credentials) { if (err) { errs.handle( @@ -58,7 +58,9 @@ function login(setupLocation, callback) { self.azure.lastRefresh = new Date(); if (setupLocation) { - return self.setup(credentials, err => callback(err, credentials)); + return self.setup(credentials, function (err) { + return callback(err, credentials); + }); } else { return callback(null, credentials); } @@ -82,7 +84,7 @@ function setupLocation(credentials, callback) { if (self.config.resourceGroup) { var resourceClient = new resourceManagement.ResourceManagementClient(credentials, self.config.subscriptionId); - resourceClient.resourceGroups.get(self.config.resourceGroup, (err, result) => { + resourceClient.resourceGroups.get(self.config.resourceGroup, function (err, result) { if (err) { return callback(err); diff --git a/lib/pkgcloud/azure-v2/compute/client/flavors.js b/lib/pkgcloud/azure-v2/compute/client/flavors.js index 39db70aeb..85d6a911c 100644 --- a/lib/pkgcloud/azure-v2/compute/client/flavors.js +++ b/lib/pkgcloud/azure-v2/compute/client/flavors.js @@ -16,17 +16,19 @@ var ComputeManagementClient = require('azure-arm-compute'); function getFlavors(callback) { var self = this; - self.login(true, err => { + self.login(true, function (err) { if (err) { return callback(err); } var client = new ComputeManagementClient(self.azure.credentials, self.config.subscriptionId); - client.virtualMachineSizes.list(self.azure.location, (err, results) => { + client.virtualMachineSizes.list(self.azure.location, function (err, results) { return err ? callback(err) - : callback(null, results.map(res => new self.models.Flavor(self, res))); + : callback(null, results.map(function (res) { + return new self.models.Flavor(self, res); + })); }); }); } @@ -45,7 +47,7 @@ function getFlavor(flavor, callback) { return callback(null, flavor); } - self.getFlavors((err, flavors) => { + self.getFlavors(function (err, flavors) { return err ? callback(err) : callback(null, _.find(flavors, { id: flavorId })); diff --git a/lib/pkgcloud/azure-v2/compute/client/images.js b/lib/pkgcloud/azure-v2/compute/client/images.js index 7c418116f..5c6e404ab 100644 --- a/lib/pkgcloud/azure-v2/compute/client/images.js +++ b/lib/pkgcloud/azure-v2/compute/client/images.js @@ -30,17 +30,19 @@ exports.getImages = function getImages(options, callback) { var offer = options.offer || constants.DEFAULT_VM_IMAGE.OFFER; var sku = options.sku || constants.DEFAULT_VM_IMAGE.SKU; - self.login(true, err => { + self.login(true, function (err) { if (err) { return callback(err); } var client = new ComputeManagementClient(self.azure.credentials, self.config.subscriptionId); - client.virtualMachineImages.list(self.azure.location, publisher, offer, sku, (err, results) => { + client.virtualMachineImages.list(self.azure.location, publisher, offer, sku, function (err, results) { return err ? callback(err) - : callback(null, results.map(res => new self.models.Image(self, res, publisher, offer, sku))); + : callback(null, results.map(function (res) { + return new self.models.Image(self, res, publisher, offer, sku); + })); }); }); }; @@ -68,14 +70,14 @@ exports.getImage = function getImage(image , options, callback) { var offer = options.offer || constants.DEFAULT_VM_IMAGE.OFFER; var sku = options.sku || constants.DEFAULT_VM_IMAGE.SKU; - self.login(true, err => { + self.login(true, function (err) { if (err) { return callback(err); } var client = new ComputeManagementClient(self.azure.credentials, self.config.subscriptionId); - client.virtualMachineImages.get(self.azure.location, publisher, offer, sku, version, (err, result) => { + client.virtualMachineImages.get(self.azure.location, publisher, offer, sku, version, function (err, result) { return err ? callback(err) : callback(null, new self.models.Image(self, result, publisher, offer, sku, version)); diff --git a/lib/pkgcloud/azure-v2/compute/client/servers.js b/lib/pkgcloud/azure-v2/compute/client/servers.js index 7308ddaf9..e47d4e7ac 100644 --- a/lib/pkgcloud/azure-v2/compute/client/servers.js +++ b/lib/pkgcloud/azure-v2/compute/client/servers.js @@ -40,17 +40,19 @@ function getLimits(callback) { function getServers(callback) { var self = this; - self.login(err => { + self.login(function (err) { if (err) { return callback(err); } var client = new ComputeManagementClient(self.azure.credentials, self.config.subscriptionId); - client.virtualMachines.list(self.config.resourceGroup, (err, results) => { + client.virtualMachines.list(self.config.resourceGroup, function (err, results) { return err ? callback(err) - : callback(null, results.map(res => new self.models.Server(self, res))); + : callback(null, results.map(function (res) { + return new self.models.Server(self, res); + })); }); }); } @@ -64,7 +66,7 @@ function getServer(server, callback) { var self = this; var serverId = server instanceof self.models.Server ? server.name : server; - self.login(err => { + self.login(function (err) { if (err) { return callback(err); @@ -74,7 +76,7 @@ function getServer(server, callback) { // This will ensure returning of instances running status var options = { expand: 'instanceView' }; - client.virtualMachines.get(self.config.resourceGroup, serverId, options, (err, result) => { + client.virtualMachines.get(self.config.resourceGroup, serverId, options, function (err, result) { if (err) { return callback(err); @@ -90,7 +92,7 @@ function getServer(server, callback) { 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, (err, networkInterface) => { + resourceClient.resources.getById(networkInterfaceId, constants.DEFAULT_API_VERSION, function (err, networkInterface) { if (err) { return callback(err); @@ -106,7 +108,7 @@ function getServer(server, callback) { } var publicIPID = networkInterface.properties.ipConfigurations[0].properties.publicIPAddress.id; - resourceClient.resources.getById(publicIPID, constants.DEFAULT_API_VERSION, (err, publicIP) => { + resourceClient.resources.getById(publicIPID, constants.DEFAULT_API_VERSION, function (err, publicIP) { if (err) { return callback(err); } @@ -239,23 +241,23 @@ function destroyServer(server, options, callback) { var publicIPs; async.waterfall([ - (next) => { + function (next) { self.login(next); }, - (credentials, next) => { + function (credentials, next) { self.getServer(serverId, next); }, - (_server, next) => { + function (_server, next) { serverDetails = _server; next(); }, - (next) => { + function (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); } - ], (err) => { + ], function (err) { if (err) { return callback(err); @@ -269,7 +271,7 @@ function destroyServer(server, options, callback) { } async.waterfall([ - (next) => { + function (next) { // Deleting the nics nicsIds = serverDetails && serverDetails.azure && @@ -277,14 +279,14 @@ function destroyServer(server, options, callback) { serverDetails.azure.networkProfile.networkInterfaces || []; // Go over all nics, get their details and go on to delete them - async.eachSeries(nicsIds, (nic, cb) => { + async.eachSeries(nicsIds, function (nic, cb) { nicsDetails = []; async.waterfall([ - (nx) => { + function (nx) { resourceClient.resources.getById(nic.id, constants.MANAGEMENT_API_VERSION, nx); }, - (nicDetails, request, response, nx) => { + function (nicDetails, request, response, nx) { nicsDetails.push(nicDetails); if (options.destroyNics) { @@ -295,16 +297,16 @@ function destroyServer(server, options, callback) { }, next); }, - (next) => { + function (next) { // Collecting public ips and vnet ids publicIPs = []; vnets = []; - nicsDetails.forEach((nic) => { + nicsDetails.forEach(function (nic) { var configs = nic && nic.properties && nic.properties.ipConfigurations || []; // Collecting - configs.forEach((config) => { + configs.forEach(function (config) { var props = config && config.properties || {}; if (props.publicIPAddress && props.publicIPAddress.id) { publicIPs.push(props.publicIPAddress.id); @@ -318,29 +320,29 @@ function destroyServer(server, options, callback) { }); next(); }, - (next) => { + function (next) { if (!options.destroyPublicIP) { return next(); } // Deleting public ips - async.eachSeries(publicIPs, (publicIP, cb) => { + async.eachSeries(publicIPs, function (publicIP, cb) { resourceClient.resources.deleteById(publicIP, constants.MANAGEMENT_API_VERSION, cb); }, next); }, - (next) => { + function (next) { if (!options.destroyVnet) { return next(); } // Deleting vnets - async.eachSeries(vnets, (vnet, cb) => { + async.eachSeries(vnets, function (vnet, cb) { resourceClient.resources.deleteById(vnet, constants.MANAGEMENT_API_VERSION, cb); }, next); }, - (next) => { + function (next) { // Deleting storage account if (!options.destroyStorage) { return next(); @@ -384,14 +386,14 @@ function stopServer(server, callback) { var self = this; var serverId = server instanceof self.models.Server ? server.id : server; - self.login(err => { + self.login(function (err) { if (err) { return callback(err); } var client = new ComputeManagementClient(self.azure.credentials, self.config.subscriptionId); - client.virtualMachines.powerOff(self.config.resourceGroup, serverId, (err) => { + client.virtualMachines.powerOff(self.config.resourceGroup, serverId, function (err) { return err ? callback(err) : callback(null, serverId); @@ -408,14 +410,14 @@ function rebootServer(server, callback) { var self = this; var serverId = server instanceof self.models.Server ? server.id : server; - self.login(err => { + self.login(function (err) { if (err) { return callback(err); } var client = new ComputeManagementClient(self.azure.credentials, self.config.subscriptionId); - client.virtualMachines.restart(self.config.resourceGroup, serverId, (err) => { + client.virtualMachines.restart(self.config.resourceGroup, serverId, function (err) { return err ? callback(err) : callback(null, serverId); diff --git a/lib/pkgcloud/azure-v2/compute/server.js b/lib/pkgcloud/azure-v2/compute/server.js index 99c0a1917..20d674502 100644 --- a/lib/pkgcloud/azure-v2/compute/server.js +++ b/lib/pkgcloud/azure-v2/compute/server.js @@ -35,8 +35,12 @@ Server.prototype._setProperties = function (details) { // TODO: there doesn't seem to be an ERROR or FAIL status in pkgcloud var statuses = details.instanceView && details.instanceView.statuses || []; - var provisioningStatus = _.find(statuses, status => status.code.startsWith('ProvisioningState/')) || {}; - var powerStateStatus = _.find(statuses, status => status.code.startsWith('PowerState/')) || {}; + var provisioningStatus = _.find(statuses, function (status) { + return status.code.startsWith('ProvisioningState/'); + }) || {}; + var powerStateStatus = _.find(statuses, function (status) { + return status.code.startsWith('PowerState/'); + }) || {}; // Azure ARM VMs are natively constructed out of a collection of roles. if (provisioningStatus.code == 'ProvisioningState/succeeded' && powerStateStatus.code == 'PowerState/deallocated') { diff --git a/lib/pkgcloud/azure-v2/storage/client/containers.js b/lib/pkgcloud/azure-v2/storage/client/containers.js index 59a673200..ff9192ef2 100644 --- a/lib/pkgcloud/azure-v2/storage/client/containers.js +++ b/lib/pkgcloud/azure-v2/storage/client/containers.js @@ -17,17 +17,19 @@ var constants = require('../../constants'); function getContainers(callback) { var self = this; - self.login(err => { + self.login(function (err) { if (err) { return callback(err); } var storageClient = new StorageManagementClient(self.azure.credentials, self.config.subscriptionId); - storageClient.storageAccounts.listByResourceGroup(self.config.resourceGroup, (err, results) => { + storageClient.storageAccounts.listByResourceGroup(self.config.resourceGroup, function (err, results) { return err ? callback(err) - : callback(null, results.map(res => new self.models.Container(self, res))); + : callback(null, results.map(function (res) { + return new self.models.Container(self, res); + })); }); }); } @@ -41,14 +43,14 @@ function getContainer(container, callback) { var self = this; var containerName = container instanceof self.models.Container ? container.name : container; - self.login(err => { + self.login(function (err) { if (err) { return callback(err); } var storageClient = new StorageManagementClient(self.azure.credentials, self.config.subscriptionId); - storageClient.storageAccounts.getProperties(self.config.resourceGroup, containerName, (err, result) => { + storageClient.storageAccounts.getProperties(self.config.resourceGroup, containerName, function (err, result) { return err ? callback(err) : callback(null, new self.models.Container(self, result)); @@ -74,7 +76,7 @@ function createContainer(options, callback) { var containerName = options instanceof self.models.Container ? options.name : options; var parameters = typeof options == 'string' ? { name: options } : options; - self.deploy('storage', parameters, (err) => { + self.deploy('storage', parameters, function (err) { return err ? callback(err) : self.getContainer(containerName, callback); @@ -96,7 +98,7 @@ function destroyContainer(container, callback) { var self = this; var containerName = container instanceof self.models.Container ? container.name : container; - self.login(err => { + self.login(function (err) { if (err) { return callback(err); @@ -111,14 +113,14 @@ function listContainerKeys(container, callback) { var self = this; var containerName = container instanceof self.models.Container ? container.name : container; - self.login(err => { + self.login(function (err) { if (err) { return callback(err); } var storageClient = new StorageManagementClient(self.azure.credentials, self.config.subscriptionId); - storageClient.storageAccounts.listKeys(self.config.resourceGroup, containerName, (err, result) => { + storageClient.storageAccounts.listKeys(self.config.resourceGroup, containerName, function (err, result) { return err ? callback(err) : callback(null, result); @@ -137,7 +139,7 @@ function getContainerKey (container, callback) { return callback(null, self.azure.storageKeys[containerName]); } - self.listContainerKeys(container, (err, result) => { + self.listContainerKeys(container, function (err, result) { if (err) { return callback(err); } @@ -153,7 +155,7 @@ function getBlobService(options, storageAccountName, callback) { options = options || {}; var azureContainer = typeof options == 'string' ? options : (options.container || constants.DEFAULT_STORAGE_CONTAINER); - self.getContainerKey(storageAccountName, (err, containerKey) => { + self.getContainerKey(storageAccountName, function (err, containerKey) { if (err) { return callback(err); } diff --git a/lib/pkgcloud/azure-v2/storage/client/files.js b/lib/pkgcloud/azure-v2/storage/client/files.js index b52c37f6a..5d8cf3062 100644 --- a/lib/pkgcloud/azure-v2/storage/client/files.js +++ b/lib/pkgcloud/azure-v2/storage/client/files.js @@ -30,7 +30,7 @@ exports.removeFile = function (container, file, options, callback) { var fileName = file instanceof this.models.File ? file.name : file; var azureContainerName = options.storage && options.storage.container || constants.DEFAULT_STORAGE_CONTAINER; - this.getBlobService(azureContainerName, containerName, (err, blobService) => { + this.getBlobService(azureContainerName, containerName, function (err, blobService) { if (err) { return callback(err); @@ -184,13 +184,13 @@ exports.getFiles = function (container, options, callback) { var azureContainer = options.container || constants.DEFAULT_STORAGE_CONTAINER; var blobs = []; - self.getBlobService(azureContainer, containerName, (err, blobService) => { + self.getBlobService(azureContainer, containerName, function (err, blobService) { if (err) { return callback(err); } - var aggregateBlobs = (err, result, cb) => { + var aggregateBlobs = function (err, result, cb) { if (err) { cb(err); } else { @@ -204,10 +204,12 @@ exports.getFiles = function (container, options, callback) { }; blobService.listBlobsSegmented(azureContainer, null, function(err, result) { - aggregateBlobs(err, result, (err, blobs) => { + aggregateBlobs(err, result, function (err, blobs) { return err ? callback(err) : - callback(null, blobs.map(blob => new self.models.File(self, blob))); + callback(null, blobs.map(function (blob) { + return new self.models.File(self, blob); + })); }); }); @@ -231,13 +233,13 @@ exports.getFile = function (container, file, options, callback) { var azureContainerName = options.storage && options.storage.container || constants.DEFAULT_STORAGE_CONTAINER; var fileName = file instanceof this.models.File ? file.name : file; - self.getBlobService(azureContainerName, containerName, (err, blobService) => { + self.getBlobService(azureContainerName, containerName, function (err, blobService) { if (err) { return callback(err); } - blobService.getBlobProperties(azureContainerName, fileName, (err, properties, status) => { + blobService.getBlobProperties(azureContainerName, fileName, function (err, properties, status) { return err ? callback(err) : (!status || !status.isSuccessful) ? callback(new Error(`status is not successfull: ${JSON.stringify(status || null)}`)) : @@ -279,20 +281,20 @@ exports.download = function (options, callback) { var blobService; async.waterfall([ - (next) => { + function (next) { self.getBlobService(azureContainerName, containerName, next); }, - (_blobService, next) => { + function (_blobService, next) { blobService = _blobService; - blobService.getBlobProperties(azureContainerName, blobName, (err, properties, status) => { + blobService.getBlobProperties(azureContainerName, blobName, function (err, properties, status) { return err ? next(err) : (!status || !status.isSuccessful) ? next(new Error(`status is not successfull: ${JSON.stringify(status || null)}`)) : next(null, properties); }); }, - (properties, next) => { + function (properties, next) { blobService.createReadStream(azureContainerName, blobName).pipe(inputStream); return next(); } diff --git a/lib/pkgcloud/azure-v2/templates/index.js b/lib/pkgcloud/azure-v2/templates/index.js index e7ab0eace..213cd1640 100644 --- a/lib/pkgcloud/azure-v2/templates/index.js +++ b/lib/pkgcloud/azure-v2/templates/index.js @@ -17,7 +17,7 @@ function deploy(templateName, options, templateProcess, callback) { templateProcess = function (template) { return template; }; } - self.login(err => { + self.login(function (err) { if (err) { return callback(err); @@ -32,7 +32,7 @@ function deploy(templateName, options, templateProcess, callback) { mode: 'Incremental' } }; - Object.keys(options).forEach((key) => { + Object.keys(options).forEach(function (key) { if (template.parameters[key]) { parameters.properties.parameters[key] = { value: options[key] }; } @@ -40,7 +40,7 @@ function deploy(templateName, options, templateProcess, callback) { var deploymentName = 'pkgc-' + (new Date()).toISOString().replace(/\:|Z|\.|\-/g, '').replace(/T/g, '-'); var resourceClient = new resourceManagement.ResourceManagementClient(self.azure.credentials, self.config.subscriptionId); - resourceClient.deployments.createOrUpdate(self.config.resourceGroup, deploymentName, parameters, (err, result) => { + resourceClient.deployments.createOrUpdate(self.config.resourceGroup, deploymentName, parameters, function (err, result) { return err ? callback(err) : callback(null, result); diff --git a/package.json b/package.json index f3005ad10..232dc042c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { - "name": "pkgcloud", + "name": "pkgcloud-with-arm", "description": "An infrastructure-as-a-service agnostic cloud library for node.js", - "version": "1.4.0", + "version": "1.4.5", "author": "Charlie Robbins ", "contributors": [ { diff --git a/test/azure-v2/compute/client/test-servers.js b/test/azure-v2/compute/client/test-servers.js index b2ea9fd35..9b842b693 100644 --- a/test/azure-v2/compute/client/test-servers.js +++ b/test/azure-v2/compute/client/test-servers.js @@ -20,7 +20,7 @@ describe('pkgcloud/azure-v2/servers', function () { it('Get multiple servers', function(done) { mockRequests.prepare(); - client.getServers((err, servers) => { + client.getServers(function (err, servers) { should.not.exist(err); should(servers).be.instanceOf(Array).and.have.lengthOf(1); done(); @@ -31,7 +31,7 @@ describe('pkgcloud/azure-v2/servers', function () { it('Get a single server with RUNNING state', function(done) { mockRequests.prepare(); - client.getServer('azure-vm-server', (err, server) => { + client.getServer('azure-vm-server', function (err, server) { should.not.exist(err); should.exist(server); server.status.should.equal('RUNNING'); @@ -44,7 +44,7 @@ describe('pkgcloud/azure-v2/servers', function () { it('Creating a new server', function(done) { mockRequests.prepare(); - client.createServer(createParams, (err, server) => { + client.createServer(createParams, function (err, server) { should.not.exist(err); should.exist(server); server.status.should.equal('RUNNING'); @@ -60,7 +60,7 @@ describe('pkgcloud/azure-v2/servers', function () { destroyPublicIP: true, destroyVnet: true, destroyStorage: true - }, (err) => { + }, function (err) { should.not.exist(err); done(); }); diff --git a/test/azure-v2/mock-requests.js b/test/azure-v2/mock-requests.js index b7968fbbb..06e54c705 100644 --- a/test/azure-v2/mock-requests.js +++ b/test/azure-v2/mock-requests.js @@ -56,7 +56,7 @@ function prepare() { // Storage // https://management.azure.com/subscriptions/azure-account-subscription-id/resourceGroups/resource-group/providers/Microsoft.Storage/storageAccounts/test-storage?api-version=2016-05-01 nock(`${azureManagementUri}/subscriptions/${config.subscriptionId}/resourcegroups/${config.resourceGroup}/providers/microsoft.storage`) - .filteringPath((path) => { return path.toLowerCase(); }) + .filteringPath(function (path) { return path.toLowerCase(); }) .get(`/storageaccounts/azurestorage?api-version=2016-05-01`) .reply(200, loadFixture('container.json')) .post(`/storageaccounts/azurestorage/listkeys?api-version=2016-05-01`) @@ -68,7 +68,7 @@ function prepare() { // Deployments nock(`${azureManagementUri}/subscriptions/${config.subscriptionId}/resourcegroups/${config.resourceGroup}/providers/microsoft.resources/deployments`) - .filteringPath((path) => { + .filteringPath(function (path) { path = path.toLowerCase(); return path.substr(0, path.indexOf('/deployments/pkgc-')) + '/deployments/pkgc-test'; }) diff --git a/test/azure-v2/storage/client/test-storage.js b/test/azure-v2/storage/client/test-storage.js index d67961696..909254f9a 100644 --- a/test/azure-v2/storage/client/test-storage.js +++ b/test/azure-v2/storage/client/test-storage.js @@ -11,7 +11,7 @@ describe('pkgcloud/azure-v2/storage', function () { it('Create container', function(done) { mockRequests.prepare(); - client.createContainer('azurestorage', (err, container) => { + client.createContainer('azurestorage', function (err, container) { should.not.exist(err); should.exist(container); should(container.name).be.exactly('azurestorage'); @@ -22,7 +22,7 @@ describe('pkgcloud/azure-v2/storage', function () { it('Get container', function(done) { mockRequests.prepare(); - client.getContainer('azurestorage', (err, container) => { + client.getContainer('azurestorage', function (err, container) { should.not.exist(err); should.exist(container); should(container.name).be.exactly('azurestorage'); @@ -35,7 +35,7 @@ describe('pkgcloud/azure-v2/storage', function () { // it('Get files in container', function(done) { // mockRequests.prepare(); - // client.getFiles('azurestorage', { container: 'container' }, (err, files) => { + // client.getFiles('azurestorage', { container: 'container' }, function (err, files) { // should.not.exist(err); // should.exist(container); // should(container.name).be.exactly('azurestorage'); From 6a38525de5c3d9f5787d8854a658c37d116bdfd0 Mon Sep 17 00:00:00 2001 From: morsh Date: Wed, 15 Feb 2017 17:42:39 +0200 Subject: [PATCH 32/41] reverting package.json --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 232dc042c..f3005ad10 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { - "name": "pkgcloud-with-arm", + "name": "pkgcloud", "description": "An infrastructure-as-a-service agnostic cloud library for node.js", - "version": "1.4.5", + "version": "1.4.0", "author": "Charlie Robbins ", "contributors": [ { From 7058437e4680a332f780e8f2b3f634a4800a6f20 Mon Sep 17 00:00:00 2001 From: morsh Date: Wed, 15 Feb 2017 17:53:42 +0200 Subject: [PATCH 33/41] removing es6 singlequotes --- lib/pkgcloud/azure-v2/storage/client/files.js | 4 +- test/azure-v2/mock-requests.js | 38 +++++++++---------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/lib/pkgcloud/azure-v2/storage/client/files.js b/lib/pkgcloud/azure-v2/storage/client/files.js index 5d8cf3062..d2eefef09 100644 --- a/lib/pkgcloud/azure-v2/storage/client/files.js +++ b/lib/pkgcloud/azure-v2/storage/client/files.js @@ -242,7 +242,7 @@ exports.getFile = function (container, file, options, callback) { blobService.getBlobProperties(azureContainerName, fileName, function (err, properties, status) { return err ? callback(err) : (!status || !status.isSuccessful) ? - callback(new Error(`status is not successfull: ${JSON.stringify(status || null)}`)) : + callback(new Error('status is not successfull: ' + JSON.stringify(status || null))) : callback(null, new self.models.File(self, properties)); }); }); @@ -290,7 +290,7 @@ exports.download = function (options, callback) { blobService.getBlobProperties(azureContainerName, blobName, function (err, properties, status) { return err ? next(err) : (!status || !status.isSuccessful) ? - next(new Error(`status is not successfull: ${JSON.stringify(status || null)}`)) : + next(new Error('status is not successfull: ' + JSON.stringify(status || null))) : next(null, properties); }); }, diff --git a/test/azure-v2/mock-requests.js b/test/azure-v2/mock-requests.js index 06e54c705..451f5d500 100644 --- a/test/azure-v2/mock-requests.js +++ b/test/azure-v2/mock-requests.js @@ -17,57 +17,57 @@ function prepare() { // Nock authentication requests - nock(`${azureAuthUri}`) - .post(`/${sp.domain}/oauth2/token?api-version=1.0`) + nock(azureAuthUri) + .post('/' + sp.domain + '/oauth2/token?api-version=1.0') .reply(200, loadFixture('authentication-certificate.json')); // Subscriptions - nock(`${azureManagementUri}`) + nock(azureManagementUri) .get('/subscriptions?api-version=2015-11-01') .reply(200, loadFixture('subscriptions.json')); // Servers - nock(`${azureManagementUri}/subscriptions/${config.subscriptionId}/resourceGroups/${config.resourceGroup}/providers/Microsoft.Compute`) - .get(`/virtualMachines?api-version=${apiVersion}`) + nock(azureManagementUri + '/subscriptions/' + config.subscriptionId + '/resourceGroups/' + config.resourceGroup + '/providers/Microsoft.Compute') + .get('/virtualMachines?api-version=' + apiVersion) .reply(200, loadFixture('servers.json')) - .get(`/virtualMachines/azure-vm-server?$expand=instanceView&api-version=${apiVersion}`) + .get('/virtualMachines/azure-vm-server?$expand=instanceView&api-version=' + apiVersion) .reply(200, loadFixture('server.json')) - .delete(`/virtualMachines/azure-vm-server?api-version=${apiVersion}`) + .delete('/virtualMachines/azure-vm-server?api-version=' + apiVersion) .reply(204, ''); // Nicks //https://management.azure.com//subscriptions/subscriptionId/resourceGroups/resource-group/providers/Microsoft.Network/networkInterfaces/nicName?api-version=2016-03-30 - nock(`${azureManagementUri}/subscriptions/${config.subscriptionId}/resourceGroups/${config.resourceGroup}/providers/Microsoft.Network`) - .get(`/networkInterfaces/nicName?api-version=${apiVersion}`) + nock(azureManagementUri + '/subscriptions/${config.subscriptionId}/resourceGroups/${config.resourceGroup}/providers/Microsoft.Network') + .get('/networkInterfaces/nicName?api-version=' + apiVersion) .reply(200, loadFixture('nic.json')) - .delete(`/networkInterfaces/nicName?api-version=${apiVersion}`) + .delete('/networkInterfaces/nicName?api-version=' + apiVersion) .reply(204, ''); // Public IPs - nock(`${azureManagementUri}/subscriptions/${config.subscriptionId}/resourceGroups/${config.resourceGroup}/providers/Microsoft.Network`) - .delete(`/publicIPAddresses/publicIPName?api-version=${apiVersion}`) + nock(azureManagementUri + '/subscriptions/' + config.subscriptionId + '/resourceGroups/' + config.resourceGroup + '/providers/Microsoft.Network') + .delete('/publicIPAddresses/publicIPName?api-version=' + apiVersion) .reply(204, ''); // VNET - nock(`${azureManagementUri}/subscriptions/${config.subscriptionId}/resourceGroups/${config.resourceGroup}/providers/Microsoft.Network`) - .delete(`/virtualNetworks/vnetName?api-version=${apiVersion}`) + nock(azureManagementUri + '/subscriptions/' + config.subscriptionId + '/resourceGroups/' + config.resourceGroup + '/providers/Microsoft.Network') + .delete('/virtualNetworks/vnetName?api-version=' + apiVersion) .reply(204, ''); // Storage // https://management.azure.com/subscriptions/azure-account-subscription-id/resourceGroups/resource-group/providers/Microsoft.Storage/storageAccounts/test-storage?api-version=2016-05-01 - nock(`${azureManagementUri}/subscriptions/${config.subscriptionId}/resourcegroups/${config.resourceGroup}/providers/microsoft.storage`) + nock(azureManagementUri + '/subscriptions/' + config.subscriptionId + '/resourcegroups/' + config.resourceGroup + '/providers/microsoft.storage') .filteringPath(function (path) { return path.toLowerCase(); }) - .get(`/storageaccounts/azurestorage?api-version=2016-05-01`) + .get('/storageaccounts/azurestorage?api-version=2016-05-01') .reply(200, loadFixture('container.json')) - .post(`/storageaccounts/azurestorage/listkeys?api-version=2016-05-01`) + .post('/storageaccounts/azurestorage/listkeys?api-version=2016-05-01') .reply(200, loadFixture('container-keys.json')) - .delete(`/storageaccounts/azurestorage/?api-version=2016-01-01`) + .delete('/storageaccounts/azurestorage/?api-version=2016-01-01') .reply(204, ''); // url:"https://management.azure.com/subscriptions/73a4ea93-d914-424d-9e64-28adf397e8e3/resourceGroups/morshe-noobaa2/providers/Microsoft.Storage/storageAccounts/boobaavmstore3/listKeys?api-version=2016-05-01" // Deployments - nock(`${azureManagementUri}/subscriptions/${config.subscriptionId}/resourcegroups/${config.resourceGroup}/providers/microsoft.resources/deployments`) + nock(azureManagementUri + '/subscriptions/' + config.subscriptionId + '/resourcegroups/' + config.resourceGroup + '/providers/microsoft.resources/deployments') .filteringPath(function (path) { path = path.toLowerCase(); return path.substr(0, path.indexOf('/deployments/pkgc-')) + '/deployments/pkgc-test'; From 326a5f5eaf3ce56d1de87bca25a0edcccec31efb Mon Sep 17 00:00:00 2001 From: morsh Date: Wed, 15 Feb 2017 17:56:50 +0200 Subject: [PATCH 34/41] jshint #4 --- examples/storage/azure-v2.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/examples/storage/azure-v2.js b/examples/storage/azure-v2.js index 65d5a6a93..99f774f3a 100644 --- a/examples/storage/azure-v2.js +++ b/examples/storage/azure-v2.js @@ -21,7 +21,11 @@ var client = pkgcloud.storage.createClient({ // }) // }); -client.getFiles('storageacountname', { container: 'container-name' }, function (err, files) (function (err) { +client.getFiles('storageacountname', { container: 'container-name' }, function (err, files) { + + if (err) { + return console.error(err); + } var file = files[0]; console.dir(file); From cd551f1f787cd46f20032286e7698baf0a6e69af Mon Sep 17 00:00:00 2001 From: morsh Date: Wed, 15 Feb 2017 19:37:54 +0200 Subject: [PATCH 35/41] removing extractors --- lib/pkgcloud/azure-v2/azure-api.js | 2 +- .../azure-v2/compute/client/flavors.js | 4 ++-- .../azure-v2/compute/client/servers.js | 18 +++++++++--------- .../azure-v2/storage/client/containers.js | 14 +++++++------- lib/pkgcloud/azure-v2/templates/index.js | 2 +- 5 files changed, 20 insertions(+), 20 deletions(-) diff --git a/lib/pkgcloud/azure-v2/azure-api.js b/lib/pkgcloud/azure-v2/azure-api.js index f39f595d7..ebebec24c 100644 --- a/lib/pkgcloud/azure-v2/azure-api.js +++ b/lib/pkgcloud/azure-v2/azure-api.js @@ -108,5 +108,5 @@ function bind(client) { } module.exports = { - bind + bind: bind }; \ No newline at end of file diff --git a/lib/pkgcloud/azure-v2/compute/client/flavors.js b/lib/pkgcloud/azure-v2/compute/client/flavors.js index 85d6a911c..1ebf961df 100644 --- a/lib/pkgcloud/azure-v2/compute/client/flavors.js +++ b/lib/pkgcloud/azure-v2/compute/client/flavors.js @@ -55,6 +55,6 @@ function getFlavor(flavor, callback) { } module.exports = { - getFlavors, - getFlavor + getFlavors: getFlavors, + getFlavor: getFlavor }; \ No newline at end of file diff --git a/lib/pkgcloud/azure-v2/compute/client/servers.js b/lib/pkgcloud/azure-v2/compute/client/servers.js index e47d4e7ac..8ad5b2787 100644 --- a/lib/pkgcloud/azure-v2/compute/client/servers.js +++ b/lib/pkgcloud/azure-v2/compute/client/servers.js @@ -438,13 +438,13 @@ function renameServer(server, callback) { } module.exports = { - getVersion, - getLimits, - getServers, - getServer, - createServer, - destroyServer, - stopServer, - rebootServer, - renameServer + getVersion: getVersion, + getLimits: getLimits, + getServers: getServers, + getServer: getServer, + createServer: createServer, + destroyServer: destroyServer, + stopServer: stopServer, + rebootServer: rebootServer, + renameServer: renameServer }; diff --git a/lib/pkgcloud/azure-v2/storage/client/containers.js b/lib/pkgcloud/azure-v2/storage/client/containers.js index ff9192ef2..b04881415 100644 --- a/lib/pkgcloud/azure-v2/storage/client/containers.js +++ b/lib/pkgcloud/azure-v2/storage/client/containers.js @@ -172,11 +172,11 @@ function getBlobService(options, storageAccountName, callback) { module.exports = { - getBlobService, - getContainers, - getContainer, - createContainer, - destroyContainer, - listContainerKeys, - getContainerKey + getBlobService: getBlobService, + getContainers: getContainers, + getContainer: getContainer, + createContainer: createContainer, + destroyContainer: destroyContainer, + listContainerKeys: listContainerKeys, + getContainerKey: getContainerKey }; \ No newline at end of file diff --git a/lib/pkgcloud/azure-v2/templates/index.js b/lib/pkgcloud/azure-v2/templates/index.js index 213cd1640..67e29ec38 100644 --- a/lib/pkgcloud/azure-v2/templates/index.js +++ b/lib/pkgcloud/azure-v2/templates/index.js @@ -49,5 +49,5 @@ function deploy(templateName, options, templateProcess, callback) { } module.exports = { - deploy + deploy: deploy }; \ No newline at end of file From ac50dc8af905133c4365b94793a99feaf54ba705 Mon Sep 17 00:00:00 2001 From: morsh Date: Thu, 16 Feb 2017 16:35:35 +0200 Subject: [PATCH 36/41] adjusting some tests --- lib/pkgcloud/azure-v2/azure-api.js | 2 +- test/azure-v2/mock-requests.js | 17 ++++++++++++++++- test/common/compute/server-test.js | 14 +++++++++++++- test/fixtures/azure-v2/resourceGroup.json | 8 ++++++++ 4 files changed, 38 insertions(+), 3 deletions(-) create mode 100644 test/fixtures/azure-v2/resourceGroup.json diff --git a/lib/pkgcloud/azure-v2/azure-api.js b/lib/pkgcloud/azure-v2/azure-api.js index ebebec24c..13522d951 100644 --- a/lib/pkgcloud/azure-v2/azure-api.js +++ b/lib/pkgcloud/azure-v2/azure-api.js @@ -58,7 +58,7 @@ function login(setupLocation, callback) { self.azure.lastRefresh = new Date(); if (setupLocation) { - return self.setup(credentials, function (err) { + return self.setupLocation(credentials, function (err) { return callback(err, credentials); }); } else { diff --git a/test/azure-v2/mock-requests.js b/test/azure-v2/mock-requests.js index 451f5d500..871b5be7a 100644 --- a/test/azure-v2/mock-requests.js +++ b/test/azure-v2/mock-requests.js @@ -25,6 +25,14 @@ function prepare() { nock(azureManagementUri) .get('/subscriptions?api-version=2015-11-01') .reply(200, loadFixture('subscriptions.json')); + nock(azureManagementUri) + .get('/subscriptions?api-version=2016-06-01') + .reply(200, loadFixture('subscriptions.json')); + + // Resource group + nock(azureManagementUri + '/subscriptions/' + config.subscriptionId + '/resourcegroups') + .get('/' + config.resourceGroup + '?api-version=2016-09-01') + .reply(200, loadFixture('resourceGroup.json')); // Servers nock(azureManagementUri + '/subscriptions/' + config.subscriptionId + '/resourceGroups/' + config.resourceGroup + '/providers/Microsoft.Compute') @@ -35,9 +43,16 @@ function prepare() { .delete('/virtualMachines/azure-vm-server?api-version=' + apiVersion) .reply(204, ''); + // Images + nock(azureManagementUri + '/subscriptions/' + config.subscriptionId + '/providers/Microsoft.Compute/locations/location') + .get('/publishers/MicrosoftWindowsServer/artifacttypes/vmimage/offers/WindowsServer/skus/2012-R2-Datacenter/versions?api-version=2016-03-30') + .reply(200, loadFixture('servers.json')) + .get('/vmSizes?api-version=2016-03-30') + .reply(200, loadFixture('servers.json')); + // Nicks //https://management.azure.com//subscriptions/subscriptionId/resourceGroups/resource-group/providers/Microsoft.Network/networkInterfaces/nicName?api-version=2016-03-30 - nock(azureManagementUri + '/subscriptions/${config.subscriptionId}/resourceGroups/${config.resourceGroup}/providers/Microsoft.Network') + nock(azureManagementUri + '/subscriptions/' + config.subscriptionId + '/resourceGroups/' + config.resourceGroup + '/providers/Microsoft.Network') .get('/networkInterfaces/nicName?api-version=' + apiVersion) .reply(200, loadFixture('nic.json')) .delete('/networkInterfaces/nicName?api-version=' + apiVersion) diff --git a/test/common/compute/server-test.js b/test/common/compute/server-test.js index 970a613f5..aedcb682c 100644 --- a/test/common/compute/server-test.js +++ b/test/common/compute/server-test.js @@ -14,6 +14,7 @@ var should = require('should'), providers = require('../../configs/providers.json'), Server = require('../../../lib/pkgcloud/core/compute/server').Server, azureApi = require('../../../lib/pkgcloud/azure/utils/azureApi'), + azureV2Mocks = require('../../azure-v2/mock-requests'), mock = !!process.env.MOCK; var azureOptions = require('../../fixtures/azure/azure-options.json'); @@ -25,6 +26,7 @@ var setupImagesMock, setupFlavorMock, setupServerMock, setupGetServersMock, azureApi._updateMinimumPollInterval(mock ? 10 : azureApi.MINIMUM_POLL_INTERVAL); providers.filter(function (provider) { + return provider == "azure-v2"; return !!helpers.pkgcloud.providers[provider].compute; }).forEach(function (provider) { describe('pkgcloud/common/compute/server [' + provider + ']', function () { @@ -46,7 +48,7 @@ providers.filter(function (provider) { // setup a filtering path for aws hockInstance.filteringPathRegEx(/https:\/\/ec2\.us-west-2\.amazonaws\.com([?\w\-\.\_0-9\/]*)/g, '$1'); - + server = http.createServer(hockInstance.handler); authServer = http.createServer(authHockInstance.handler); @@ -294,6 +296,10 @@ setupImagesMock = function (client, provider, servers) { .get('/azure-account-subscription-id/services/images') .replyWithFile(200, __dirname + '/../../fixtures/azure/images.xml'); } + else if (provider === 'azure-v2') { + + azureV2Mocks.prepare(); + } else if (provider === 'digitalocean') { servers.server .get('/v2/images?per_page=200&page=1') @@ -355,6 +361,9 @@ setupFlavorMock = function (client, provider, servers) { .get('/v2/5ACED3DC3AA740ABAA41711243CC6949/flavors/detail') .replyWithFile(200, __dirname + '/../../fixtures/hp/flavors.json'); } + else if (provider === 'azure-v2') { + azureV2Mocks.prepare(); + } }; setupServerMock = function (client, provider, servers) { @@ -471,6 +480,9 @@ setupServerMock = function (client, provider, servers) { .get('/v2/5ACED3DC3AA740ABAA41711243CC6949/servers/5a023de8-957b-4822-ad84-8c7a9ef83c07') .replyWithFile(200, __dirname + '/../../fixtures/openstack/serverCreated2.json'); } + else if (provider === 'azure-v2') { + azureV2Mocks.prepare(); + } }; setupGetServersMock = function (client, provider, servers) { diff --git a/test/fixtures/azure-v2/resourceGroup.json b/test/fixtures/azure-v2/resourceGroup.json new file mode 100644 index 000000000..6db789d9c --- /dev/null +++ b/test/fixtures/azure-v2/resourceGroup.json @@ -0,0 +1,8 @@ +{ + "id": "/subscriptions/azure-account-subscription-id/resourcegroups/resource-group", + "name": "resource-group", + "location": "location", + "properties": { + "provisioningState": "Succeeded" + } +} \ No newline at end of file From 0e8b2292acee3d26f943d8b6439444a8eceee6b5 Mon Sep 17 00:00:00 2001 From: morsh Date: Thu, 16 Feb 2017 16:39:56 +0200 Subject: [PATCH 37/41] canceling filter of azure-v2 --- test/common/compute/server-test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/common/compute/server-test.js b/test/common/compute/server-test.js index aedcb682c..efbf63a72 100644 --- a/test/common/compute/server-test.js +++ b/test/common/compute/server-test.js @@ -26,7 +26,6 @@ var setupImagesMock, setupFlavorMock, setupServerMock, setupGetServersMock, azureApi._updateMinimumPollInterval(mock ? 10 : azureApi.MINIMUM_POLL_INTERVAL); providers.filter(function (provider) { - return provider == "azure-v2"; return !!helpers.pkgcloud.providers[provider].compute; }).forEach(function (provider) { describe('pkgcloud/common/compute/server [' + provider + ']', function () { From b8f41ac410560120bfca7d99205de3fe255a9dcd Mon Sep 17 00:00:00 2001 From: jackyalbo Date: Wed, 15 Mar 2017 15:18:42 +0200 Subject: [PATCH 38/41] adding ssh support for both linux and windows will support win32-openssh for windows and will add default user to sudoers for linux --- .../azure-v2/compute/client/servers.js | 145 +++++++++++------- lib/pkgcloud/azure-v2/scripts/ssh.ps1 | 8 + lib/pkgcloud/azure-v2/scripts/sudo.sh | 4 + 3 files changed, 98 insertions(+), 59 deletions(-) create mode 100644 lib/pkgcloud/azure-v2/scripts/ssh.ps1 create mode 100644 lib/pkgcloud/azure-v2/scripts/sudo.sh diff --git a/lib/pkgcloud/azure-v2/compute/client/servers.js b/lib/pkgcloud/azure-v2/compute/client/servers.js index 8ad5b2787..ee0e1f71c 100644 --- a/lib/pkgcloud/azure-v2/compute/client/servers.js +++ b/lib/pkgcloud/azure-v2/compute/client/servers.js @@ -27,7 +27,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 ); } @@ -48,9 +50,9 @@ function getServers(callback) { 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 err ? + callback(err) : + callback(null, results.map(function (res) { return new self.models.Server(self, res); })); }); @@ -63,7 +65,7 @@ function getServers(callback) { * @param {Function} callback cb(err, serverId). */ function getServer(server, callback) { - var self = this; + var self = this; var serverId = server instanceof self.models.Server ? server.name : server; self.login(function (err) { @@ -73,9 +75,11 @@ function getServer(server, callback) { } var client = new ComputeManagementClient(self.azure.credentials, self.config.subscriptionId); - + // This will ensure returning of instances running status - var options = { expand: 'instanceView' }; + var options = { + expand: 'instanceView' + }; client.virtualMachines.get(self.config.resourceGroup, serverId, options, function (err, result) { if (err) { @@ -83,15 +87,15 @@ function getServer(server, callback) { } // 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) { if (err) { @@ -99,11 +103,11 @@ function getServer(server, callback) { } 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)); } @@ -143,21 +147,27 @@ 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' }); + var vmIndex = _.findIndex(template.resources, { + 'type': 'Microsoft.Compute/virtualMachines' + }); // Adding additional data disks if (options.storageDataDiskNames && options.storageDataDiskNames.length) { @@ -174,11 +184,11 @@ function createServer(options, callback) { }); } - // If this is a windows machine, add an extension that enables remote connection via WinRM + // If this is a windows machine, add an extension that enables ssh connection via Win32-OpenSSH if (options.osType === 'Windows') { template.resources[vmIndex].resources = [{ 'type': 'Microsoft.Compute/virtualMachines/extensions', - 'name': '[concat(variables(\'vmName\'),\'/WinRMCustomScriptExtension\')]', + 'name': '[concat(variables(\'vmName\'),\'/Win32sshExtension\')]', 'apiVersion': constants.DEFAULT_API_VERSION, 'location': '[resourceGroup().location]', 'dependsOn': [ @@ -187,15 +197,30 @@ function createServer(options, callback) { 'properties': { 'publisher': 'Microsoft.Compute', 'type': 'CustomScriptExtension', - 'typeHandlerVersion': '1.4', + 'typeHandlerVersion': '1.8', '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\'))]' - } + 'fileUris': ["https://raw.githubusercontent.com/CatalystCode/pkgcloud/master/lib/pkgcloud/azure-v2/scripts/ssh.ps1"], + 'commandToExecute': `powershell -File ssh.ps1 ${hostname}\\${options.username} ${options.password}` + }, + } + }]; + } else { // linux - make sure the new user is in sudoers - so he can sudo with no password + template.resources[vmIndex].resources = [{ + 'type': 'Microsoft.Compute/virtualMachines/extensions', + 'name': '[concat(variables(\'vmName\'),\'/LinuxSudoExtension\')]', + 'apiVersion': constants.DEFAULT_API_VERSION, + 'location': '[resourceGroup().location]', + 'dependsOn': [ + '[concat(\'Microsoft.Compute/virtualMachines/\', variables(\'vmName\'))]' + ], + 'properties': { + 'publisher': 'Microsoft.Compute', + 'type': 'CustomScriptForLinux', + 'typeHandlerVersion': '1.5', + 'settings': { + 'fileUris': ["https://raw.githubusercontent.com/CatalystCode/pkgcloud/master/lib/pkgcloud/azure-v2/scripts/sudo.sh"], + 'commandToExecute': 'bash sudo.sh ' + options.username + }, } }]; } @@ -236,7 +261,7 @@ function destroyServer(server, options, callback) { var serverDetails; var nicsIds; var nicsDetails; - + var vnets; var publicIPs; @@ -255,7 +280,7 @@ function destroyServer(server, options, callback) { // 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) { @@ -264,19 +289,19 @@ function destroyServer(server, options, callback) { } if (!options.destroyNics && - !options.destroyPublicIP && - !options.destroyVnet && - !options.destroyStorage) { + !options.destroyPublicIP && + !options.destroyVnet && + !options.destroyStorage) { return callback(); } async.waterfall([ function (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) { @@ -329,7 +354,7 @@ function destroyServer(server, options, callback) { // Deleting public ips async.eachSeries(publicIPs, function (publicIP, cb) { resourceClient.resources.deleteById(publicIP, constants.MANAGEMENT_API_VERSION, cb); - }, next); + }, next); }, function (next) { @@ -340,7 +365,7 @@ function destroyServer(server, options, callback) { // Deleting vnets async.eachSeries(vnets, function (vnet, cb) { resourceClient.resources.deleteById(vnet, constants.MANAGEMENT_API_VERSION, cb); - }, next); + }, next); }, function (next) { // Deleting storage account @@ -348,12 +373,12 @@ function destroyServer(server, options, callback) { 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 +389,17 @@ 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); }); }); - + } /** @@ -394,9 +419,9 @@ function stopServer(server, callback) { 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); + return err ? + callback(err) : + callback(null, serverId); }); }); } @@ -418,9 +443,9 @@ function rebootServer(server, callback) { 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); + return err ? + callback(err) : + callback(null, serverId); }); }); } @@ -432,7 +457,9 @@ 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 ); } @@ -447,4 +474,4 @@ module.exports = { stopServer: stopServer, rebootServer: rebootServer, renameServer: renameServer -}; +}; \ No newline at end of file diff --git a/lib/pkgcloud/azure-v2/scripts/ssh.ps1 b/lib/pkgcloud/azure-v2/scripts/ssh.ps1 new file mode 100644 index 000000000..1c9e702cb --- /dev/null +++ b/lib/pkgcloud/azure-v2/scripts/ssh.ps1 @@ -0,0 +1,8 @@ +$username = $args[0] +$password = $args[1] +$securePassword = ConvertTo-SecureString $password -AsPlainText -Force +$credential = New-Object System.Management.Automation.PSCredential $username, $securePassword +Enable-PSRemoting -Force +$scriptPath = ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/DarwinJS/ChocoPackages/master/openssh/InstallChoco_and_win32-openssh_with_server.ps1')) +Invoke-Command -ScriptBlock ([scriptblock]::Create($scriptPath)) -Credential $credential -ComputerName localhost +Disable-PSRemoting -Force \ No newline at end of file diff --git a/lib/pkgcloud/azure-v2/scripts/sudo.sh b/lib/pkgcloud/azure-v2/scripts/sudo.sh new file mode 100644 index 000000000..430aee644 --- /dev/null +++ b/lib/pkgcloud/azure-v2/scripts/sudo.sh @@ -0,0 +1,4 @@ +#!/bin/bash +# Make sure you use a username that is lowercase. +USERNAME=$1 +echo "$USERNAME ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers \ No newline at end of file From b02a55c8e93c89fd0565e6c93a27cb19148f722e Mon Sep 17 00:00:00 2001 From: jackyalbo Date: Tue, 25 Apr 2017 18:26:06 +0300 Subject: [PATCH 39/41] small urgent fix --- lib/pkgcloud/azure-v2/compute/client/servers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pkgcloud/azure-v2/compute/client/servers.js b/lib/pkgcloud/azure-v2/compute/client/servers.js index ee0e1f71c..4c9afc24a 100644 --- a/lib/pkgcloud/azure-v2/compute/client/servers.js +++ b/lib/pkgcloud/azure-v2/compute/client/servers.js @@ -200,7 +200,7 @@ function createServer(options, callback) { 'typeHandlerVersion': '1.8', 'settings': { 'fileUris': ["https://raw.githubusercontent.com/CatalystCode/pkgcloud/master/lib/pkgcloud/azure-v2/scripts/ssh.ps1"], - 'commandToExecute': `powershell -File ssh.ps1 ${hostname}\\${options.username} ${options.password}` + 'commandToExecute': `powershell -File ssh.ps1 ${options.username} ${options.password}` }, } }]; From 718987cb88e144ffcd5537c9540f736c0ef48bb2 Mon Sep 17 00:00:00 2001 From: jackyalbo Date: Wed, 26 Apr 2017 20:17:50 +0300 Subject: [PATCH 40/41] miniature fix for running ssh on windows --- lib/pkgcloud/azure-v2/compute/client/servers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pkgcloud/azure-v2/compute/client/servers.js b/lib/pkgcloud/azure-v2/compute/client/servers.js index 4c9afc24a..49478aaad 100644 --- a/lib/pkgcloud/azure-v2/compute/client/servers.js +++ b/lib/pkgcloud/azure-v2/compute/client/servers.js @@ -200,7 +200,7 @@ function createServer(options, callback) { 'typeHandlerVersion': '1.8', 'settings': { 'fileUris': ["https://raw.githubusercontent.com/CatalystCode/pkgcloud/master/lib/pkgcloud/azure-v2/scripts/ssh.ps1"], - 'commandToExecute': `powershell -File ssh.ps1 ${options.username} ${options.password}` + 'commandToExecute': `powershell -File ssh.ps1 .\\${options.username} ${options.password}` }, } }]; From 2caa019eeb446c0dc2bfe34bbb77cd5903a371fb Mon Sep 17 00:00:00 2001 From: jackyalbo Date: Thu, 27 Apr 2017 15:01:42 +0300 Subject: [PATCH 41/41] bug fix --- lib/pkgcloud/azure-v2/compute/client/servers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pkgcloud/azure-v2/compute/client/servers.js b/lib/pkgcloud/azure-v2/compute/client/servers.js index 49478aaad..aa4b360b7 100644 --- a/lib/pkgcloud/azure-v2/compute/client/servers.js +++ b/lib/pkgcloud/azure-v2/compute/client/servers.js @@ -214,7 +214,7 @@ function createServer(options, callback) { '[concat(\'Microsoft.Compute/virtualMachines/\', variables(\'vmName\'))]' ], 'properties': { - 'publisher': 'Microsoft.Compute', + 'publisher': 'Microsoft.OSTCExtensions', 'type': 'CustomScriptForLinux', 'typeHandlerVersion': '1.5', 'settings': {