UNPKG

10.9 kBJavaScriptView Raw
1/* jshint node: true */
2'use strict';
3
4var async = require('async');
5var util = require('util');
6var path = require('path');
7var fs = require('fs-extra');
8var tar = require('tar-fs');
9var zlib = require('zlib');
10var spawn = require('child_process').spawn;
11var mustache = require('mustache');
12var unzip = require('node-unzip-2');
13var _ = require('underscore');
14var tmp = require('tmp');
15tmp.setGracefulCleanup();
16
17var ziputils = require('./ziputils');
18
19var DeploymentDelay = 60
20var ProxyBase = 'apiproxy';
21
22module.exports.createApiProxy = function(opts, request, done) {
23 // Create a dummy "API proxy" file for the root of this thing.
24 var rootDoc = mustache.render('<APIProxy name="{{api}}"/>', opts);
25 var rootEntryName = opts.api + '.xml';
26
27 var uri = util.format('%s/v1/o/%s/apis?action=import&validate=false&name=%s',
28 opts.baseuri, opts.organization, opts.api);
29 if (opts.debug) {
30 console.log('Calling %s', uri);
31 }
32 if (opts.verbose) {
33 console.log('Creating revision %d of API %s', opts.deploymentVersion,
34 opts.api);
35 }
36 // The only way to do this is to import a ZIP. What fun.
37 var zipBuf = ziputils.makeOneFileZip(ProxyBase, rootEntryName, rootDoc);
38 // For debugging
39 //fs.writeFileSync('./test.zip', zipBuf);
40 request({
41 uri: uri,
42 headers: { 'Content-Type': 'application/octet-stream' },
43 json: false,
44 method: 'POST',
45 body: zipBuf
46 }, function(err, req, body) {
47 proxyCreationDone(err, req, body, opts, done);
48 });
49}
50
51module.exports.createProxy = function(opts, request, done) {
52 var vhostStr = (opts.virtualhosts ? opts.virtualhosts : 'default,secure');
53 // Create an array of objects for underscore
54 var vhosts = _.map(vhostStr.split(','), function(i) {
55 return { name: i };
56 });
57
58 var basepath = (opts['base-path'] ? opts['base-path'] : '/');
59
60 var targetDoc = mustache.render(
61 '<ProxyEndpoint name="default">' +
62 '<PreFlow name="PreFlow"/>' +
63 '<PostFlow name="PostFlow"/>' +
64 '<HTTPProxyConnection>' +
65 '<BasePath>{{basepath}}</BasePath>' +
66 '{{#vhosts}}<VirtualHost>{{name}}</VirtualHost>{{/vhosts}}' +
67 '</HTTPProxyConnection>' +
68 '<RouteRule name="default">' +
69 '<TargetEndpoint>default</TargetEndpoint>' +
70 '</RouteRule>' +
71 '</ProxyEndpoint>', {
72 vhosts: vhosts,
73 basepath: basepath
74 });
75 if (opts.debug) {
76 console.log('vhosts = %j', vhosts);
77 console.log('proxy = %s', targetDoc);
78 }
79
80 var uri = util.format('%s/v1/o/%s/apis/%s/revisions/%d/proxies?name=default',
81 opts.baseuri, opts.organization, opts.api,
82 opts.deploymentVersion);
83 if (opts.verbose) {
84 console.log('Creating the proxy endpoint');
85 }
86
87 request({
88 uri: uri,
89 method: 'POST',
90 json: false,
91 headers: { 'Content-Type': 'application/xml' },
92 body: targetDoc
93 }, function(err, req, body) {
94 handleUploadResult(err, req, 'proxies/default.xml', done);
95 });
96}
97
98module.exports.deployProxy = function(opts, request, done) {
99 if (opts['import-only']) {
100 if (opts.verbose) {
101 console.log('Not deploying the proxy right now');
102 }
103 done();
104 return;
105 }
106
107 if (opts.verbose) {
108 console.log('Deploying revision %d of %s to %s', opts.deploymentVersion,
109 opts.api, opts.environments);
110 }
111
112 var environments = opts.environments.split(',');
113
114 function deployToEnvironment(environment, done) {
115
116 var uri = util.format('%s/v1/o/%s/e/%s/apis/%s/revisions/%d/deployments',
117 opts.baseuri, opts.organization, environment, opts.api,
118 opts.deploymentVersion);
119
120 if (opts.debug) { console.log('Going to POST to %s', uri); }
121
122 // Unlike "deployproxy" command, ignore the base path here, because we baked it into the proxy definition.
123 var deployCmd = util.format('action=deploy&override=true&delay=%d', DeploymentDelay);
124
125 if (opts.debug) { console.log('Going go send command %s', deployCmd); }
126
127 request({
128 uri: uri,
129 method: 'POST',
130 json: false,
131 body: deployCmd,
132 headers: {
133 'Content-Type': 'application/x-www-form-urlencoded',
134 'Accept': 'application/json'
135 }
136 }, function(err, req, body) {
137 if (err) { return done(err); }
138
139 var jsonBody = (body ? JSON.parse(body) : null);
140
141 if (req.statusCode === 200) {
142 if (opts.verbose) { console.log('Deployment on %s successful', environment); }
143 if (opts.debug) { console.log('%s', body); }
144 return done(undefined, jsonBody);
145 }
146
147 if (opts.verbose) { console.error('Deployment on %s result: %j', environment, body); }
148 var errMsg;
149 if (jsonBody && (jsonBody.message)) {
150 errMsg = jsonBody.message;
151 } else {
152 errMsg = util.format('Deployment on %s failed with status code %d', environment, req.statusCode);
153 }
154 done(new Error(errMsg));
155 });
156 }
157
158 var tasks = {};
159 environments.forEach(function(env) {
160 tasks[env] = deployToEnvironment.bind(this, env);
161 });
162
163 async.parallel(tasks, done);
164}
165
166module.exports.unzipProxy = function(opts, destDir, ignoreDir, cb) {
167
168 if (opts.debug) { console.log('Extracting proxy to', destDir); }
169
170 var count = 1;
171 var called = false;
172 function done(err) {
173 if (!called) {
174 count--;
175 if (err || count === 0) { cb(err); }
176 }
177 }
178
179 fs.createReadStream(opts.file)
180 .pipe(unzip.Parse())
181 .on('error', done)
182 .on('close', done)
183 .on('entry', function (entry) {
184 if (entry.path.indexOf(ignoreDir) === 0) {
185 if (opts.debug) { console.log('skipping', entry.path); }
186 entry.autodrain(); // ignore all hosted resources
187 } else {
188 count++;
189 if (opts.debug) { console.log('extracting', entry.path); }
190 var destFile = path.resolve(destDir, entry.path);
191 mkdirs(path.dirname(destFile), function(err) {
192 if (err) { return cb(err); }
193
194 entry
195 .pipe(fs.createWriteStream(destFile))
196 .on('error', done)
197 .on('close', done);
198 });
199 }
200 });
201 }
202
203module.exports.handleUploadResult = handleUploadResult;
204
205function handleUploadResult(err, req, fileName, itemDone) {
206 if (err) {
207 itemDone(err);
208 } else if ((req.statusCode === 200) || (req.statusCode === 201)) {
209 itemDone();
210 } else {
211 itemDone(new Error(util.format('Error uploading resource %s: %d\n%s',
212 fileName, req.statusCode, req.body)));
213 }
214}
215
216function mkdirs(dirpath, cb) {
217
218 var parts = dirpath.split(path.sep);
219 var start = 1;
220 if (dirpath[0] === path.sep) {
221 parts[0] = '/';
222 start = 2;
223 }
224 for (var i = start; i <= parts.length; i++) {
225 try {
226 var dir = path.join.apply(null, parts.slice(0, i));
227 fs.mkdirSync(dir);
228 } catch (err) {
229 if (err.code !== 'EEXIST') { return cb(err); }
230 }
231 }
232 cb();
233}
234
235module.exports.copyFile = function(source, target, cb) {
236
237 mkdirs(path.dirname(target), function(err) {
238 if (err) { return cb(err); }
239
240 cb = _.once(cb);
241 var wr = fs.createWriteStream(target)
242 .on('error', cb)
243 .on('close', cb);
244 fs.createReadStream(source)
245 .pipe(wr)
246 .on('error', cb);
247 });
248}
249
250module.exports.uploadSource = function(sourceDir, type, opts, request, done) {
251// Get a list of entries, broken down by which are directories
252 ziputils.enumerateDirectory(sourceDir, type, opts.remoteNpm, function(err, entries) {
253 if (err) { return done(err); }
254
255 if (opts.debug) { console.log('Directories to upload: %j', entries); }
256
257 async.eachLimit(entries, opts.asynclimit, function(entry, entryDone) {
258 var uri =
259 util.format('%s/v1/o/%s/apis/%s/revisions/%d/resources?type=%s&name=%s',
260 opts.baseuri, opts.organization, opts.api,
261 opts.deploymentVersion, type, entry.resourceName);
262 if (entry.directory) {
263 // ZIP up all directories, possibly with additional file prefixes
264 ziputils.zipDirectory(entry.fileName, entry.zipEntryName, function(err, zipBuf) {
265 if (err) {
266 entryDone(err);
267 } else {
268 if (opts.verbose) {
269 console.log('Uploading resource %s of size %d', entry.resourceName, zipBuf.length);
270 }
271 request({
272 uri: uri,
273 method: 'POST',
274 json: false,
275 headers: { 'Content-Type': 'application/octet-stream' },
276 body: zipBuf
277 }, function(err, req, body) {
278 handleUploadResult(err, req, entry.fileName, entryDone);
279 });
280 }
281 });
282
283 } else {
284 if (opts.verbose) {
285 console.log('Uploading resource %s', entry.resourceName);
286 }
287 var httpReq = request({
288 uri: uri,
289 method: 'POST',
290 json: false,
291 headers: { 'Content-Type': 'application/octet-stream' }
292 }, function(err, req, body) {
293 handleUploadResult(err, req, entry.fileName, entryDone);
294 });
295
296 var fileStream = fs.createReadStream(entry.fileName);
297 fileStream.pipe(httpReq);
298 }
299 }, function(err) {
300 done(err);
301 });
302 });
303}
304
305module.exports.usePackedSource = function(sourceDir, opts, cb) {
306 if (opts.debug) {
307 console.log('packaging bundled dependencies for upload')
308 }
309
310 tmp.dir(function(err, tempDir) {
311 if (err) {
312 return cb(err);
313 }
314
315 fs.copy(sourceDir, tempDir, function(err) {
316 if (err) {
317 return cb(err)
318 }
319
320 var cmd = 'npm' + (process.platform === 'win32' ? '.cmd' : '');
321 var pack = spawn(cmd, ['pack'], {cwd: tempDir});
322 pack.on('error', function(err) {
323 return cb(err)
324 });
325 var packageName;
326
327 pack.stdout.on('data', function(data) {
328 packageName = data.toString().trim()
329 });
330
331 pack.on('close', function() {
332 try {
333 var packageArchive = path.join(tempDir, packageName);
334 fs.createReadStream(packageArchive).pipe(zlib.createGunzip()).pipe(tar.extract(tempDir)).on('finish', function() {
335 fs.removeSync(packageArchive) // remove the pack archive so it doesn't show up in the proxy
336
337 if (opts.debug) {
338 console.log('bundled dependencies ready for upload')
339 }
340
341 // return path to packed directory
342 return cb(undefined, path.join(tempDir, 'package'))
343 }).on('error', function(err) {
344 return cb(err);
345 });
346 } catch(err) {
347 return cb(err)
348 }
349 });
350 });
351 });
352}
353
354function proxyCreationDone (err, req, body, opts, done) {
355 if (err) {
356 done(err);
357 } else if ((req.statusCode === 200) || (req.statusCode === 201)) {
358 done();
359 } else {
360 if (opts.verbose) {
361 console.error('Proxy creation error:', body);
362 }
363 done(new Error(util.format('Proxy creation failed. Status code %d',
364 req.statusCode)));
365 }
366}
\No newline at end of file