1 |
|
2 | 'use strict';
|
3 |
|
4 | var async = require('async');
|
5 | var util = require('util');
|
6 | var path = require('path');
|
7 | var fs = require('fs-extra');
|
8 | var tar = require('tar-fs');
|
9 | var zlib = require('zlib');
|
10 | var spawn = require('child_process').spawn;
|
11 | var mustache = require('mustache');
|
12 | var unzip = require('node-unzip-2');
|
13 | var _ = require('underscore');
|
14 | var tmp = require('tmp');
|
15 | tmp.setGracefulCleanup();
|
16 |
|
17 | var ziputils = require('./ziputils');
|
18 |
|
19 | var DeploymentDelay = 60
|
20 | var ProxyBase = 'apiproxy';
|
21 |
|
22 | module.exports.createApiProxy = function(opts, request, done) {
|
23 |
|
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 |
|
37 | var zipBuf = ziputils.makeOneFileZip(ProxyBase, rootEntryName, rootDoc);
|
38 |
|
39 |
|
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 |
|
51 | module.exports.createProxy = function(opts, request, done) {
|
52 | var vhostStr = (opts.virtualhosts ? opts.virtualhosts : 'default,secure');
|
53 |
|
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 |
|
98 | module.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 |
|
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 |
|
166 | module.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();
|
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 |
|
203 | module.exports.handleUploadResult = handleUploadResult;
|
204 |
|
205 | function 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 |
|
216 | function 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 |
|
235 | module.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 |
|
250 | module.exports.uploadSource = function(sourceDir, type, opts, request, done) {
|
251 |
|
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 |
|
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 |
|
305 | module.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)
|
336 |
|
337 | if (opts.debug) {
|
338 | console.log('bundled dependencies ready for upload')
|
339 | }
|
340 |
|
341 |
|
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 |
|
354 | function 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 |