Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Merge branch 'develop' into feature/plugin_system

Conflicts:
	SConstruct
	lib/http/constants.js
	lib/util/misc.js
	package.json
	tests/simple/util-misc.js
  • Loading branch information...
commit dcf50b4426b6f6e20315eb428281dd340cc56d06 2 parents c41fab3 + c15a443
@Kami Kami authored
Showing with 25,867 additions and 2,791 deletions.
  1. +458 −0 lib/bundles/index.js
  2. +39 −39 lib/cast-agent/init.js
  3. +170 −0 lib/cast-agent/managers.js
  4. +11 −44 lib/cast-client/commands/bundles/delete.js
  5. +44 −41 lib/cast-client/commands/bundles/list.js
  6. +7 −17 lib/cast-client/commands/ca/delete.js
  7. +9 −4 lib/cast-client/commands/ca/list.js
  8. +7 −17 lib/cast-client/commands/ca/sign.js
  9. +1 −12 lib/cast-client/commands/instances/create.js
  10. +1 −13 lib/cast-client/commands/instances/destroy.js
  11. +8 −23 lib/cast-client/commands/instances/upgrade.js
  12. +75 −0 lib/cast-client/commands/jobs/list.js
  13. +2 −14 lib/cast-client/commands/services/service_cmd.js
  14. +3 −2 lib/cast-client/commands/services/tail.js
  15. +2 −1  lib/cast-client/parser.js
  16. +96 −0 lib/control/bundles.js
  17. +161 −0 lib/control/ca.js
  18. +32 −0 lib/control/facts.js
  19. +137 −0 lib/control/health.js
  20. +25 −0 lib/control/index.js
  21. +51 −0 lib/control/info.js
  22. +169 −0 lib/control/instances.js
  23. +44 −0 lib/control/jobs.js
  24. +142 −0 lib/control/services.js
  25. +254 −202 lib/deployment/instances.js
  26. +13 −4 lib/http/constants.js
  27. +69 −495 lib/http/endpoints/bundles.js
  28. +19 −144 lib/http/endpoints/ca.js
  29. +6 −8 lib/http/endpoints/facts.js
  30. +9 −39 lib/http/endpoints/health.js
  31. +7 −29 lib/http/endpoints/info.js
  32. +31 −134 lib/http/endpoints/instances.js
  33. +63 −0 lib/http/endpoints/jobs.js
  34. +93 −137 lib/http/endpoints/services.js
  35. +3 −2 lib/http/middleware/authentication.js
  36. +1 −1  lib/http/middleware/required-params.js
  37. +55 −0 lib/jobs/errors.js
  38. +3 −0  lib/jobs/index.js
  39. +82 −19 lib/jobs/job.js
  40. +27 −4 lib/jobs/manager.js
  41. +284 −36 lib/jobs/resource.js
  42. +191 −363 lib/security/ca.js
  43. +60 −19 lib/service_management/base.js
  44. +46 −22 lib/service_management/mock/index.js
  45. +85 −152 lib/service_management/runit/index.js
  46. +3 −2 lib/services/health.js
  47. +1 −1  lib/services/http.js
  48. +105 −0 lib/tempfiles/index.js
  49. +2 −0  lib/util/config.js
  50. +103 −0 lib/util/hashedstream.js
  51. +232 −55 lib/util/http.js
  52. +9 −0 lib/util/misc.js
  53. +30 −4 lib/util/test.js
  54. +2 −0  node_modules/swiz/.npmignore
  55. +203 −0 node_modules/swiz/LICENSE.txt
  56. +16 −0 node_modules/swiz/Makefile
  57. +17 −0 node_modules/swiz/README.md
  58. +35 −0 node_modules/swiz/demo.js
  59. +12 −0 node_modules/swiz/ftest.js
  60. +105 −0 node_modules/swiz/lib/bitbuffer.js
  61. +126 −0 node_modules/swiz/lib/cidr.js
  62. +343 −0 node_modules/swiz/lib/serializer.js
  63. +30 −0 node_modules/swiz/lib/swiz.js
  64. +1,097 −0 node_modules/swiz/lib/valve.js
  65. +9 −0 node_modules/swiz/node_modules/async/.gitmodules
  66. +19 −0 node_modules/swiz/node_modules/async/LICENSE
  67. +21 −0 node_modules/swiz/node_modules/async/Makefile
  68. +970 −0 node_modules/swiz/node_modules/async/README.md
  69. BIN  node_modules/swiz/node_modules/async/async.min.js.gzip
  70. +3 −0  node_modules/swiz/node_modules/async/index.js
  71. +632 −0 node_modules/swiz/node_modules/async/lib/async.js
  72. +4 −0 node_modules/swiz/node_modules/async/nodelint.cfg
  73. +16 −0 node_modules/swiz/node_modules/async/package.json
  74. +1,367 −0 node_modules/swiz/node_modules/async/test/test-async.js
  75. +24 −0 node_modules/swiz/node_modules/async/test/test.html
  76. +2 −0  node_modules/swiz/node_modules/ipv6/.gitignore
  77. +19 −0 node_modules/swiz/node_modules/ipv6/LICENSE
  78. +37 −0 node_modules/swiz/node_modules/ipv6/README.md
  79. BIN  node_modules/swiz/node_modules/ipv6/images/alert-overlay.png
  80. +98 −0 node_modules/swiz/node_modules/ipv6/index.html
  81. +1 −0  node_modules/swiz/node_modules/ipv6/index.js
  82. +380 −0 node_modules/swiz/node_modules/ipv6/ipv6.js
  83. +48 −0 node_modules/swiz/node_modules/ipv6/lib/browser/diff_match_patch.js
  84. +18 −0 node_modules/swiz/node_modules/ipv6/lib/browser/jquery.ba-bbq.min.js
  85. +1,211 −0 node_modules/swiz/node_modules/ipv6/lib/browser/jsbn-combined.js
  86. +559 −0 node_modules/swiz/node_modules/ipv6/lib/browser/jsbn.js
  87. +648 −0 node_modules/swiz/node_modules/ipv6/lib/browser/jsbn2.js
  88. +480 −0 node_modules/swiz/node_modules/ipv6/lib/browser/json2.js
  89. +183 −0 node_modules/swiz/node_modules/ipv6/lib/browser/sprintf.js
  90. +1,255 −0 node_modules/swiz/node_modules/ipv6/lib/node/bigint.js
  91. +21 −0 node_modules/swiz/node_modules/ipv6/package.json
  92. +148 −0 node_modules/swiz/node_modules/ipv6/test/ipv6-test.js
  93. +164 −0 node_modules/swiz/node_modules/ipv6/v6decode.css
  94. +189 −0 node_modules/swiz/node_modules/ipv6/v6decode.js
  95. +1 −0  node_modules/swiz/node_modules/validator/.gitignore
  96. +20 −0 node_modules/swiz/node_modules/validator/LICENSE
  97. +166 −0 node_modules/swiz/node_modules/validator/README.md
  98. +15 −0 node_modules/swiz/node_modules/validator/index.html
  99. +1 −0  node_modules/swiz/node_modules/validator/index.js
  100. +291 −0 node_modules/swiz/node_modules/validator/lib/entities.js
  101. +89 −0 node_modules/swiz/node_modules/validator/lib/filter.js
  102. +15 −0 node_modules/swiz/node_modules/validator/lib/index.js
  103. +207 −0 node_modules/swiz/node_modules/validator/lib/validator.js
  104. +201 −0 node_modules/swiz/node_modules/validator/lib/xss.js
  105. +27 −0 node_modules/swiz/node_modules/validator/package.json
  106. +134 −0 node_modules/swiz/node_modules/validator/test/filter.test.js
  107. +396 −0 node_modules/swiz/node_modules/validator/test/validator.test.js
  108. +23 −0 node_modules/swiz/node_modules/validator/validator-min.js
  109. +819 −0 node_modules/swiz/node_modules/validator/validator.js
  110. +40 −0 node_modules/swiz/package.json
  111. +102 −0 node_modules/swiz/tests/test-bitbuffer.js
  112. +58 −0 node_modules/swiz/tests/test-cidr.js
  113. +294 −0 node_modules/swiz/tests/test-swiz.js
  114. +739 −0 node_modules/swiz/tests/test-valve.js
  115. +135 −0 node_modules/swiz/timeswiz.js
  116. +6 −0 node_modules/whiskey/CHANGES.md
  117. +304 −19 node_modules/whiskey/assets/coverage_html.js
  118. +166 −0 node_modules/whiskey/assets/jquery-1.4.3.min.js
  119. +99 −0 node_modules/whiskey/assets/jquery.hotkeys.js
  120. +53 −0 node_modules/whiskey/assets/jquery.isonscreen.js
  121. BIN  node_modules/whiskey/assets/keybd_closed.png
  122. BIN  node_modules/whiskey/assets/keybd_open.png
  123. +45 −0 node_modules/whiskey/assets/style.css
  124. +28 −10 node_modules/whiskey/assets/whiskey.magic
  125. +34 −9 node_modules/whiskey/assets/whiskey_source.magic
  126. +37 −0 node_modules/whiskey/example/test-getFilePathAndPattern.js
  127. +42 −8 node_modules/whiskey/lib/common.js
  128. +1 −1  node_modules/whiskey/lib/constants.js
  129. +1 −1  node_modules/whiskey/lib/reporters/coverage/cli.js
  130. +6 −2 node_modules/whiskey/lib/reporters/coverage/html.js
  131. +8 −4 node_modules/whiskey/lib/run.js
  132. +56 −26 node_modules/whiskey/lib/run_test_file.js
  133. +24 −0 node_modules/whiskey/lib/util.js
  134. +84 −0 node_modules/whiskey/node_modules/gex/README.md
  135. +82 −0 node_modules/whiskey/node_modules/gex/lib/gex.js
  136. +4 −0 node_modules/whiskey/node_modules/gex/node_modules/underscore/.npmignore
  137. +22 −0 node_modules/whiskey/node_modules/gex/node_modules/underscore/LICENSE
  138. +19 −0 node_modules/whiskey/node_modules/gex/node_modules/underscore/README
  139. +1,560 −0 node_modules/whiskey/node_modules/gex/node_modules/underscore/index.html
  140. +1 −0  node_modules/whiskey/node_modules/gex/node_modules/underscore/index.js
  141. +12 −0 node_modules/whiskey/node_modules/gex/node_modules/underscore/package.json
  142. +807 −0 node_modules/whiskey/node_modules/gex/node_modules/underscore/underscore.js
  143. +22 −0 node_modules/whiskey/node_modules/gex/package.json
  144. +1 −0  node_modules/whiskey/node_modules/magic-templates/.npmignore
  145. +82 −0 node_modules/whiskey/node_modules/magic-templates/README.md
  146. +125 −0 node_modules/whiskey/node_modules/magic-templates/context.js
  147. +20 −0 node_modules/whiskey/node_modules/magic-templates/exceptions.js
  148. +113 −0 node_modules/whiskey/node_modules/magic-templates/extensions.js
  149. +37 −0 node_modules/whiskey/node_modules/magic-templates/index.js
  150. +18 −0 node_modules/whiskey/node_modules/magic-templates/package.json
  151. +24 −0 node_modules/whiskey/node_modules/magic-templates/tags/block.js
  152. +53 −0 node_modules/whiskey/node_modules/magic-templates/tags/extends.js
  153. +97 −0 node_modules/whiskey/node_modules/magic-templates/tags/for.js
  154. +146 −0 node_modules/whiskey/node_modules/magic-templates/tags/if.js
  155. +61 −0 node_modules/whiskey/node_modules/magic-templates/tags/include.js
  156. +6 −0 node_modules/whiskey/node_modules/magic-templates/tags/index.js
  157. +43 −0 node_modules/whiskey/node_modules/magic-templates/template.js
  158. +239 −0 node_modules/whiskey/node_modules/magic-templates/templateproto.js
  159. +10 −0 node_modules/whiskey/node_modules/magic-templates/tests/base.html
  160. +21 −0 node_modules/whiskey/node_modules/magic-templates/tests/forloop.js
  161. +4 −0 node_modules/whiskey/node_modules/magic-templates/tests/included.html
  162. +34 −0 node_modules/whiskey/node_modules/magic-templates/tests/page.html
  163. +78 −0 node_modules/whiskey/node_modules/magic-templates/tests/run.js
  164. +54 −0 node_modules/whiskey/node_modules/magic-templates/tests/run2.js
  165. +2 −0  node_modules/whiskey/node_modules/magic-templates/tests/test.html
  166. +29 −0 node_modules/whiskey/node_modules/magic-templates/tests/test_forloop.html
  167. +204 −0 node_modules/whiskey/node_modules/magic-templates/tokens.js
  168. +14 −0 node_modules/whiskey/node_modules/sprintf/README.md
  169. +189 −0 node_modules/whiskey/node_modules/sprintf/lib/sprintf.js
  170. +14 −0 node_modules/whiskey/node_modules/sprintf/package.json
  171. +3 −2 node_modules/whiskey/package.json
  172. +17 −0 node_modules/whiskey/test/run.sh
  173. +2 −1  package.json
  174. +8 −0 tests/assert.js
  175. 0  tests/{simple → client-commands}/client-commands-deploy.js
  176. +332 −0 tests/control/test-bundles.js
  177. +92 −53 tests/{simple/security-ca.js → control/test-ca.js}
  178. +39 −0 tests/control/test-facts.js
  179. +114 −0 tests/control/test-health.js
  180. +49 −0 tests/control/test-info.js
  181. +203 −0 tests/control/test-instances.js
  182. +89 −0 tests/control/test-jobs.js
  183. +154 −0 tests/control/test-services.js
  184. +114 −0 tests/endpoints/mockjobs.js
  185. +346 −0 tests/endpoints/test-bundles.js
  186. +154 −0 tests/endpoints/test-ca.js
  187. +31 −0 tests/endpoints/test-endpoints.js
  188. +42 −0 tests/endpoints/test-facts.js
  189. +135 −0 tests/endpoints/test-health.js
  190. +42 −0 tests/endpoints/test-info.js
  191. +282 −0 tests/endpoints/test-instances.js
  192. +148 −0 tests/endpoints/test-jobs.js
  193. +363 −0 tests/endpoints/test-services.js
  194. +2 −2 tests/init-dist.js
  195. +0 −343 tests/simple/http-bundles.js
  196. +0 −211 tests/simple/http-endpoints-ca.js
Sorry, we could not display the entire diff because it was too big.
View
458 lib/bundles/index.js
@@ -0,0 +1,458 @@
+/*
+ * Licensed to Cloudkick, Inc ('Cloudkick') under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * Cloudkick licenses this file to You 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 fs = require('fs');
+var path = require('path');
+var util = require('util');
+
+var sprintf = require('sprintf').sprintf;
+var async = require('async');
+
+var crypto = require('crypto');
+var manifest = require('manifest');
+var config = require('util/config');
+var Errorf = require('util/misc').Errorf;
+var fsutil = require('util/fs');
+var locking = require('util/locking');
+var extractTarball = require('util/tarball').extractTarball;
+var HashedStream = require('util/hashedstream').HashedStream;
+var jobs = require('jobs');
+var managers = require('cast-agent/managers');
+
+
+/**
+ * Given the name of an application and the name of a bundle file, verify that
+ * they match and return the version of the bundle file if so.
+ * @param {String} app The name of the bundle application.
+ * @param {String} file The name of the file.
+ * @return {String|Boolean} The version of the bundle, or false if the name
+ * and file do not match.
+ */
+function getBundleVersion(app, file) {
+ var pattern = sprintf('^%s@(.*).tar.gz$', app);
+ var result = file.match(pattern);
+
+ if (!result) {
+ return false;
+ } else {
+ return result[1];
+ }
+}
+
+
+/**
+ * Retrieve the name of a bundle.
+ * @param {String} name The name of the application.
+ * @param {String} version The version of the bundle.
+ * @return {String} The name of the bundle.
+ */
+function getBundleName(name, version) {
+ return sprintf('%s@%s', name, version);
+}
+
+
+/**
+ * Manage application bundles.
+ * @constructor
+ */
+function BundleManager() {
+ locking.Lockable.call(this);
+ var conf = config.get();
+ this._bundleRoot = conf['bundle_dir'];
+ this._extractedRoot = conf['extracted_dir'];
+ this._inProgress = {};
+}
+
+util.inherits(BundleManager, locking.Lockable);
+
+
+/**
+ * Initialize directories used by the BundleManager.
+ * @param {Function} callback A callback fired with (err).
+ */
+BundleManager.prototype.init = function(callback) {
+ async.parallel([
+ async.apply(fsutil.ensureDirectory, this._bundleRoot),
+ async.apply(fsutil.ensureDirectory, this._extractedRoot)
+ ], callback);
+};
+
+
+/**
+ * Retrieve an application's bundle path.
+ * @param {String} name The name of the application.
+ * @return {String} The application's bundle path.
+ */
+BundleManager.prototype.getAppBundlePath = function(name) {
+ return path.join(this._bundleRoot, name);
+};
+
+
+/**
+ * Retrieve an application's extracted path.
+ * @param {String} name The name of the application.
+ * @return {String} The application's extracted path.
+ */
+BundleManager.prototype.getAppExtractedPath = function(name) {
+ return path.join(this._extractedRoot, name);
+};
+
+
+/**
+ * Retrieve the file name of a bundle.
+ * @param {String} name The name of the application.
+ * @param {String} version The version of the bundle.
+ * @return {String} The name of the bundle file.
+ */
+BundleManager.prototype.getBundleFileName = function(name, version) {
+ return sprintf('%s.tar.gz', getBundleName(name, version));
+};
+
+
+/**
+ * Retrieve the path to a bundle file.
+ * @param {String} name The name of the application.
+ * @param {String} version The version of the bundle.
+ * @return {String} The path to the bundle file.
+ */
+BundleManager.prototype.getBundleFilePath = function(name, version) {
+ var appBundlePath = this.getAppBundlePath(name);
+ var bundleFileName = this.getBundleFileName(name, version);
+ return path.join(appBundlePath, bundleFileName);
+};
+
+
+/**
+ * Retrieve the path to an extracted bundle.
+ * @param {String} name The name of the application.
+ * @param {String} version The version of the bundle.
+ * @return {String} The path to the extracted bundle.
+ */
+BundleManager.prototype.getExtractedDirPath = function(name, version) {
+ var appExtractedPath = this.getAppExtractedPath(name);
+ var bundleName = getBundleName(name, version);
+ return path.join(appExtractedPath, bundleName);
+};
+
+
+/**
+ * Check whether a bundle exists.
+ * @param {String} name The name of the application to check.
+ * @param {String} version The version of the bundle to check.
+ * @param {Function} callback A callback fired with (exists).
+ */
+BundleManager.prototype.bundleExists = function(name, version, callback) {
+ path.exists(this.getBundleFilePath(name, version), callback);
+};
+
+
+/**
+ * Ensure the directories necessary to accept bundles for an application
+ * with the specified name.
+ * @param {String} name The name of the application to ensure.
+ * @param {Function} callback A callback fired with (err).
+ */
+BundleManager.prototype.ensureApplication = function(name, callback) {
+ var self = this;
+
+ function ensureAppBundleDir(callback) {
+ fsutil.ensureDirectory(self.getAppBundlePath(name), callback);
+ }
+
+ function ensureAppExtractedDir(callback) {
+ fsutil.ensureDirectory(self.getAppExtractedPath(name), callback);
+ }
+
+ async.parallel([ensureAppBundleDir, ensureAppExtractedDir], callback);
+};
+
+
+/**
+ * Remove an application's directories if they are empty.
+ * @param {String} name The name of the application to clean.
+ * @param {Function} callback A callback fired with (err).
+ */
+BundleManager.prototype.cleanApplication = function(name, callback) {
+ var self = this;
+
+ function cleanAppBundleDir(callback) {
+ fs.rmdir(self.getAppBundlePath(name), function(err) {
+ if (err && err.code === 'ENOTEMPTY') {
+ err = undefined;
+ }
+ callback(err);
+ });
+ }
+
+ function cleanAppExtractedDir(callback) {
+ fs.rmdir(self.getAppExtractedPath(name), function(err) {
+ if (err && err.code === 'ENOTEMPTY') {
+ err = undefined;
+ }
+ callback(err);
+ });
+ }
+
+ // TODO: This is liable to cause problems on Windows
+ async.parallel([cleanAppBundleDir, cleanAppExtractedDir], callback);
+};
+
+
+/**
+ * Add a bundle from a streamed tarball.
+ * @param {String} name The name of the bundle's application.
+ * @param {String} version The version of the bundle.
+ * @param {stream.Stream} iStream A stream to read the bundle from.
+ * @param {Object} opts An (optional) object that may contain a 'getSHA1'
+ * 'getSHA1' property, which is a function taking a callback that takes
+ * (err, sha1) - make sense?
+ * @param {Function} callback A callback fired with (err).
+ */
+BundleManager.prototype.add = function(name, version, iStream, opts, callback) {
+ if (!callback) {
+ callback = opts;
+ opts = {};
+ }
+
+ var self = this;
+ var tmpFileManager = managers.getManager('TempFileManager');
+ var bundleName = getBundleName(name, version);
+
+ if (this._inProgress[bundleName]) {
+ callback(new Errorf('Upload already in progress for %s', bundleName));
+ return;
+ }
+
+ // Permanent paths for bundle and extracted
+ var bundleFilePath = this.getBundleFilePath(name, version);
+ var extractedDirPath = this.getExtractedDirPath(name, version);
+
+ // Temporary paths for bundle and extracted
+ var tmpBundle = tmpFileManager.allocate('.tar.gz');
+ var tmpExtracted = tmpFileManager.allocate('');
+ var tmpManifest = path.join(tmpExtracted, 'cast.json');
+
+ this._inProgress[bundleName] = true;
+
+ // Pipe the incoming stream to a paused HashedStream
+ var hs = new HashedStream('sha1');
+ hs.pause();
+ iStream.pipe(hs);
+
+ async.series([
+ // Make sure no such bundle already exists
+ // Note: by this point we have an in-memory lock on this bundle name
+ function checkExists(callback) {
+ self.bundleExists(name, version, function(exists) {
+ if (exists) {
+ callback(new jobs.AlreadyExistsError('Bundle', bundleName));
+ } else {
+ callback();
+ }
+ });
+ },
+
+ // Receive the actual stream
+ function receiveStream(callback) {
+ var fstream = fs.createWriteStream(tmpBundle);
+ var received;
+
+ hs.on('hash', function(sha1) {
+ received = sha1.digest('base64');
+ });
+
+ fstream.on('error', function(err) {
+ callback(err);
+ });
+
+ fstream.on('close', function() {
+ if (opts.getSHA1) {
+ opts.getSHA1(function(err, expected) {
+ if (!err && received !== expected) {
+ err = new Error('SHA1 mismatch');
+ err.responseCode = 400;
+ }
+ callback(err);
+ return;
+ });
+ } else {
+ callback();
+ }
+ });
+
+ hs.pipe(fstream);
+ hs.resume();
+ },
+
+ // Extract the tarball to a temporary directory
+ async.apply(extractTarball, tmpBundle, tmpExtracted, 0755),
+
+ // Validate the manifest
+ function(callback) {
+ manifest.validateManifest(tmpManifest, function(err, manifestObject) {
+ if (err) {
+ err.responseCode = 400;
+ }
+ callback(err);
+ return;
+ });
+ },
+
+ // Lock the BundleManager while we operate on the actual bundle directories
+ self.withLock.bind(self),
+
+ // Make sure the application directories exist
+ self.ensureApplication.bind(self, name),
+
+ // Swap the new tarball into place
+ async.apply(fs.rename, tmpBundle, bundleFilePath),
+
+ // Swap the new extracted directory into place
+ async.apply(fs.rename, tmpExtracted, extractedDirPath)
+ ],
+ function(err) {
+ tmpFileManager.free(tmpBundle);
+ tmpFileManager.free(tmpExtracted);
+ delete self._inProgress[bundleName];
+ self.cleanApplication(name, function() {
+ // Release the BundleManager lock
+ self.releaseLock();
+ callback(err);
+ });
+ });
+};
+
+
+/**
+ * Retrieve a stream containing a bundle file.
+ * @param {String} name The name of the application.
+ * @param {String} version The version of the application.
+ * @param {Function} callback A callback fired with (err, version).
+ */
+BundleManager.prototype.getBundle = function(name, version, callback) {
+ var bundleName = getBundleName(name, version);
+ var bundleFilePath = this.getBundleFilePath(name, version);
+ var oStream = fs.createReadStream(bundleFilePath);
+
+ function onError(err) {
+ if (err && err.code === 'ENOENT') {
+ err = new jobs.NotFoundError('Bundle', bundleName);
+ }
+ callback(err);
+ }
+
+ oStream.on('error', onError);
+
+ oStream.on('open', function() {
+ oStream.removeListener('error', callback);
+ callback(null, oStream);
+ });
+};
+
+
+/**
+ * Remove a bundle.
+ * @param {String} name The name of the application to remove the bundle for.
+ * @param {String} version The version of the bundle to remove.
+ * @param {Function} callback A callback fired with (err).
+ */
+BundleManager.prototype.remove = function(name, version, callback) {
+ var self = this;
+ var tmpFileManager = managers.getManager('TempFileManager');
+ var tmpExtractedPath = tmpFileManager.allocate('');
+ var bundleName = getBundleName(name, version);
+ var bundleFilePath = this.getBundleFilePath(name, version);
+ var extractedDirPath = this.getExtractedDirPath(name, version);
+
+ // Note: we rename() the extracted bundle into the temporary directory before
+ // we remove it so that there will never be a half-removed extracted bundle.
+ async.series([
+ // Lock the BundleManager while we operate on live bundle directories
+ self.withLock.bind(self),
+
+ // Move extracted bundle to temporary directory
+ function(callback) {
+ fs.rename(extractedDirPath, tmpExtractedPath, function(err) {
+ if (err && err.code === 'ENOENT') {
+ err = new jobs.NotFoundError('Bundle', bundleName);
+ }
+ callback(err);
+ });
+ },
+
+ // Remove bundle file
+ async.apply(fs.unlink, bundleFilePath),
+
+ // Clean bundle directories, unlock
+ function(callback) {
+ self.cleanApplication(name, function(err) {
+ self.releaseLock();
+ callback(err);
+ });
+ },
+
+ // Remove the extracted bundle from tmp at our leisure
+ async.apply(fsutil.rmtree, tmpExtractedPath)
+ ], callback);
+};
+
+
+/**
+ * Retrieve data about an application.
+ * {
+ * 'name: 'foo',
+ * 'bundles': [
+ * 'foo@v1.0.tar.gz',
+ * 'foo@v2.0.tar.gz'
+ * ]
+ * }
+ * @param {String} name The name of the application.
+ * @param {Function} callback A callback fired with (err, files).
+ */
+BundleManager.prototype.getApplication = function(name, callback) {
+ var bundlePath = this.getAppBundlePath(name);
+ fs.readdir(bundlePath, function(err, files) {
+ if (err && err.code === 'ENOENT') {
+ callback(new jobs.NotFoundError('BundleApplication', name));
+ } else if (err) {
+ callback(err);
+ } else {
+ callback(null, {
+ name: name,
+ bundles: files
+ });
+ }
+ });
+};
+
+
+/**
+ * Retrieve a list of data for all applications.
+ * @param {Function} callback A callback fired with (err, apps).
+ */
+BundleManager.prototype.listApplications = function(callback) {
+ var self = this;
+
+ fs.readdir(this._bundleRoot, function(err, files) {
+ async.map(files, self.getApplication.bind(self), callback);
+ });
+};
+
+
+exports.getBundleVersion = getBundleVersion;
+exports.getBundleName = getBundleName;
+exports.BundleManager = BundleManager;
View
78 lib/cast-agent/init.js
@@ -24,11 +24,14 @@ var async = require('async');
var sprintf = require('sprintf').sprintf;
var config = require('util/config');
+var misc = require('util/misc');
+var flowctrl = require('util/flow_control');
var fsutil = require('util/fs');
var certgen = require('security/certgen.js');
var norris = require('norris');
-var ca = require('security/ca');
var dotfiles = require('util/client_dotfiles');
+var managers = require('cast-agent/managers');
+var control = require('control');
/*
* Function which determines if this is the first start of the agent.
@@ -123,34 +126,11 @@ function installLocalRemote(callback) {
dotfiles.saveGlobalRemotes(remotes, callback);
},
- // If SSL is enabled, generate a CSR, add it to the CA, and sign it
- // Note: it is unnecessary to retrieve the cert from the CA, as this will
- // be automatically done by the client the first time it is used.
- function(callback) {
- var castCA = null;
-
- function onceAdded(err, reqStatus) {
- if (err) {
- callback(err);
- } else {
- castCA.signRequest('localhost', false, callback);
- }
- }
-
- function withCSR(err, csrBuf) {
- if (err) {
- callback(err);
- } else {
- castCA.addRequest('localhost', csrBuf.toString('utf8'), onceAdded);
- }
- }
-
- function onceGenerated(err) {
- if (err) {
- callback(err);
- } else {
- dotfiles.loadRemoteCSR(remote, withCSR);
- }
+ // Generate a CSR (if SSL is enabled)
+ function generateCSR(callback) {
+ if (!conf['ssl_enabled']) {
+ callback();
+ return;
}
function reportCertOpts(callback) {
@@ -163,12 +143,36 @@ function installLocalRemote(callback) {
});
}
- if (conf['ssl_enabled']) {
- castCA = ca.getCA();
- dotfiles.ensureRemoteCSR(remote, reportCertOpts, onceGenerated);
+ dotfiles.ensureRemoteCSR(remote, reportCertOpts, callback);
+
+ },
+
+ // Load the CSR (if SSL is enabled)
+ function loadCSR(callback) {
+ if (!conf['ssl_enabled']) {
+ callback(null, null);
} else {
+ dotfiles.loadRemoteCSR(remote, callback);
+ }
+ },
+
+ // Submit and sign the CSR (if SSL is enabled)
+ function addAndSign(csrBuf, callback) {
+ if (!conf['ssl_enabled']) {
callback();
+ return;
}
+
+ callback = flowctrl.fireOnce(callback);
+
+ var a = control.ca.createRequest('localhost', csrBuf.toString('utf8'));
+ var s = control.ca.signRequest('localhost');
+
+ a.on('error', callback);
+ s.on('error', callback);
+ s.on('success', function(results) {
+ callback();
+ });
}
], callback);
}
@@ -202,10 +206,7 @@ function initialize(callback) {
pathsToCreate = [
conf['data_dir'],
- conf['service_dir_available'],
- conf['bundle_dir'],
- conf['extracted_dir'],
- conf['app_dir']
+ conf['service_dir_available']
];
async.forEachSeries(pathsToCreate, fsutil.ensureDirectory, callback);
@@ -229,9 +230,8 @@ function initialize(callback) {
});
});
- ops.push(function(callback) {
- ca.getCA().init(callback);
- });
+ // Initialize job managers
+ ops.push(managers.initManagers);
// Install a remote for local use on the first run
if (firstRun) {
View
170 lib/cast-agent/managers.js
@@ -0,0 +1,170 @@
+/*
+ * Licensed to Cloudkick, Inc ('Cloudkick') under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * Cloudkick licenses this file to You 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 async = require('async');
+var sprintf = require('sprintf').sprintf;
+var swiz = require('swiz');
+
+var jobs = require('jobs');
+var merge = require('util/misc').merge;
+
+/**
+ * ResourceManagers that should be instantiated, initialized and registered
+ * with the job manager.
+ */
+var RESOURCE_MANAGERS = [
+ {
+ module: 'security/ca',
+ type: 'SigningRequestManager'
+ },
+ {
+ module: 'deployment/instances',
+ type: 'InstanceManager'
+ }
+];
+
+/**
+ * Managers other than resource managers. A manager is essentially any class
+ * that that we should create a single agent-wide instance of, and must provide
+ * an init method.
+ */
+var GENERAL_MANAGERS = [
+ {
+ module: 'tempfiles',
+ type: 'TempFileManager'
+ },
+ {
+ module: 'bundles',
+ type: 'BundleManager'
+ }
+];
+
+
+/**
+ * The Cast Agent's JobManager.
+ */
+var jobManager = null;
+
+
+/**
+ * The Cast Agent's ResourceManagers.
+ */
+var managers = {};
+
+
+/**
+ * The serializer's definitions.
+ */
+var serializerDefs = {};
+
+
+/**
+ * The Cast Agent's Swiz instance.
+ */
+var serializer = new swiz.Swiz(serializerDefs);
+
+
+/**
+ * Register a definition with the serializer.
+ * @param {Object} defs A map of type names to definitions.
+ */
+function registerSerializerDefs(defs) {
+ var type;
+ for (type in defs) {
+ if (defs.hasOwnProperty(type)) {
+ serializerDefs[type] = defs[type];
+ }
+ }
+}
+
+
+/**
+ * Initialize the job manager and all resource managers.
+ * @param {Function} callback
+ */
+function initManagers(callback) {
+ registerSerializerDefs(jobs.Job.prototype.getSerializerDefs());
+
+ // First we need a JobManager.
+ jobManager = new jobs.JobManager();
+
+ function initResourceManager(managerInfo, callback) {
+ var constructor = require(managerInfo.module)[managerInfo.type];
+ var m = new constructor();
+
+ m.init(function(err) {
+ if (!err) {
+ managers[managerInfo.type] = m;
+ jobManager.registerResourceManager(m);
+ registerSerializerDefs(m.resourceType.prototype.getSerializerDefs());
+ }
+ callback(err);
+ });
+ }
+
+ function initGeneralManager(managerInfo, callback) {
+ var constructor = require(managerInfo.module)[managerInfo.type];
+ var m = new constructor();
+
+ m.init(function(err) {
+ if (!err) {
+ managers[managerInfo.type] = m;
+ }
+ callback(err);
+ });
+ }
+
+ // Initialize resource managers then general managers
+ async.forEachSeries(RESOURCE_MANAGERS, initResourceManager, function(err) {
+ if (!err) {
+ async.forEach(GENERAL_MANAGERS, initGeneralManager, callback);
+ } else {
+ callback(err);
+ }
+ });
+}
+
+
+/**
+ * Get the Cast JobManager.
+ */
+function getJobManager() {
+ return jobManager;
+}
+
+
+/**
+ * Get a ResourceManager.
+ */
+function getManager(name) {
+ return managers[name];
+}
+
+
+/**
+ * Get the Cast Swiz instance.
+ */
+function getSerializer() {
+ return serializer;
+}
+
+
+exports.initManagers = initManagers;
+exports.getManager = getManager;
+exports.getJobManager = getJobManager;
+exports.getSerializer = getSerializer;
+exports.registerSerializerDefs = registerSerializerDefs;
View
55 lib/cast-client/commands/bundles/delete.js
@@ -15,21 +15,14 @@
* limitations under the License.
*/
-var sys = require('sys');
-var fs = require('fs');
var path = require('path');
-var querystring = require('querystring');
var sprintf = require('sprintf').sprintf;
var async = require('async');
var term = require('terminal');
-var tarball = require('util/tarball');
var misc = require('util/misc');
-var locking = require('util/locking');
-var dotfiles = require('util/client_dotfiles');
var http = require('util/http');
-var clientUtils = require('util/client');
var Errorf = misc.Errorf;
@@ -45,14 +38,7 @@ var config = {
longDescription: 'Delete an existing bundle from the remotes server,',
requiredArguments: [['identifier', 'Bundle identifier']],
optionalArguments: [],
- options: [
- {
- names: ['--type', '-t'],
- dest: 'type',
- action: 'store',
- desc: sprintf('A type of bundle to delete. Valid options are: %s.', VALID_TYPES.join(', '))
- }
- ],
+ options: [],
usesGlobalOptions: ['debug']
};
@@ -84,9 +70,6 @@ function handleCommand(args, parser, callback) {
if (split.length !== 2) {
err = new Errorf('Invalid application name: %s', identifier);
}
- else if (!misc.inArray(bundleType, VALID_TYPES)) {
- err = new Errorf('Invalid bundle type: %s', bundleType);
- }
applicationName = split[0];
callback(err, applicationName);
@@ -137,32 +120,16 @@ function handleCommand(args, parser, callback) {
// Perform the request
function(applicationName, callback) {
- var remotePath, body, opts;
- var err, msg;
-
- remotePath = path.join('/bundles', applicationName, identifier);
- body = querystring.stringify({
- bundle_type: bundleType
- });
-
- http.getApiResponse(remotePath, 'DELETE', { 'remote': args.remote,
- 'apiVersion': '1.0',
- 'parseJson': true },
- function(err, response) {
- if (err) {
- callback(err);
- return;
- }
-
- if (response.statusCode === 404) {
- err = new Errorf('Bundle "%s" does not exist', identifier);
- }
- else if (response.statusCode !== 204) {
- err = new Error(response.body);
- }
-
- callback(err);
- });
+ var fileName = sprintf('%s.tar.gz', identifier);
+ var remotePath = path.join('/bundles', applicationName, fileName);
+ var opts = {
+ remote: args.remote,
+ apiVersion: '1.0',
+ parseJson: true,
+ expectedStatusCodes: [204]
+ };
+
+ http.getApiResponse(remotePath, 'DELETE', opts, callback);
}
],
View
85 lib/cast-client/commands/bundles/list.js
@@ -19,9 +19,9 @@ var sys = require('sys');
var sprintf = require('sprintf').sprintf;
var async = require('async');
-
var terminal = require('terminal');
+var bundles = require('bundles');
var http = require('util/http');
var misc = require('util/misc');
var clientUtils = require('util/client');
@@ -34,51 +34,54 @@ var config = {
usesGlobalOptions: ['debug', 'remote']
};
-function handleCommand(args) {
- async.series([
- function(callback) {
- http.getApiResponse('/bundles/', 'GET', { 'remote': args.remote,
- 'apiVersion': '1.0',
- 'parseJson': true },
- function(err, response) {
- if (err) {
- callback(err);
- return;
- }
- if (response.statusCode !== 200) {
- callback(new misc.Errorf('HTTP Error, status code: %d, returned body: %s',
- response.statusCode,
- response.body.message));
- return;
- }
+function handleCommand(args, parser, callback) {
+ var opts = {
+ remote: args.remote,
+ apiVersion: '1.0',
+ parseJson: true,
+ expectedStatusCodes: [200]
+ };
- var bundles = response.body;
- terminal.printTable([
- {
- title: 'Name',
- valueProperty: 'name',
- paddingRight: 30
- },
- {
- title: 'Version',
- valueProperty: 'version',
- paddingRight: 20
- },
- {
- title: 'Identifier',
- valueProperty: 'identifier'
- }
- ], bundles, 'No bundles available');
- });
- }
- ],
-
- function(err) {
+ http.getApiResponse('/bundles/', 'GET', opts, function(err, response) {
if (err) {
- clientUtils.printErrorAndExit(err, 1);
+ callback(err);
return;
}
+
+ var files = [];
+
+ response.body.forEach(function(app) {
+ app.bundles.forEach(function(file) {
+ var version = bundles.getBundleVersion(app.name, file);
+ var identifier = bundles.getBundleName(app.name, version);
+ files.push({
+ name: app.name,
+ version: version,
+ identifier: identifier
+ });
+ });
+ });
+
+
+ terminal.printTable([
+ {
+ title: 'Name',
+ valueProperty: 'name',
+ paddingRight: 30
+ },
+ {
+ title: 'Version',
+ valueProperty: 'version',
+ paddingRight: 20
+ },
+ {
+ title: 'Identifier',
+ valueProperty: 'identifier'
+ }
+ ], files, 'No bundles available');
+
+ callback();
});
}
View
24 lib/cast-client/commands/ca/delete.js
@@ -38,24 +38,14 @@ var config = {
};
function handleCommand(args, parser, callback) {
- async.series([
- function(callback) {
- var remotePath = sprintf('/ca/%s/', args.hostname);
- http.getApiResponse(remotePath, 'DELETE', { 'remote': args.remote,
- 'apiVersion': '1.0',
- 'parseJson': true,
- 'expectedStatusCodes': [200]},
- function(err, response) {
- if (err) {
- callback(err);
- return;
- }
-
- var req = response.body;
- sys.puts(sprintf('CSR for \'%s\': %s', req.hostname, req.status));
- });
+ var remotePath = sprintf('/ca/%s/', args.hostname);
+ http.executeRemoteJob(args.remote, remotePath, 'DELETE', function(err, result) {
+ if (err) {
+ callback(err);
+ } else {
+ callback(null, 'SigningRequest deleted');
}
- ], callback);
+ });
}
exports.config = config;
View
13 lib/cast-client/commands/ca/list.js
@@ -58,6 +58,11 @@ function handleCommand(args, parser, callback) {
var requests = response.body;
var headingDesc;
+ requests = requests.map(function(request) {
+ request.signed = request.cert ? true : false;
+ return request;
+ });
+
if (!args.listAll) {
requests = requests.filter(function(request) {
return !request.signed;
@@ -71,13 +76,13 @@ function handleCommand(args, parser, callback) {
terminal.printTable([
{
title: 'Hostname',
- valueProperty: 'hostname',
+ valueProperty: 'name',
paddingRight: 50
},
{
- title: 'Status',
- valueProperty: 'status',
- paddingRight: 25
+ title: 'Signed',
+ valueProperty: 'signed',
+ paddingRight: 10
}
], requests, 'No CSRs');
});
View
24 lib/cast-client/commands/ca/sign.js
@@ -39,24 +39,14 @@ var config = {
};
function handleCommand(args, parser, callback) {
- async.series([
- function(callback) {
- var remotePath = sprintf('/ca/%s/sign/', args.hostname);
- http.getApiResponse(remotePath, 'POST', { 'remote': args.remote,
- 'apiVersion': '1.0',
- 'parseJson': true,
- 'expectedStatusCodes': [200]},
- function(err, response) {
- if (err) {
- callback(err);
- return;
- }
-
- var req = response.body;
- sys.puts(sprintf('CSR for \'%s\': %s', req.hostname, req.status));
- });
+ var remotePath = sprintf('/ca/%s/sign/', args.hostname);
+ http.executeRemoteJob(args.remote, remotePath, 'POST', function(err, result) {
+ if (err) {
+ callback(err);
+ } else {
+ callback(null, 'SigningRequest signed');
}
- ], callback);
+ });
}
exports.config = config;
View
13 lib/cast-client/commands/instances/create.js
@@ -120,18 +120,7 @@ function handleCommand(args, parser, callback) {
enable_service: args.enable
});
- http.getApiResponse(remotePath, 'PUT', body, { 'remote': args.remote,
- 'apiVersion': '1.0',
- 'parseJson': true,
- 'expectedStatusCodes': [200]},
- function(err, response) {
- if (err) {
- callback(err);
- return;
- }
-
- callback(err);
- });
+ http.executeRemoteJob(args.remote, remotePath, 'PUT', body, callback);
}
],
View
14 lib/cast-client/commands/instances/destroy.js
@@ -48,7 +48,6 @@ function handleCommand(args, parser, callback) {
async.series([
function(callback) {
-
var promptStr = 'Are you sure you want to destroy \'' + instanceName + '\'?';
term.prompt(promptStr, ['y', 'n'], 'n', null, function(resp) {
if (resp !== 'y') {
@@ -64,18 +63,7 @@ function handleCommand(args, parser, callback) {
function(callback) {
var remotePath = sprintf('/instances/%s/', instanceName);
- http.getApiResponse(remotePath, 'DELETE', { 'remote': args.remote,
- 'apiVersion': '1.0',
- 'parseJson': true,
- 'expectedStatusCodes': [200]},
- function(err, response) {
- if (err) {
- callback(err);
- return;
- }
-
- callback();
- });
+ http.executeRemoteJob(args.remote, remotePath, 'DELETE', callback);
}
],
View
31 lib/cast-client/commands/instances/upgrade.js
@@ -42,29 +42,14 @@ var config = {
};
function handleCommand(args, parser, callback) {
- async.series([
- function(callback) {
- var remotePath = sprintf('/instances/%s/upgrade/', args.name);
-
- var body = querystring.stringify({
- bundle_version: args.version
- });
-
- http.getApiResponse(remotePath, 'POST', body, { 'remote': args.remote,
- 'apiVersion': '1.0',
- 'parseJson': true,
- 'expectedStatusCodes': [200]},
- function(err, response) {
- callback(err);
- });
- }
- ],
-
- function(err) {
- var successMessage = sprintf('Instance "%s" has been upgraded to version %s',
- args.name, args.version);
- callback(err, successMessage);
- });
+ var remotePath = sprintf('/instances/%s/upgrade/', args.name);
+ var body = querystring.stringify({
+ bundle_version: args.version
+ });
+
+ http.executeRemoteJob(args.remote, remotePath, 'POST', body, function(err) {
+ callback(err, sprintf('Instance upgraded to version %s', args.version));
+ });
}
exports.config = config;
View
75 lib/cast-client/commands/jobs/list.js
@@ -0,0 +1,75 @@
+/*
+ * Licensed to Cloudkick, Inc ('Cloudkick') under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * Cloudkick licenses this file to You 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 sys = require('sys');
+
+var sprintf = require('sprintf').sprintf;
+var async = require('async');
+var terminal = require('terminal');
+
+var http = require('util/http');
+var clientUtils = require('util/client');
+
+var config = {
+ shortDescription: 'Print a list of jobs',
+ longDescription: 'Print a list of jobs.',
+ requiredArguments: [],
+ optionalArguments: [],
+ usesGlobalOptions: ['debug', 'remote']
+};
+
+
+function handleCommand(args, parser, callback) {
+ var reqOpts = {
+ remote: args.remote,
+ apiVersion: '1.0',
+ parseJson: true,
+ expectedStatusCodes: [200]
+ };
+
+ function onResponse(err, response) {
+ if (err) {
+ callback(err);
+ return;
+ }
+
+ terminal.printTable([
+ {
+ title: 'Job ID',
+ valueProperty: 'id',
+ paddingRight: 40
+ },
+ {
+ title: 'Status',
+ valueProperty: 'status',
+ paddingRight: 15
+ },
+ {
+ title: 'Queued At',
+ valueProperty: 'queued_at',
+ paddingRight: 30
+ }
+ ], response.body, 'No jobs available');
+
+ callback();
+ }
+
+ http.getApiResponse('/jobs/', 'GET', reqOpts, onResponse);
+}
+
+exports.config = config;
+exports.handleCommand = handleCommand;
View
16 lib/cast-client/commands/services/service_cmd.js
@@ -38,20 +38,8 @@ function buildCommand(mod, action, actionPtense) {
};
mod.exports.handleCommand = function(args, parser, callback) {
- async.series([
- function getResponse(callback) {
- var actionPath = path.join('/', 'services', args.name, action, '/');
- http.getApiResponse(actionPath, 'PUT', { 'remote': args.remote,
- 'apiVersion': '1.0',
- 'parseJson': true,
- 'expectedStatusCodes': [200]},
- function(err, response) {
- callback(err);
- });
- }
- ],
-
- function(err) {
+ var actionPath = path.join('/', 'services', args.name, action, '/');
+ http.executeRemoteJob(args.remote, actionPath, 'PUT', function(err) {
var successMessage = sprintf('Service "%s" %s.', args.name, actionPtense);
callback(err, successMessage);
});
View
5 lib/cast-client/commands/services/tail.js
@@ -21,6 +21,7 @@ var path = require('path');
var sprintf = require('sprintf').sprintf;
var http = require('util/http');
+var misc = require('util/misc');
var config = {
shortDescription: 'Print log file for a specified service',
@@ -90,10 +91,10 @@ function handleCommand(args, parser, callback) {
});
response.on('end', function() {
try {
- sys.puts('Error: ' + JSON.parse(body).message);
+ callback(new misc.ServerError(JSON.parse(body).message));
}
catch (e) {
- callback(err);
+ callback(e);
}
});
}
View
3  lib/cast-client/parser.js
@@ -79,7 +79,8 @@ exports.COMMANDS = [
'remotes/set-default',
'ca/list',
'ca/sign',
- 'ca/delete'
+ 'ca/delete',
+ 'jobs/list'
];
/**
View
96 lib/control/bundles.js
@@ -0,0 +1,96 @@
+/*
+ * Licensed to Cloudkick, Inc ('Cloudkick') under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * Cloudkick licenses this file to You 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 agentManagers = require('cast-agent/managers');
+
+
+function getBundleManager() {
+ return agentManagers.getManager('BundleManager');
+}
+
+
+/**
+ * Add a bundle to cast.
+ * @param {String} name The name of the application.
+ * @param {String} version The version of the application.
+ * @param {stream.Stream} iStream A stream to read the bundle from.
+ * @param {Function} getSHA1 An (optional) function called after iStream has
+ * emitted an 'end' event that takes a callback taking (err, sha1), where
+ * the 'sha1' argument is a base64 string.
+ * @param {Function} callback A callback fired with (err).
+ */
+function addBundle(name, version, iStream, getSHA1, callback) {
+ if (!callback) {
+ callback = getSHA1;
+ getSHA1 = undefined;
+ }
+
+ var opts = {
+ getSHA1: getSHA1
+ };
+
+ getBundleManager().add(name, version, iStream, opts, callback);
+}
+
+
+/**
+ * Retrieve a bundle from cast in the form of a stream.
+ * @param {String} name The name of the application.
+ * @param {String} version The version of the application.
+ * @param {Function} callback A callback fired with (err, oStream).
+ */
+function getBundle(name, version, callback) {
+ getBundleManager().getBundle(name, version, callback);
+}
+
+
+/**
+ * Remove a bundle from cast.
+ * @param {String} name The name of the application.
+ * @param {String} version The version of the application.
+ * @param {Function} callback A callback fired with (err).
+ */
+function removeBundle(name, version, callback) {
+ getBundleManager().remove(name, version, callback);
+}
+
+
+/**
+ * Retrieve information about an application.
+ * @param {String} name The name of the application.
+ * @param {Function} callback A callback fired with (err, app).
+ */
+function getApplication(name, callback) {
+ getBundleManager().getApplication(name, callback);
+}
+
+
+/**
+ * Retrieve a list of applications.
+ * @param {Function} callback A callback fired with (err, appList).
+ */
+function listApplications(callback) {
+ getBundleManager().listApplications(callback);
+}
+
+
+exports.addBundle = addBundle;
+exports.getBundle = getBundle;
+exports.removeBundle = removeBundle;
+exports.getApplication = getApplication;
+exports.listApplications = listApplications;
View
161 lib/control/ca.js
@@ -0,0 +1,161 @@
+/*
+ * Licensed to Cloudkick, Inc ('Cloudkick') under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * Cloudkick licenses this file to You 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 sys = require('sys');
+
+var agentManagers = require('cast-agent/managers');
+var jobs = require('jobs');
+var ca = require('security/ca');
+
+
+/**
+ * Create a CSR with a given name and body.
+ * @extends {jobs.Job}
+ * @param {String} name The name of the CSR.
+ * @param {String} csrText The text of the CSR.
+ */
+function CreateRequestJob(name, csrText) {
+ jobs.Job.call(this, [csrText]);
+ this.options = jobs.JobOptions.CREATE;
+ this.resourceName = name;
+ this.resourceType = ca.SigningRequest;
+}
+
+sys.inherits(CreateRequestJob, jobs.Job);
+
+
+/**
+ * Create the CSR.
+ * @param {security.ca.SigningRequest} request The request resource.
+ * @param {String} csrText The text of the CSR.
+ * @param {Function} callback A callback fired with (err).
+ */
+CreateRequestJob.prototype.run = function(request, csrText, callback) {
+ request.create(csrText, callback);
+};
+
+
+/**
+ * Sign a CSR.
+ * @extends {jobs.Job}
+ * @param {String} name The name of the CSR to sign.
+ * @param {Boolean} overwrite Should an existing certificate be overwritten?
+ */
+function SignRequestJob(name, overwrite) {
+ jobs.Job.call(this, [overwrite]);
+ this.options = jobs.JobOptions.UPDATE;
+ this.resourceName = name;
+ this.resourceType = ca.SigningRequest;
+}
+
+sys.inherits(SignRequestJob, jobs.Job);
+
+
+/**
+ * Sign the CSR.
+ * @param {security.ca.SigningRequest} request The request resource.
+ * @param {Boolean} overwrite Should an existing certificate be overwritten?
+ * @param {Function} callback A callback fired with (err).
+ */
+SignRequestJob.prototype.run = function(request, overwrite, callback) {
+ request.sign(overwrite, callback);
+};
+
+
+/**
+ * Delete an unsigned CSR.
+ * @extends {jobs.Job}
+ * @param {String} name The name of the CSR to delete.
+ */
+function DeleteRequestJob(name) {
+ jobs.Job.call(this);
+ this.options = jobs.JobOptions.DELETE;
+ this.resourceName = name;
+ this.resourceType = ca.SigningRequest;
+}
+
+sys.inherits(DeleteRequestJob, jobs.Job);
+
+
+DeleteRequestJob.prototype.run = function(request, callback) {
+ request.destroy(callback);
+};
+
+
+/**
+ * Retrieve a signing request by name.
+ * @param {String} name The name of the CSR to retrieve.
+ * @param {FUnction} callback A callback fired with (err, req).
+ */
+function getRequest(name, callback) {
+ agentManagers.getManager('SigningRequestManager').get(name, callback);
+}
+
+
+/**
+ * Retrieve a list of signing requests.
+ * @param {Function} callback A callback fired with (err, reqList).
+ */
+function listRequests(callback) {
+ agentManagers.getManager('SigningRequestManager').list(callback);
+}
+
+
+/**
+ * Create a new CSR with the specified name and text.
+ * @export
+ * @param {String} name The name for the CSR.
+ * @param {String} csrText The text of the CSR.
+ * @return {jobs.Job} The job.
+ */
+function createRequest(name, csrText) {
+ var j = new CreateRequestJob(name, csrText);
+ agentManagers.getJobManager().run(j);
+ return j;
+}
+
+
+/**
+ * Sign the CSR with the specified name.
+ * @param {String} name The name of the CSR to sign.
+ * @param {Boolean} overwrite Overwrite existing certificates?
+ * @return {jobs.Job} The job.
+ */
+function signRequest(name, overwrite) {
+ var j = new SignRequestJob(name, overwrite);
+ agentManagers.getJobManager().run(j);
+ return j;
+}
+
+
+/**
+ * Delete the CSR with the specified name.
+ * @param {String} name The name of the CSR.
+ * @return {jobs.Job} The job.
+ */
+function deleteRequest(name) {
+ var j = new DeleteRequestJob(name);
+ agentManagers.getJobManager().run(j);
+ return j;
+}
+
+
+exports.getRequest = getRequest;
+exports.listRequests = listRequests;
+exports.createRequest = createRequest;
+exports.signRequest = signRequest;
+exports.deleteRequest = deleteRequest;
View
32 lib/control/facts.js
@@ -0,0 +1,32 @@
+/*
+ * Licensed to Cloudkick, Inc ('Cloudkick') under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * Cloudkick licenses this file to You 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 norris = require('norris');
+
+
+/**
+ * Retrieve facts about the host cast is running on.
+ * @param {Function} callback A callback fired with (err, facts).
+ */
+function getFacts(callback) {
+ // We currently hardcode a null error argument for API consistency and
+ // future-proofing.
+ norris.get(callback.bind(null, null));
+}
+
+
+exports.getFacts = getFacts;
View
137 lib/control/health.js
@@ -0,0 +1,137 @@
+/*
+ * Licensed to Cloudkick, Inc ('Cloudkick') under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * Cloudkick licenses this file to You 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 async = require('async');
+
+var health = require('services/health').health;
+var jobs = require('jobs');
+
+
+/**
+ * Retrieve a list of all checks.
+ * @param {Function} callback A callback fired with (err, checks).
+ */
+function listChecks(callback) {
+ callback(null, health.getChecksArray());
+}
+
+
+/**
+ * Retrieve a list of all scheduled checks.
+ * @param {Function} calback A callback fired with (err, checks).
+ */
+function listScheduledChecks(callback) {
+ var checks = health.getChecksArray().filter(function(check) {
+ return check.isScheduled;
+ });
+ callback(null, checks);
+}
+
+
+/**
+ * Retrieve an individual check by its id.
+ *
+ * @param {Integer} checkId Check ID.
+ * @param {Function} callback A callback fired with (err, check).
+ */
+function getCheck(checkId, callback) {
+ var check = health.activeChecks[checkId];
+
+ if (!check) {
+ callback(new jobs.NotFoundError('Check', checkId));
+ } else {
+ callback(null, check);
+ }
+}
+
+/**
+ * Add a new check.
+ *
+ * @param {ScheduledCheck} scheduledCheck Sccheduled check.
+ * @param {Boolean} schedule true to instantly schedule the check,
+ * false to just add it to the active checks hash
+ * table.
+ * @param {Function} callback A callback fired with (err).
+ */
+function addCheck(scheduledCheck, schedule, callback) {
+ health.addCheck(scheduledCheck, schedule);
+ callback(null);
+}
+
+/**
+ * Remove and unschedule a check
+ *
+ * @param {Integer} id Check ID.
+ * @param {Function} callback A callback called with (err, removed)
+ */
+function removeCheck(checkId, callback) {
+ var removed = false;
+
+ try {
+ removed = health.removeCheck(checkId);
+ }
+ catch (err) {
+ callback(err, removed);
+ return;
+ }
+
+ callback(null, removed);
+}
+
+/**
+ * Resume a check.
+ *
+ * @param {Integer} checkId Check ID.
+ * @param {Function} callback A callback fired with (err).
+ */
+function resumeCheck(checkId, callback) {
+ try {
+ health.resumeCheck(checkId);
+ }
+ catch (err) {
+ callback(err);
+ return;
+ }
+
+ callback(null);
+}
+
+/**
+ * Pause a check.
+ *
+ * @param {Integer} checkId Check ID.
+ * @param {Function} callback A callback fired with (err).
+ */
+function pauseCheck(checkId, callback) {
+ try {
+ health.pauseCheck(checkId);
+ }
+ catch (err) {
+ callback(err);
+ return;
+ }
+
+ callback(null);
+}
+
+exports.listChecks = listChecks;
+exports.listScheduledChecks = listScheduledChecks;
+exports.getCheck = getCheck;
+exports.addCheck = addCheck;
+exports.removeCheck = removeCheck;
+exports.resumeCheck = resumeCheck;
+exports.pauseCheck = pauseCheck;
View
25 lib/control/index.js
@@ -0,0 +1,25 @@
+/*
+ * Licensed to Cloudkick, Inc ('Cloudkick') under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * Cloudkick licenses this file to You 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.
+ */
+
+exports.ca = require('./ca');
+exports.bundles = require('./bundles');
+exports.instances = require('./instances');
+exports.services = require('./services');
+exports.health = require('./health');
+exports.jobs = require('./jobs');
+exports.facts = require('./facts');
+exports.info = require('./info');
View
51 lib/control/info.js
@@ -0,0 +1,51 @@
+/*
+ * Licensed to Cloudkick, Inc ('Cloudkick') under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * Cloudkick licenses this file to You 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 os = require('os');
+
+var norris = require('norris');
+var httpConstants = require('http/constants');
+var agent = require('cast-agent/entry');
+var version = require('util/version');
+
+
+/**
+ * Retrieve information about the running cast agent.
+ * @param {Function} callback A callback fired with (err, info).
+ */
+function getInfo(callback) {
+ var dateStarted = agent.dateStarted;
+ var currentDate = new Date();
+ var uptime = (currentDate.getTime() / 1000) - (dateStarted.getTime() / 1000);
+
+ norris.get(function(facts) {
+ callback(null, {
+ 'agent_version': version.toString(),
+ 'node_version': process.version,
+ 'api_version': httpConstants.CURRENT_API_VERSION,
+ 'hostname': facts.hostname,
+ 'architecture': facts.arch,
+ 'os': os.release(),
+ 'memory': os.totalmem(),
+ 'os_uptime': os.uptime(),
+ 'agent_uptime': uptime
+ });
+ });
+}
+
+
+exports.getInfo = getInfo;
View
169 lib/control/instances.js
@@ -0,0 +1,169 @@
+/*
+ * Licensed to Cloudkick, Inc ('Cloudkick') under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * Cloudkick licenses this file to You 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 util = require('util');
+
+var agentManagers = require('cast-agent/managers');
+var jobs = require('jobs');
+var instances = require('deployment/instances');
+
+
+/**
+ * Create an instance.
+ * @extends {jobs.Job}
+ * @param {String} name The name of the Instance.
+ * @param {String} bundleName The name of the bundle to use.
+ * @param {String} bundleVersion The version of the bundle to use.
+ */
+function CreateInstanceJob(name, bundleName, bundleVersion) {
+ jobs.Job.call(this, [bundleName, bundleVersion]);
+ this.options = jobs.JobOptions.CREATE;
+ this.resourceName = name;
+ this.resourceType = instances.Instance;
+}
+
+util.inherits(CreateInstanceJob, jobs.Job);
+
+
+/**
+ * Create an Instance.
+ * @param {deployment.instances.Instance} instance The Instance to create.
+ * @param {String} bundleName The name of the bundle to use.
+ * @param {String} bundleVersion The version of the bundle to use.
+ * @param {Function} callback A callback fired with (err).
+ */
+CreateInstanceJob.prototype.run = function(instance, bundleName, bundleVersion, callback) {
+ instance.create(bundleName, bundleVersion, callback);
+};
+
+
+/**
+ * Delete an instance.
+ * @extends {jobs.Job}
+ * @param {String} name The name of the Instance to delete.
+ */
+function DeleteInstanceJob(name) {
+ jobs.Job.call(this);
+ this.options = jobs.JobOptions.DELETE;
+ this.resourceName = name;
+ this.resourceType = instances.Instance;
+}
+
+util.inherits(DeleteInstanceJob, jobs.Job);
+
+
+/**
+ * Delete an instance.
+ * @param {deployment.instances.Instance} instance The Instance to delete.
+ * @param {Function} callback A callback fired with (err).
+ */
+DeleteInstanceJob.prototype.run = function(instance, callback) {
+ instance.destroy(callback);
+};
+
+
+/**
+ * Delete an instance.
+ * @extends {jobs.Job}
+ * @param {String} name The name of the instance to upgrade.
+ * @param {String} bundleVersion The version to upgrade to.
+ */
+function UpgradeInstanceJob(name, bundleVersion) {
+ jobs.Job.call(this, [bundleVersion]);
+ this.options = jobs.JobOptions.UPDATE;
+ this.resourceName = name;
+ this.resourceType = instances.Instance;
+}
+
+util.inherits(UpgradeInstanceJob, jobs.Job);
+
+
+/**
+ * Upgrade an instance.
+ * @param {deployment.instances.Instance} instance The instance to upgrade.
+ * @param {String} bundleVersion The version to upgrade to.
+ * @param {Function} callback A callback fired with (err).
+ */
+UpgradeInstanceJob.prototype.run = function(instance, bundleVersion, callback) {
+ instance.upgrade(bundleVersion, callback);
+};
+
+
+/**
+ * Get an instance by name.
+ * @param {String} name The name of the instance.
+ * @param {Function} callback A callback fired with (err, instance).
+ */
+function getInstance(name, callback) {
+ agentManagers.getManager('InstanceManager').get(name, callback);
+}
+
+
+/**
+ * List existing instances.
+ * @param {Function} callback A callback fired with (err, instances).
+ */
+function listInstances(callback) {
+ agentManagers.getManager('InstanceManager').list(callback);
+}
+
+
+/**
+ * Create a new instance.
+ * @param {String} name The name of the instance to create.
+ * @param {String} bundleName The name of the bundle to use.
+ * @param {String} bundleVersion The version of the bundle to use.
+ * @return {jobs.Job} The job that will create the instance.
+ */
+function createInstance(name, bundleName, bundleVersion) {
+ var j = new CreateInstanceJob(name, bundleName, bundleVersion);
+ agentManagers.getJobManager().run(j);
+ return j;
+}
+
+
+/**
+ * Upgrade an instance.
+ * @param {String} name The name of the instance to upgrade.
+ * @param {String} bundleVersion The version to upgrade the instance to.
+ * @return {jobs.Job} The job that will upgrade the instance.
+ */
+function upgradeInstance(name, bundleVersion) {
+ var j = new UpgradeInstanceJob(name, bundleVersion);
+ agentManagers.getJobManager().run(j);
+ return j;
+}
+
+
+/**
+ * Delete an existing instance.
+ * @param {String} name The name of the instance to delete.
+ * @return {jobs.Job} The job that will delete the instance.
+ */
+function deleteInstance(name) {
+ var j = new DeleteInstanceJob(name);
+ agentManagers.getJobManager().run(j);
+ return j;
+}
+
+
+
+exports.getInstance = getInstance;
+exports.listInstances = listInstances;
+exports.createInstance = createInstance;
+exports.upgradeInstance = upgradeInstance;
+exports.deleteInstance = deleteInstance;
View
44 lib/control/jobs.js
@@ -0,0 +1,44 @@
+/*
+ * Licensed to Cloudkick, Inc ('Cloudkick') under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * Cloudkick licenses this file to You 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 sys = require('sys');
+
+var agentManagers = require('cast-agent/managers');
+
+/**
+ * Retrieve a list of all jobs. Note: there is currently no need for this to be
+ * async, but we are likely to need it to be async in the future.
+ * @param {Function} callback A callback fired with (err, jobs).
+ */
+function listJobs(callback) {
+ agentManagers.getJobManager().listJobs(callback);
+}
+
+
+/**
+ * Retrieve a job based on its id. Note: there is currently no need for this to
+ * be async, but we are likely to need it to be async in the future.
+ * @param {String} id The id of the job to retrieve.
+ * @param {Function} callback A callback fired with (err, job).
+ */
+function getJob(id, callback) {
+ agentManagers.getJobManager().getJob(id, callback);
+}
+
+
+exports.listJobs = listJobs;
+exports.getJob = getJob;
View
142 lib/control/services.js
@@ -0,0 +1,142 @@
+/*
+ * Licensed to Cloudkick, Inc ('Cloudkick') under one or more