diff --git a/lib/cli/cli.js b/lib/cli/cli.js index da98a582b..4e7056d0b 100644 --- a/lib/cli/cli.js +++ b/lib/cli/cli.js @@ -11,6 +11,11 @@ var Table = require('easy-table'); var cli = new commander.Command(); cli.output = log; +cli.exit = function (level, message, exitCode) { + log.log(level, message); + process.exit(exitCode); +}; + log.reset = function (options) { log.format = options; log.level = options.level; @@ -135,7 +140,7 @@ function enableNestedCommands(command) { } if (!command.categories[category]) { - log.error("Unknown command", category); + log.error('\'' + category + '\' is not an azure command. See \'azure help\'.'); } else { command.categories[category].parse(args); if (command.categories[category].args.length == 0) { diff --git a/lib/cli/commands/account-config.js b/lib/cli/commands/account-config.js new file mode 100644 index 000000000..019cc44a1 --- /dev/null +++ b/lib/cli/commands/account-config.js @@ -0,0 +1,41 @@ + +var fs = require('fs'); +var path = require('path'); +var xml2js = require('xml2js'); +var assert = require('assert'); +var pfx2pem = require('../../util/certificates/pkcs').pfx2pem; + +exports.init = function (cli) { + + var log = cli.output; + + var account = cli.category('account'); + + var config = account.category('config') + .description('Manage settings in your config file.'); + + config.command('list') + .description('Display config settings.') + .action(function (options) { + log.info('Displaying config settings'); + + var cfg = account.readConfig(); + + log.table(cfg, function (row, name) { + row.cell('Setting', name); + row.cell('Value', cfg[name]); + }); + }); + + config.command('set ') + .description('Change a config setting.') + .action(function (name, value, options) { + log.info('Setting \'' + name + '\' to value \'' + value + '\''); + var cfg = account.readConfig(); + cfg[name] = value; + account.writeConfig(cfg); + log.info('Changes saved.'); + }); + + +}; diff --git a/lib/cli/commands/account.js b/lib/cli/commands/account.js index c4c511886..5f19f1c58 100644 --- a/lib/cli/commands/account.js +++ b/lib/cli/commands/account.js @@ -14,13 +14,13 @@ exports.init = function (cli) { account.command('download') - .description('Download or import a publishsettings file for your Azure account.') + .description('Launch a browser to download your publishsettings file.') .action(function (publishSettingsFile) { console.log('downloading: ', publishSettingsFile); }); account.command('import ') - .description('Download or import a publishsettings file for your Azure account.') + .description('Import a publishsettings file for your account.') .action(function (publishSettingsFile) { log.info('Importing publish settings file', publishSettingsFile); @@ -41,7 +41,9 @@ exports.init = function (cli) { function processSettings(settings) { var attribs = settings.PublishProfile['@']; var subs = settings.PublishProfile.Subscription; - if (typeof (subs[0]) === 'undefined') { + if (subs === 'undefined') { + subs = []; + } else if (typeof (subs[0]) === 'undefined') { subs = [subs]; } @@ -51,8 +53,6 @@ exports.init = function (cli) { else { for (var index in subs) { log.info('Found subscription:', subs[index]['@'].Name); - //log.info('Profile applies to subscription'); - //log.verbose(' Name:', subs[index]['@'].Name); log.verbose(' Id:', subs[index]['@'].Id); } } @@ -89,7 +89,8 @@ exports.init = function (cli) { config.subscription = subs[0]['@'].Id; account.writeConfig(config); } - + log.warn('The \'' + publishSettingsFile + '\' file contains sensitive information.'); + log.warn('Remember to delete it now that it has been imported.'); log.info('Account publish settings imported successfully'); }); }); @@ -101,17 +102,25 @@ exports.init = function (cli) { .option('-s, --subscription ', 'use the subscription id as the default') .action(function (options) { var config = account.readConfig(); + var writeConfig = false; if (options.subscription) { log.info('Setting default subscription to', options.subscription); config.subscription = options.subscription; - account.writeConfig(config); + writeConfig = true; + } - log.table(config, function (row, name) { - row.cell('Setting', name); - row.cell('Value', config[name]); - }); + if (writeConfig) { + account.writeConfig(config); + log.info('Changes saved'); + } else { + log.info('Showing account settings'); + log.table(config, function (row, name) { + row.cell('Setting', name); + row.cell('Value', config[name]); + }); + } }); account.command('clear') @@ -126,6 +135,7 @@ exports.init = function (cli) { var wazConfigPath = path.join(wazPath, 'config.json'); var config = {}; log.silly('Reading config', wazConfigPath); + if (path.existsSync(wazConfigPath)) { try { config = JSON.parse(fs.readFileSync(wazConfigPath)); @@ -134,6 +144,7 @@ exports.init = function (cli) { log.warn('Unable to read settings'); config = {}; } + log.json('silly', config); } return config; }; @@ -145,10 +156,56 @@ exports.init = function (cli) { fs.writeFileSync(wazConfigPath, JSON.stringify(config)); }; + account.readPublishSettings = function () { + var wazPath = path.join(process.env.HOME, '.azure'); + var wazPublishSettingsFilePath = path.join(wazPath, 'publishSettings.xml'); + + var publishSettings = {}; + + var parser = new xml2js.Parser(); + parser.on('end', function (result) { publishSettings = result; }); + try { + log.silly('Reading publish settings', wazPublishSettingsFilePath); + var readBuffer = fs.readFileSync(wazPublishSettingsFilePath); + parser.parseString(readBuffer); + } catch (err) { + log.error('Unable to read publish settings file'); + log.error(err); + } + + return publishSettings; + } + account.defaultSubscriptionId = function () { return account.readConfig().subscription; }; + account.lookupSubscriptionId = function (subscription) { + // use default subscription if not passed as an argument + if (subscription === undefined) { + subscription = account.readConfig().subscription; + } + + // load and normalize publish settings + var publishSettings = account.readPublishSettings(); + + var subs = publishSettings.PublishProfile.Subscription; + if (subs === 'undefined') { + subs = []; + } else if (typeof (subs[0]) === 'undefined') { + subs = [subs]; + } + + // use subscription id when the subscription name matches + for (var index in subs) { + if (subs[index]['@'].Name === subscription) { + return subs[index]['@'].Id; + } + } + + return subscription; + }; + account.managementCertificate = function () { var wazPath = path.join(process.env.HOME, '.azure'); var wazManagementCertificateFilePath = path.join(wazPath, 'managementCertificate.pem'); diff --git a/lib/cli/commands/site.js b/lib/cli/commands/site.js index f0707a6ae..ebb280b73 100644 --- a/lib/cli/commands/site.js +++ b/lib/cli/commands/site.js @@ -2,11 +2,27 @@ var fs = require('fs'); var pfx2pem = require('../../util/certificates/pkcs').pfx2pem; var Channel = require('../channel'); +var async = require('async'); exports.init = function (cli) { var log = cli.output; + var regions = [ + { + prompt: 'South Central US', + webspace: 'southcentraluswebspace', + location: 'SouthCentralUS1', + plan: 'VirtualDedicatedPlan' + }, + { + prompt: 'North Europe', + webspace: 'northeuropewebspace', + location: 'NorthEurope1', + plan: 'VirtualDedicatedPlan' + }, + ]; + function getChannel() { var pem = cli.category('account').managementCertificate(); @@ -20,79 +36,34 @@ exports.init = function (cli) { return channel; } + var site = cli.category('site') .description('Commands to manage your web sites.'); - site.command('sample') - .description('Sample output from json and table') - .action(function () { - var data = [ - { id: 123123, desc: 'Something awesome', price: 1000.00 }, - { id: 245452, desc: 'Very interesting book', price: 11.45 }, - { id: 232323, desc: 'Yet another product', price: 555.55 } - ]; - - log.json(data); - - log.table(data, function (row, item) { - row.cell('Product Id', item.id); - row.cell('Description', item.desc); - row.cell('Price, USD', item.price.toFixed(2), row.padLeft); - }); - - log.json('verbose', data); - - log.table('verbose', data, function (row, item) { - row.cell('Product Id', item.id); - row.cell('Description', item.desc); - row.cell('Price, USD', item.price.toFixed(2), row.padLeft); - }); - }); site.command('list') .description('List your web sites.') .option('-s, --subscription ', 'use the subscription id') .action(function (options) { - - var subscription = options.subscription || cli.category('account').defaultSubscriptionId(); - log.info('Listing your web sites'); - log.verbose('Subscription ', subscription); - getChannel() - .path(subscription) - .path('services/webspaces/northeuropewebspace/sites/') - .GET(function (err, data) { - if (err) { - log.error(err.Message); - log.verbose('Error', clean(err)); - } - else { + var parameters = { + subscription: cli.category('account').lookupSubscriptionId(options.subscription) + }; - data.Site = isArray(data.Site) ? data.Site : [data.Site]; - log.json('verbose', data); - - log.table(data.Site, function (row, site) { - row.cell('Name', site.Name); - row.cell('State', site.State); - row.cell('Host names', clean(site).HostNames); - }); -// for (var index in data.Site) { -// log.data('Site', { -// Name: data.Site[index].Name, -// State: data.Site[index].State, -// UsageState: data.Site[index].UsageState -// }); -// log.verbose('Site', clean(data.Site[index])); -// } - } + site.doSitesGet( + parameters, + function (sites) { + log.table(sites, function (row, site) { + row.cell('Name', site.Name); + row.cell('State', site.State); + row.cell('Host names', clean(site).HostNames); + }); }); + }); - function isArray(testObject) { - return testObject && !(testObject.propertyIsEnumerable('length')) && typeof testObject === 'object' && typeof testObject.length === 'number'; - } site.command('show ') .description('Show details for a web sites.') @@ -100,7 +71,7 @@ exports.init = function (cli) { .action(function (name, options) { var parameters = { - subscription: options.subscription || cli.category('account').defaultSubscriptionId(), + subscription: cli.category('account').lookupSubscriptionId(options.subscription), site: { name: name } @@ -127,29 +98,43 @@ exports.init = function (cli) { .option('--hostname ', 'custom host name to use') .action(function (name, options) { - var parameters = { - subscription: options.subscription || cli.category('account').defaultSubscriptionId(), - site: { - name: name, - location: options.location, - hostname: options.hostname - } - }; + log.help('Choose a location'); + cli.choose(regions.map(function (x) { return x.prompt; }), function (regionIndex) { - site.doSitesPost(parameters, function () { - site.doRepositoryPost(parameters, function () { - site.doRepositoryGet(parameters, function (repo) { - site.doRepositoryGet(parameters, function (account) { - log.help('To start adding content to the website, type in the following:\n git init\n git add .'); + var parameters = { + subscription: cli.category('account').lookupSubscriptionId(options.subscription), + site: { + name: name, + webspace: regions[regionIndex].webspace, + hostname: options.hostname + } + }; + + site.doSitesPost(parameters, function (err) { + if (err) { + return cli.exit('error', 'Command failed', -1); + } + site.doRepositoryPost(parameters, function (err) { + if (err) { + return cli.exit('error', 'Command failed', -1); + } + site.doRepositoryGet(parameters, function (err, repo) { + if (err) { + return cli.exit('error', 'Command failed', -1); + } + log.help('To start adding content to the website, type in the following:'); log.help(' git init'); log.help(' git add .'); log.help(' git commit -m "initial commit"'); - log.help(' git remote add azure http://{UserName}@repository-{Name}.antintdublin.dnsremap.com/git'); + log.help(' git remote add azure ' + repo + parameters.site.name + '.git'); log.help(' git push azure master'); + + cli.exit('info', 'Success', 0); }); }); }); }); + }); site.command('delete ') @@ -157,7 +142,8 @@ exports.init = function (cli) { .option('-s, --subscription ', 'use the subscription id') .action(function (name, options) { - var subscription = options.subscription || cli.category('account').defaultSubscriptionId(); + var subscription = cli.category('account').lookupSubscriptionId(options.subscription); + getChannel() .path(subscription) @@ -174,7 +160,7 @@ exports.init = function (cli) { .option('-s, --subscription ', 'use the subscription id') .action(function (name, options) { - var subscription = options.subscription || cli.category('account').defaultSubscriptionId(); + var subscription = cli.category('account').lookupSubscriptionId(options.subscription); getChannel() .path(subscription) @@ -183,9 +169,9 @@ exports.init = function (cli) { .header('Content-Type', 'application/xml') .POST(function (req) { req.write(''); - // req.write(''); - // req.write('Running'); - // req.write(''); + req.write(''); + req.write('Running'); + req.write(''); req.write(''); req.end(); @@ -199,7 +185,8 @@ exports.init = function (cli) { .option('-s, --subscription ', 'use the subscription id') .action(function (name, options) { - var subscription = options.subscription || cli.category('account').defaultSubscriptionId(); + var subscription = cli.category('account').lookupSubscriptionId(options.subscription); + getChannel() .path(subscription) @@ -223,112 +210,140 @@ exports.init = function (cli) { ///////////////// // fundamental operations - site.doSitesPost = function (options, done, failed) { + site.doSitesPost = function (options, callback) { log.info('Creating a new web site'); log.verbose('Subscription', options.subscription); - log.verbose('Parameters', options.site); + log.verbose('Webspace', options.site.webspace); + log.verbose('Site', options.site.name); getChannel() .path(options.subscription) - .path('services/webspaces/northeuropewebspace/sites/') + .path('services/webspaces') + .path(options.site.webspace) + .path('sites/') .header('Content-Type', 'application/xml') .POST( writers.Site.xml(options.site), function (err, result) { if (err) { - log.error('Failed to create site'); - log.error(err.Message); - log.verbose('Error', clean(err)); - if (failed) failed(err, result); + logError('Failed to create site', err); } else { log.info('Created website at ', clean(result).HostNames); log.verbose('Site', clean(result)); - if (done) done(result); } + callback(err, result); }); }; - site.doRepositoryPost = function (options, done, failed) { + site.doRepositoryPost = function (options, callback) { log.info('Initializing repository'); log.verbose('Subscription', options.subscription); + log.verbose('Webspace', options.site.webspace); log.verbose('Site', options.site.name); getChannel() .path(options.subscription) - .path('services/webspaces/northeuropewebspace/sites/') + .path('services/webspaces') + .path(options.site.webspace) + .path('sites') .path(options.site.name) .path('repository') .POST( "", function (err, result) { if (err) { - log.error('Failed to initialize repository'); - log.error(err.Message); - log.verbose('Error', clean(err)); - if (failed) failed(err, result); + logError('Failed to initialize repository', err); } else { log.info('Repository initialized'); - if (done) done(result); } + callback(err, result); }); }; - site.doSiteGet = function (options, done, failed) { + site.doSitesGet = function (options, callback) { + log.verbose('Subscription', options.subscription); + + var channel = getChannel() + .path(options.subscription) + .path('services/webspaces'); + + async.map( + regions, + function (region, result) { + channel + .path(region.webspace) + .path('sites/') + .GET(result); + }, + function (err, result) { + if (err) { + logError('Failed to get site info', err); + } else { + var sites = []; + result.forEach(function (item) { + sites = sites.concat(toArray(item.Site)); + }); + result = sites; + + log.json('verbose', result); + } + callback(err, result); + }); + }; + + site.doSiteGet = function (options, callback) { getChannel() .path(options.subscription) - .path('services/webspaces/northeuropewebspace/sites') + .path('services/webspaces') + .path(options.site.webspace) + .path('sites') .path(options.site.name) .GET( function (err, result) { if (err) { - log.error('Failed to get site info'); - log.error(err.Message); - log.verbose('Error', clean(err)); - if (failed) failed(err, result); + logError('Failed to get site info', err); } else { log.verbose('Site', clean(result)); - if (done) done(result); } + callback(err, result); }); }; - site.doSiteConfigGet = function (options, done, failed) { + site.doSiteConfigGet = function (options, callback) { getChannel() .path(options.subscription) - .path('services/webspaces/northeuropewebspace/sites') + .path('services/webspaces') + .path(options.site.webspace) + .path('sites') .path(options.site.name) .path('config') .GET( function (err, result) { if (err) { - log.error('Failed to get site config info'); - log.error(err.Message); - log.verbose('Error', clean(err)); - if (failed) failed(err, result); + logError('Failed to get site config info', err); } else { log.verbose('SiteConfig', clean(result)); - if (done) done(result); } + callback(err, result); }); }; - site.doRepositoryGet = function (options, done, failed) { + site.doRepositoryGet = function (options, callback) { getChannel() .path(options.subscription) - .path('services/webspaces/northeuropewebspace/sites') + .path('services/webspaces') + .path(options.site.webspace) + .path('sites') .path(options.site.name) .path('repository') .GET( function (err, result) { if (err) { - log.error('Failed to get repository info'); - log.error(err.Message); - log.verbose('Error', clean(err)); - if (failed) failed(err, result); + logError('Failed to get repository info', err); } else { log.verbose('Repository', clean(result)); - if (done) done(result); } + callback(err, result); }); }; @@ -344,11 +359,8 @@ exports.init = function (cli) { req.write(''); req.write(''); req.write(site.name + '.antdir0.antares-test.windows-int.net'); - req.write(''); - // req.write(''); - // req.write('www.' + site.name + '.antint0.antares-test.windows-int.net'); - // req.write(''); + if (site.hostname) { req.write(''); req.write(site.hostname); @@ -401,4 +413,31 @@ exports.init = function (cli) { log.data(title + ' ' + property, cleaned[property]); } } + + function logError(message, err) { + log.error(message); + if (err) { + if (err.message) { + log.error(err.message); + log.verbose('stack', err.stack); + log.json('silly', err); + } + else if (err.Message) { + log.error(err.Message); + log.json('verbose', clean(err)); + } + else { + log.error(err); + } + } + } + + function isArray(testObject) { + return testObject && !(testObject.propertyIsEnumerable('length')) && typeof testObject === 'object' && typeof testObject.length === 'number'; + } + + function toArray(testObject) { + return isArray(testObject) ? testObject : typeof testObject === 'undefined' ? [] : [testObject]; + } + }; diff --git a/lib/cli/commands/space.js b/lib/cli/commands/space.js index 2433d80f3..d8bd664f4 100644 --- a/lib/cli/commands/space.js +++ b/lib/cli/commands/space.js @@ -27,7 +27,7 @@ exports.init = function (waz) { .option('-s, --subscription ', 'use the subscription id') .action(function (name, options) { - var subscription = options.subscription || waz.category('account').defaultSubscriptionId(); + var subscription = cli.category('account').lookupSubscriptionId(options.subscription); getChannel() .header('x-ms-version', '2011-02-25') @@ -79,7 +79,7 @@ exports.init = function (waz) { req.write(name); req.write(''); req.write(''); - req.write(options.plan || 'Plan 1'); + req.write(options.plan || 'VirtualDedicatedPlan'); req.write(''); req.write(''); req.write(subscription); diff --git a/package.json b/package.json index 74497bc36..aa7fc8e70 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "underscore": ">= 1.3.1", "underscore.string": ">= 2.0.0", "tunnel": ">= 0.0.1", + "async": ">= 0.1.18", "commander": ">= 0.5.2", "winston": ">= 0.5.10", "colors": "0.x.x",