server-control-s3 0.0.13 → 0.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/server_control.d.ts +28 -0
- package/dist/server_control.js +701 -0
- package/package.json +34 -12
- package/npm-shrinkwrap.json +0 -1015
- package/src/index.js +0 -620
- package/src/request.js +0 -40
|
@@ -0,0 +1,701 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var asyncEach = require('async/each');
|
|
6
|
+
var asyncForever = require('async/forever');
|
|
7
|
+
var asyncSeries = require('async/series');
|
|
8
|
+
var clientAutoScaling = require('@aws-sdk/client-auto-scaling');
|
|
9
|
+
var clientEc2 = require('@aws-sdk/client-ec2');
|
|
10
|
+
var child_process = require('node:child_process');
|
|
11
|
+
var fs = require('node:fs');
|
|
12
|
+
var node_path = require('node:path');
|
|
13
|
+
var clientS3 = require('@aws-sdk/client-s3');
|
|
14
|
+
var http = require('http');
|
|
15
|
+
var https = require('https');
|
|
16
|
+
var url_lib = require('url');
|
|
17
|
+
|
|
18
|
+
function _interopNamespaceDefault(e) {
|
|
19
|
+
var n = Object.create(null);
|
|
20
|
+
if (e) {
|
|
21
|
+
Object.keys(e).forEach(function (k) {
|
|
22
|
+
if (k !== 'default') {
|
|
23
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
24
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
25
|
+
enumerable: true,
|
|
26
|
+
get: function () { return e[k]; }
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
n.default = e;
|
|
32
|
+
return Object.freeze(n);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
var child_process__namespace = /*#__PURE__*/_interopNamespaceDefault(child_process);
|
|
36
|
+
var fs__namespace = /*#__PURE__*/_interopNamespaceDefault(fs);
|
|
37
|
+
|
|
38
|
+
const TIMEOUT = 15 * 1000;
|
|
39
|
+
function webRequest(opts, done) {
|
|
40
|
+
if (!opts.timeout) {
|
|
41
|
+
opts.timeout = TIMEOUT;
|
|
42
|
+
}
|
|
43
|
+
const url = new url_lib.URL(opts.url);
|
|
44
|
+
const transport = url.protocol === 'https:' ? https : http;
|
|
45
|
+
const request_options = {
|
|
46
|
+
method: opts.method || 'GET',
|
|
47
|
+
timeout: opts.timeout,
|
|
48
|
+
headers: opts.headers || {},
|
|
49
|
+
};
|
|
50
|
+
let request_body;
|
|
51
|
+
if (opts.json) {
|
|
52
|
+
if (request_options.method === 'GET') {
|
|
53
|
+
Object.keys(opts.json).forEach((key) => {
|
|
54
|
+
url.searchParams.append(key, opts.json[key]);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
request_body = JSON.stringify(opts.json);
|
|
59
|
+
request_options.headers['Content-Type'] = 'application/json';
|
|
60
|
+
request_options.headers['Content-Length'] =
|
|
61
|
+
Buffer.byteLength(request_body);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const client_req = transport.request(url, request_options, (incoming_res) => {
|
|
65
|
+
let response_body = '';
|
|
66
|
+
incoming_res.on('data', (chunk) => {
|
|
67
|
+
response_body += chunk;
|
|
68
|
+
});
|
|
69
|
+
incoming_res.on('end', () => {
|
|
70
|
+
const status_code = incoming_res.statusCode;
|
|
71
|
+
let err = null;
|
|
72
|
+
if (status_code < 200 || status_code > 299) {
|
|
73
|
+
err = status_code;
|
|
74
|
+
}
|
|
75
|
+
const content_type = incoming_res.headers['content-type'] || '';
|
|
76
|
+
if (content_type.includes('application/json')) {
|
|
77
|
+
try {
|
|
78
|
+
response_body = JSON.parse(response_body);
|
|
79
|
+
}
|
|
80
|
+
catch (e) {
|
|
81
|
+
// ignore
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
done(err, response_body);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
client_req.on('error', (err) => {
|
|
88
|
+
done(err);
|
|
89
|
+
});
|
|
90
|
+
client_req.on('timeout', () => {
|
|
91
|
+
client_req.destroy();
|
|
92
|
+
const err = new Error('Request timed out');
|
|
93
|
+
err.code = 'ETIMEDOUT';
|
|
94
|
+
done(err);
|
|
95
|
+
});
|
|
96
|
+
if (request_body) {
|
|
97
|
+
client_req.write(request_body);
|
|
98
|
+
}
|
|
99
|
+
client_req.end();
|
|
100
|
+
}
|
|
101
|
+
function headUrl(url, opts, done) {
|
|
102
|
+
if (typeof opts === 'function') {
|
|
103
|
+
done = opts;
|
|
104
|
+
opts = {};
|
|
105
|
+
}
|
|
106
|
+
if (url.indexOf('http') === 0) {
|
|
107
|
+
webRequest({ url, method: 'HEAD' }, done);
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
const parts = url.match(/s3:\/\/([^/]*)\/(.*)/);
|
|
111
|
+
const Bucket = parts && parts[1];
|
|
112
|
+
const Key = parts && parts[2];
|
|
113
|
+
const s3 = new clientS3.S3Client({ region: opts.region });
|
|
114
|
+
const command = new clientS3.HeadObjectCommand({
|
|
115
|
+
Bucket: Bucket || '',
|
|
116
|
+
Key: Key || '',
|
|
117
|
+
});
|
|
118
|
+
s3.send(command).then((data) => done(null, data), (err) => done(err));
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function fetchFileContents(url, opts, done) {
|
|
122
|
+
if (typeof opts === 'function') {
|
|
123
|
+
done = opts;
|
|
124
|
+
opts = {};
|
|
125
|
+
}
|
|
126
|
+
if (url.indexOf('http') === 0) {
|
|
127
|
+
webRequest({ url }, done);
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
const parts = url.match(/s3:\/\/([^/]*)\/(.*)/);
|
|
131
|
+
const Bucket = parts && parts[1];
|
|
132
|
+
const Key = parts && parts[2];
|
|
133
|
+
const s3 = new clientS3.S3Client({ region: opts.region });
|
|
134
|
+
const command = new clientS3.GetObjectCommand({
|
|
135
|
+
Bucket: Bucket || '',
|
|
136
|
+
Key: Key || '',
|
|
137
|
+
});
|
|
138
|
+
s3.send(command).then((data) => {
|
|
139
|
+
const stream = data.Body;
|
|
140
|
+
let body = '';
|
|
141
|
+
stream.on('data', (chunk) => {
|
|
142
|
+
body += chunk.toString();
|
|
143
|
+
});
|
|
144
|
+
stream.on('end', () => {
|
|
145
|
+
done(null, body);
|
|
146
|
+
});
|
|
147
|
+
stream.on('error', (err) => {
|
|
148
|
+
done(err);
|
|
149
|
+
});
|
|
150
|
+
}, (err) => done(err));
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
var index = {
|
|
155
|
+
init,
|
|
156
|
+
getGitCommitHash,
|
|
157
|
+
};
|
|
158
|
+
const MAX_WAIT_COUNT = 12;
|
|
159
|
+
const SERVER_WAIT_MS = 10 * 1000;
|
|
160
|
+
const DEFAULT_CONFIG = {
|
|
161
|
+
route_prefix: '',
|
|
162
|
+
secret: 'secret',
|
|
163
|
+
sc_update_url_key_name: 'SC_UPDATE_URL',
|
|
164
|
+
restart_function: _defaultRestartFunction,
|
|
165
|
+
service_port: 80,
|
|
166
|
+
http_proto: 'http',
|
|
167
|
+
auth_middleware: false,
|
|
168
|
+
repo_dir: process.env.PWD || '.',
|
|
169
|
+
console_log: console.log,
|
|
170
|
+
error_log: console.error,
|
|
171
|
+
update_launch_default: true,
|
|
172
|
+
remove_old_target: true,
|
|
173
|
+
};
|
|
174
|
+
const g_config = {};
|
|
175
|
+
let g_gitCommitHash = false;
|
|
176
|
+
let g_updateHash = '';
|
|
177
|
+
function init(app, config) {
|
|
178
|
+
Object.assign(g_config, DEFAULT_CONFIG, config);
|
|
179
|
+
if (typeof g_config.route_prefix !== 'string') {
|
|
180
|
+
throw 'server-control route_prefix required';
|
|
181
|
+
}
|
|
182
|
+
if (!g_config.remote_repo_prefix) {
|
|
183
|
+
throw 'server-control remote_repo_prefix required';
|
|
184
|
+
}
|
|
185
|
+
g_config.route_prefix.replace(/\/$/, '');
|
|
186
|
+
g_config.remote_repo_prefix.replace(/\/$/, '');
|
|
187
|
+
asyncSeries([
|
|
188
|
+
(done) => {
|
|
189
|
+
_getAwsRegion(done);
|
|
190
|
+
},
|
|
191
|
+
(done) => {
|
|
192
|
+
getGitCommitHash(() => { });
|
|
193
|
+
const { route_prefix } = g_config;
|
|
194
|
+
if (g_config.remove_old_target) {
|
|
195
|
+
_removeOldTarget();
|
|
196
|
+
}
|
|
197
|
+
app.all(route_prefix + '/server_data', _parseQuery, _secretOrAuth, _serverData);
|
|
198
|
+
app.all(route_prefix + '/group_data', _parseQuery, _secretOrAuth, _groupData);
|
|
199
|
+
app.all(route_prefix + '/update_group', _parseQuery, _secretOrAuth, _updateGroup);
|
|
200
|
+
app.all(route_prefix + '/update_server', _parseQuery, _secretOrAuth, _updateServer);
|
|
201
|
+
done();
|
|
202
|
+
},
|
|
203
|
+
]);
|
|
204
|
+
}
|
|
205
|
+
function _parseQuery(req, res, next) {
|
|
206
|
+
if (typeof req.query === 'string') {
|
|
207
|
+
const query = {};
|
|
208
|
+
req.query.split('&').forEach((key_val) => {
|
|
209
|
+
const split = key_val.split('=');
|
|
210
|
+
query[split[0]] = split[1] || '';
|
|
211
|
+
});
|
|
212
|
+
req.query = query;
|
|
213
|
+
}
|
|
214
|
+
next();
|
|
215
|
+
}
|
|
216
|
+
function _secretOrAuth(req, res, next) {
|
|
217
|
+
if (req.headers && req.headers['x-sc-secret'] === g_config.secret) {
|
|
218
|
+
next();
|
|
219
|
+
}
|
|
220
|
+
else if (req.body &&
|
|
221
|
+
req.body.secret &&
|
|
222
|
+
req.body.secret === g_config.secret) {
|
|
223
|
+
next();
|
|
224
|
+
}
|
|
225
|
+
else if (req.cookies &&
|
|
226
|
+
req.cookies.secret &&
|
|
227
|
+
req.cookies.secret === g_config.secret) {
|
|
228
|
+
next();
|
|
229
|
+
}
|
|
230
|
+
else if (typeof g_config.auth_middleware === 'function') {
|
|
231
|
+
g_config.auth_middleware(req, res, next);
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
res.sendStatus(403);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
function _serverData(req, res) {
|
|
238
|
+
res.header('Cache-Control', 'no-cache, no-store, must-revalidate');
|
|
239
|
+
getGitCommitHash((err, git_commit_hash) => {
|
|
240
|
+
const body = {
|
|
241
|
+
git_commit_hash,
|
|
242
|
+
uptime: process.uptime(),
|
|
243
|
+
};
|
|
244
|
+
if (err) {
|
|
245
|
+
res.status(500);
|
|
246
|
+
body.err = err;
|
|
247
|
+
}
|
|
248
|
+
res.send(body);
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
function _groupData(req, res) {
|
|
252
|
+
res.header('Cache-Control', 'no-cache, no-store, must-revalidate');
|
|
253
|
+
_getGroupData((err, result) => {
|
|
254
|
+
const body = {
|
|
255
|
+
LATEST: result.latest || 'unknown',
|
|
256
|
+
InstanceId: result.InstanceId || 'unknown',
|
|
257
|
+
instance_list: result.instance_list,
|
|
258
|
+
};
|
|
259
|
+
if (result.auto_scale_group) {
|
|
260
|
+
body.auto_scale_group = {
|
|
261
|
+
AutoScalingGroupName: result.auto_scale_group.AutoScalingGroupName,
|
|
262
|
+
LaunchTemplate: result.auto_scale_group.LaunchTemplate,
|
|
263
|
+
};
|
|
264
|
+
if (result.launch_template) {
|
|
265
|
+
body.launch_template = result.launch_template;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
if (err) {
|
|
269
|
+
res.status(500).send({ err, body });
|
|
270
|
+
}
|
|
271
|
+
else {
|
|
272
|
+
res.send(body);
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
function _getGroupData(done) {
|
|
277
|
+
const autoscaling = _getAutoscaling();
|
|
278
|
+
const ec2 = _getEC2();
|
|
279
|
+
let latest = false;
|
|
280
|
+
let InstanceId = false;
|
|
281
|
+
let asg = false;
|
|
282
|
+
let instance_list = [];
|
|
283
|
+
let launch_template = false;
|
|
284
|
+
asyncSeries([
|
|
285
|
+
(done) => {
|
|
286
|
+
_getLatest((err, result) => {
|
|
287
|
+
if (err) {
|
|
288
|
+
_errorLog('_getGroupData: latest err:', err);
|
|
289
|
+
}
|
|
290
|
+
latest = result || false;
|
|
291
|
+
done();
|
|
292
|
+
});
|
|
293
|
+
},
|
|
294
|
+
(done) => {
|
|
295
|
+
const opts = {
|
|
296
|
+
url: 'http://169.254.169.254/latest/meta-data/instance-id',
|
|
297
|
+
...(g_config.metadata_opts || {}),
|
|
298
|
+
};
|
|
299
|
+
webRequest(opts, (err, results) => {
|
|
300
|
+
if (err) {
|
|
301
|
+
_errorLog('_getGroupData: Failed to get instance id:', err);
|
|
302
|
+
}
|
|
303
|
+
InstanceId = results || '';
|
|
304
|
+
done();
|
|
305
|
+
});
|
|
306
|
+
},
|
|
307
|
+
(done) => {
|
|
308
|
+
const command = new clientAutoScaling.DescribeAutoScalingGroupsCommand({});
|
|
309
|
+
autoscaling.send(command).then((data) => {
|
|
310
|
+
asg = data.AutoScalingGroups.find((group) => {
|
|
311
|
+
return (group.AutoScalingGroupName === g_config.asg_name ||
|
|
312
|
+
group.Instances.find((i) => i.InstanceId === InstanceId));
|
|
313
|
+
});
|
|
314
|
+
if (!asg) {
|
|
315
|
+
_errorLog('_getGroupData: asg not found:', g_config.asg_name);
|
|
316
|
+
done('asg_not_found');
|
|
317
|
+
}
|
|
318
|
+
else {
|
|
319
|
+
done();
|
|
320
|
+
}
|
|
321
|
+
}, (err) => {
|
|
322
|
+
_errorLog('_getGroupData: find asg err:', err);
|
|
323
|
+
done(err);
|
|
324
|
+
});
|
|
325
|
+
},
|
|
326
|
+
(done) => {
|
|
327
|
+
const opts = {
|
|
328
|
+
InstanceIds: asg.Instances.map((i) => i.InstanceId),
|
|
329
|
+
};
|
|
330
|
+
const command = new clientEc2.DescribeInstancesCommand(opts);
|
|
331
|
+
ec2.send(command).then((results) => {
|
|
332
|
+
instance_list = [];
|
|
333
|
+
results.Reservations.forEach((reservation) => {
|
|
334
|
+
reservation.Instances.forEach((i) => {
|
|
335
|
+
instance_list.push({
|
|
336
|
+
InstanceId: i.InstanceId,
|
|
337
|
+
PrivateIpAddress: i.PrivateIpAddress,
|
|
338
|
+
PublicIpAddress: i.PublicIpAddress,
|
|
339
|
+
LaunchTime: i.LaunchTime,
|
|
340
|
+
ImageId: i.ImageId,
|
|
341
|
+
InstanceType: i.InstanceType,
|
|
342
|
+
State: i.State,
|
|
343
|
+
});
|
|
344
|
+
});
|
|
345
|
+
});
|
|
346
|
+
done();
|
|
347
|
+
}, (err) => {
|
|
348
|
+
_errorLog('_getGroupData: describeInstances err:', err);
|
|
349
|
+
done(err);
|
|
350
|
+
});
|
|
351
|
+
},
|
|
352
|
+
(done) => {
|
|
353
|
+
const list = instance_list.filter((i) => i.State.Name === 'running');
|
|
354
|
+
asyncEach(list, (instance, done) => {
|
|
355
|
+
_getServerData(instance, (err, body) => {
|
|
356
|
+
instance.git_commit_hash = body && body.git_commit_hash;
|
|
357
|
+
instance.uptime = body && body.uptime;
|
|
358
|
+
done(err);
|
|
359
|
+
});
|
|
360
|
+
}, done);
|
|
361
|
+
},
|
|
362
|
+
(done) => {
|
|
363
|
+
const lt = asg.LaunchTemplate ||
|
|
364
|
+
asg.MixedInstancesPolicy?.LaunchTemplate?.LaunchTemplateSpecification;
|
|
365
|
+
const opts = {
|
|
366
|
+
LaunchTemplateId: lt?.LaunchTemplateId,
|
|
367
|
+
Versions: [lt?.Version],
|
|
368
|
+
};
|
|
369
|
+
const command = new clientEc2.DescribeLaunchTemplateVersionsCommand(opts);
|
|
370
|
+
ec2.send(command).then((data) => {
|
|
371
|
+
if (data?.LaunchTemplateVersions?.length > 0) {
|
|
372
|
+
launch_template = data.LaunchTemplateVersions[0];
|
|
373
|
+
const ud = launch_template.LaunchTemplateData.UserData;
|
|
374
|
+
if (ud) {
|
|
375
|
+
const s = Buffer.from(ud, 'base64').toString('utf8');
|
|
376
|
+
launch_template.LaunchTemplateData.UserData = s;
|
|
377
|
+
}
|
|
378
|
+
done();
|
|
379
|
+
}
|
|
380
|
+
else {
|
|
381
|
+
done('launch_template_not_found');
|
|
382
|
+
}
|
|
383
|
+
}, (err) => {
|
|
384
|
+
_errorLog('_getGroupData: launch template fetch error:', err);
|
|
385
|
+
done(err);
|
|
386
|
+
});
|
|
387
|
+
},
|
|
388
|
+
], (err) => {
|
|
389
|
+
const ret = {
|
|
390
|
+
latest,
|
|
391
|
+
InstanceId,
|
|
392
|
+
auto_scale_group: asg,
|
|
393
|
+
launch_template,
|
|
394
|
+
instance_list,
|
|
395
|
+
};
|
|
396
|
+
done(err, ret);
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
function _getServerData(instance, done) {
|
|
400
|
+
const proto = g_config.http_proto;
|
|
401
|
+
const ip = instance.PrivateIpAddress;
|
|
402
|
+
const port = g_config.service_port;
|
|
403
|
+
const prefix = g_config.route_prefix;
|
|
404
|
+
const url = `${proto}://${ip}:${port}${prefix}/server_data`;
|
|
405
|
+
const opts = {
|
|
406
|
+
url,
|
|
407
|
+
method: 'GET',
|
|
408
|
+
headers: {
|
|
409
|
+
'x-sc-secret': g_config.secret,
|
|
410
|
+
},
|
|
411
|
+
json: {
|
|
412
|
+
secret: g_config.secret,
|
|
413
|
+
},
|
|
414
|
+
};
|
|
415
|
+
webRequest(opts, (err, body) => {
|
|
416
|
+
if (err) {
|
|
417
|
+
_errorLog('_getServerData: request err:', err);
|
|
418
|
+
}
|
|
419
|
+
done(err, body);
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
function _updateServer(req, res) {
|
|
423
|
+
res.header('Cache-Control', 'no-cache, no-store, must-revalidate');
|
|
424
|
+
const hash = req.body.hash || req.query.hash;
|
|
425
|
+
if (hash) {
|
|
426
|
+
_updateSelf(hash, (err) => {
|
|
427
|
+
if (err) {
|
|
428
|
+
res.status(500).send(err);
|
|
429
|
+
}
|
|
430
|
+
else {
|
|
431
|
+
res.send('Restarting server');
|
|
432
|
+
g_config.restart_function();
|
|
433
|
+
}
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
else {
|
|
437
|
+
res.status(400).send('hash is required');
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
function _updateSelf(hash, done) {
|
|
441
|
+
const dir = g_config.repo_dir;
|
|
442
|
+
const url = `${g_config.remote_repo_prefix}/${hash}.tar.gz`;
|
|
443
|
+
const cmd = `cd ${dir} && ${__dirname}/../scripts/update_to_hash.sh ${url}`;
|
|
444
|
+
child_process__namespace.exec(cmd, (err, stdout, stderr) => {
|
|
445
|
+
if (err) {
|
|
446
|
+
_errorLog('_updateSelf: update_to_hash.sh failed with err:', err, 'stdout:', stdout, 'stderr:', stderr);
|
|
447
|
+
err = 'update_failed';
|
|
448
|
+
}
|
|
449
|
+
else {
|
|
450
|
+
g_updateHash = hash;
|
|
451
|
+
}
|
|
452
|
+
done(err);
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
function _removeOldTarget() {
|
|
456
|
+
const dir = g_config.repo_dir;
|
|
457
|
+
const cmd = `${__dirname}/../scripts/remove_old_target.sh ${dir}`;
|
|
458
|
+
child_process__namespace.exec(cmd, (err, stdout, stderr) => {
|
|
459
|
+
if (err) {
|
|
460
|
+
_errorLog('_removeOldTarget: remove_old_target.sh failed with err:', err, 'stdout:', stdout, 'stderr:', stderr);
|
|
461
|
+
}
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
function _updateGroup(req, res) {
|
|
465
|
+
res.header('Cache-Control', 'no-cache, no-store, must-revalidate');
|
|
466
|
+
const hash = req.body.hash || req.query.hash;
|
|
467
|
+
if (hash) {
|
|
468
|
+
const url = `${g_config.remote_repo_prefix}/${hash}.tar.gz`;
|
|
469
|
+
const key_name = g_config.sc_update_url_key_name;
|
|
470
|
+
const ami_id = req.body.ami_id || req.query.ami_id || false;
|
|
471
|
+
const ec2 = _getEC2();
|
|
472
|
+
let group_data = false;
|
|
473
|
+
let old_data = '';
|
|
474
|
+
let new_version;
|
|
475
|
+
const server_result = {};
|
|
476
|
+
asyncSeries([
|
|
477
|
+
(done) => {
|
|
478
|
+
_getGroupData((err, result) => {
|
|
479
|
+
if (!err) {
|
|
480
|
+
group_data = result;
|
|
481
|
+
const data = result.launch_template.LaunchTemplateData.UserData;
|
|
482
|
+
data.split('\n').forEach((line) => {
|
|
483
|
+
if (line.length && line.indexOf(key_name) === -1) {
|
|
484
|
+
old_data += line + '\n';
|
|
485
|
+
}
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
done(err);
|
|
489
|
+
});
|
|
490
|
+
},
|
|
491
|
+
(done) => {
|
|
492
|
+
headUrl(url, { region: g_config.region }, (err) => {
|
|
493
|
+
if (err) {
|
|
494
|
+
_errorLog('_updateGroup: head url:', url, 'err:', err);
|
|
495
|
+
err = 'url_not_found';
|
|
496
|
+
}
|
|
497
|
+
done(err);
|
|
498
|
+
});
|
|
499
|
+
},
|
|
500
|
+
(done) => {
|
|
501
|
+
const new_data = `${old_data}${key_name}=${url}\n`;
|
|
502
|
+
const opts = {
|
|
503
|
+
LaunchTemplateId: group_data.launch_template.LaunchTemplateId,
|
|
504
|
+
SourceVersion: String(group_data.launch_template.VersionNumber),
|
|
505
|
+
LaunchTemplateData: {
|
|
506
|
+
UserData: Buffer.from(new_data, 'utf8').toString('base64'),
|
|
507
|
+
},
|
|
508
|
+
};
|
|
509
|
+
if (ami_id) {
|
|
510
|
+
opts.LaunchTemplateData.ImageId = ami_id;
|
|
511
|
+
}
|
|
512
|
+
const command = new clientEc2.CreateLaunchTemplateVersionCommand(opts);
|
|
513
|
+
ec2.send(command).then((data) => {
|
|
514
|
+
new_version = data.LaunchTemplateVersion.VersionNumber;
|
|
515
|
+
done();
|
|
516
|
+
}, (err) => {
|
|
517
|
+
_errorLog('_updateGroup: failed to create version, err:', err);
|
|
518
|
+
done(err);
|
|
519
|
+
});
|
|
520
|
+
},
|
|
521
|
+
(done) => {
|
|
522
|
+
if (g_config.update_launch_default) {
|
|
523
|
+
const opts = {
|
|
524
|
+
DefaultVersion: String(new_version),
|
|
525
|
+
LaunchTemplateId: group_data.launch_template.LaunchTemplateId,
|
|
526
|
+
};
|
|
527
|
+
const command = new clientEc2.ModifyLaunchTemplateCommand(opts);
|
|
528
|
+
ec2.send(command).then(() => {
|
|
529
|
+
done();
|
|
530
|
+
}, (err) => {
|
|
531
|
+
_errorLog('_updateGroup: failed to update default, err:', err);
|
|
532
|
+
done(err);
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
else {
|
|
536
|
+
done();
|
|
537
|
+
}
|
|
538
|
+
},
|
|
539
|
+
(done) => {
|
|
540
|
+
let group_err;
|
|
541
|
+
asyncEach(group_data.instance_list, (instance, done) => {
|
|
542
|
+
if (instance.InstanceId === group_data.InstanceId) {
|
|
543
|
+
done();
|
|
544
|
+
}
|
|
545
|
+
else {
|
|
546
|
+
_updateInstance(hash, instance, (err) => {
|
|
547
|
+
if (err) {
|
|
548
|
+
_errorLog('_updateGroup: update instance:', instance.InstanceId, 'err:', err);
|
|
549
|
+
group_err = err;
|
|
550
|
+
}
|
|
551
|
+
server_result[instance.InstanceId] = err;
|
|
552
|
+
done();
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
}, () => done(group_err));
|
|
556
|
+
},
|
|
557
|
+
(done) => {
|
|
558
|
+
_updateSelf(hash, (err) => {
|
|
559
|
+
server_result[group_data.InstanceId] = err;
|
|
560
|
+
done(err);
|
|
561
|
+
});
|
|
562
|
+
},
|
|
563
|
+
], (err) => {
|
|
564
|
+
const body = {
|
|
565
|
+
err,
|
|
566
|
+
server_result,
|
|
567
|
+
launch_template_version: new_version,
|
|
568
|
+
};
|
|
569
|
+
if (err) {
|
|
570
|
+
res.status(500).send(body);
|
|
571
|
+
}
|
|
572
|
+
else {
|
|
573
|
+
body._msg =
|
|
574
|
+
'Successful updating all servers, restarting this server.';
|
|
575
|
+
res.send(body);
|
|
576
|
+
g_config.restart_function();
|
|
577
|
+
}
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
else {
|
|
581
|
+
res.status(400).send('hash is required');
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
function _updateInstance(hash, instance, done) {
|
|
585
|
+
asyncSeries([
|
|
586
|
+
(done) => {
|
|
587
|
+
const proto = g_config.http_proto;
|
|
588
|
+
const ip = instance.PrivateIpAddress;
|
|
589
|
+
const port = g_config.service_port;
|
|
590
|
+
const prefix = g_config.route_prefix;
|
|
591
|
+
const url = `${proto}://${ip}:${port}${prefix}/update_server`;
|
|
592
|
+
const opts = {
|
|
593
|
+
url,
|
|
594
|
+
method: 'GET',
|
|
595
|
+
headers: {
|
|
596
|
+
'x-sc-secret': g_config.secret,
|
|
597
|
+
},
|
|
598
|
+
json: {
|
|
599
|
+
hash,
|
|
600
|
+
secret: g_config.secret,
|
|
601
|
+
},
|
|
602
|
+
};
|
|
603
|
+
webRequest(opts, done);
|
|
604
|
+
},
|
|
605
|
+
(done) => _waitForServer({ instance, hash }, done),
|
|
606
|
+
], done);
|
|
607
|
+
}
|
|
608
|
+
function _waitForServer(params, done) {
|
|
609
|
+
const { instance, hash } = params;
|
|
610
|
+
let count = 0;
|
|
611
|
+
asyncForever((done) => {
|
|
612
|
+
count++;
|
|
613
|
+
_getServerData(instance, (err, body) => {
|
|
614
|
+
if (!err && body && body.git_commit_hash === hash) {
|
|
615
|
+
done('stop');
|
|
616
|
+
}
|
|
617
|
+
else if (count > MAX_WAIT_COUNT) {
|
|
618
|
+
done('too_many_tries');
|
|
619
|
+
}
|
|
620
|
+
else {
|
|
621
|
+
setTimeout(done, SERVER_WAIT_MS);
|
|
622
|
+
}
|
|
623
|
+
});
|
|
624
|
+
}, (err) => {
|
|
625
|
+
if (err === 'stop') {
|
|
626
|
+
err = null;
|
|
627
|
+
}
|
|
628
|
+
done(err);
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
function _getLatest(done) {
|
|
632
|
+
const url = g_config.remote_repo_prefix + '/LATEST';
|
|
633
|
+
fetchFileContents(url, { region: g_config.region }, (err, body) => {
|
|
634
|
+
done(err, body && body.trim());
|
|
635
|
+
});
|
|
636
|
+
}
|
|
637
|
+
function getGitCommitHash(done) {
|
|
638
|
+
if (typeof g_gitCommitHash === 'string') {
|
|
639
|
+
done && done(null, g_gitCommitHash);
|
|
640
|
+
}
|
|
641
|
+
else {
|
|
642
|
+
const file = node_path.join(g_config.repo_dir, '.git_commit_hash');
|
|
643
|
+
fs__namespace.readFile(file, 'utf8', (err, result) => {
|
|
644
|
+
if (!err && !result) {
|
|
645
|
+
err = 'no_result';
|
|
646
|
+
}
|
|
647
|
+
if (err) {
|
|
648
|
+
_errorLog('getGitCommitHash: err:', err, 'file:', file);
|
|
649
|
+
}
|
|
650
|
+
else {
|
|
651
|
+
g_gitCommitHash = result.trim();
|
|
652
|
+
}
|
|
653
|
+
done && done(err, g_gitCommitHash);
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
function _getAwsRegion(done) {
|
|
658
|
+
if (g_config.region) {
|
|
659
|
+
return done();
|
|
660
|
+
}
|
|
661
|
+
const opts = {
|
|
662
|
+
url: 'http://169.254.169.254/latest/dynamic/instance-identity/document',
|
|
663
|
+
...(g_config.metadata_opts || {}),
|
|
664
|
+
};
|
|
665
|
+
webRequest(opts, (err, results) => {
|
|
666
|
+
if (err) {
|
|
667
|
+
_errorLog('_getAwsRegion: metadata err:', err);
|
|
668
|
+
}
|
|
669
|
+
else {
|
|
670
|
+
try {
|
|
671
|
+
const json = JSON.parse(results);
|
|
672
|
+
if (json && json.region) {
|
|
673
|
+
g_config.region = json.region;
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
catch (e) {
|
|
677
|
+
_errorLog('_getAwsRegion: threw:', e);
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
done();
|
|
681
|
+
});
|
|
682
|
+
}
|
|
683
|
+
function _getAutoscaling() {
|
|
684
|
+
return new clientAutoScaling.AutoScalingClient({ region: g_config.region });
|
|
685
|
+
}
|
|
686
|
+
function _getEC2() {
|
|
687
|
+
return new clientEc2.EC2Client({ region: g_config.region });
|
|
688
|
+
}
|
|
689
|
+
function _errorLog(...args) {
|
|
690
|
+
g_config.error_log(...args);
|
|
691
|
+
}
|
|
692
|
+
function _defaultRestartFunction() {
|
|
693
|
+
g_config.console_log('server-control: updated to: ', g_updateHash, 'restarting...');
|
|
694
|
+
setTimeout(function () {
|
|
695
|
+
process.exit(0);
|
|
696
|
+
}, 100);
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
exports.default = index;
|
|
700
|
+
exports.getGitCommitHash = getGitCommitHash;
|
|
701
|
+
exports.init = init;
|