-
Notifications
You must be signed in to change notification settings - Fork 1
/
cli-args.js
369 lines (357 loc) · 15.3 KB
/
cli-args.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
const fs = require('fs');
const path = require('path');
const logging = require('./lib/cli/logging');
exports.knownActions = [
'start', 'restart', 'stop',
'scale', 'kill', 'show',
'log', 'add', 'delete', 'inspect'];
exports.options = [
{
name: 'actionSelect',
value: String,
defaultOption: true,
multiple: true,
defaultValue: []
},
{
name: 'config',
alias: 'c',
typeLabel: '{underline File|Folder}',
type: String,
description: "Default: process-config.\\{js,json\\}\n" +
"Load a configuration file. If the given path is a folder, also checks parent folders. " +
"If you specified a configuration for an already running application, it will only be applied " +
"once the application is manually (re-)started, but not when a new process is spawned after a crash.",
lazyMultiple: true,
defaultValue: []
},
{
name: 'set',
typeLabel: '[{underline app}:]{underline key}={underline value}',
type: String,
description: "Override a configuration key.",
lazyMultiple: true,
defaultValue: []
},
{
name: 'lines',
alias: 'n',
typeLabel: '{underline num}',
type: Number,
description: "When using the {bold log} action, sets the number of past log lines to display. " +
"Up to {bold max-buffered-log-bytes} (see --help-configuration).",
defaultValue: 10
},
{
name: 'follow',
alias: 'f',
type: Boolean,
description: "When using the {bold log} action, will output new log lines continously as they appear. " +
"Cancel with {italic CTRL-C}.",
defaultValue: false
},
{
name: 'launch',
type: Boolean,
description: "Start the daemon even if there's nothing to do.",
defaultValue: false
},
{
name: 'kill',
type: Boolean,
description: "Stop the daemon, ungracefully killing any remaining processes. " +
"This is done after all other commands have been sent to the daemon.\n" +
"Use {italic 'final-pm --wait --kill stop all'} to achieve a graceful stop.",
defaultValue: false
},
{
name: 'wait',
type: Boolean,
description: "Wait for any pending actions to complete. This means final-pm will only return once " +
"the {bold queue}, {bold new}, {bold old} and {bold marked generations} are empty.",
defaultValue: false
},
{
name: 'force',
type: Boolean,
description: "Make final-pm ignore some safeguards. (I hope you know what you're doing)",
defaultValue: false
},
{
name: 'no-upload',
type: Boolean,
description: "Don't upload new application configurations from config files.",
defaultValue: false
},
{
name: 'dry',
type: Boolean,
description: "Don't actually do anything, use {italic --verbose} for more output.",
defaultValue: false
},
{
name: 'verbose',
alias: 'v',
type: Boolean,
description: "Show debug output.",
defaultValue: false
},
{
name: 'help',
type: Boolean,
description: "Print short usage guide.",
defaultValue: false
},
{
name: 'help-usage',
type: Boolean,
description: "Print full usage guide including {bold actions}.",
defaultValue: false
},
{
name: 'help-generations',
type: Boolean,
description: "Print help page about {bold generations}.",
defaultValue: false
},
{
name: 'help-example',
type: Boolean,
description: "Print a short example application.",
defaultValue: false
},
{
name: 'help-configuration',
type: Boolean,
description: "Print full configuration help.",
defaultValue: false
},
{
name: 'help-all',
type: Boolean,
description: "Print full help page.",
defaultValue: false
},
];
exports.help = [
{
header: "Options",
content: [
"# final-pm [--config {underline File|Folder}] [{underline Action} {underline Select}...]"
]
},
{
optionList: exports.options,
hide: "actionSelect"
},
];
exports.usage = [
{
header: "FinalPM",
content: [
"{italic Finally a good process manager.}",
"",
"By default all actions are {bold graceful}. Old processes will always be cleanly stopped only " +
"once new processes have indicated they are {bold ready}.",
"",
"{underline Examples}",
"",
"# Start processes of all configured applications.",
"final-pm start all",
"",
"# Override configuration settings and start 4 instances of 'worker'",
"final-pm --set worker:instances=4 start worker",
"",
"# Stop processes by PID",
"final-pm stop pid=43342 pid=3452",
"",
"# Stop processes by application name 'worker'",
"final-pm stop worker",
"",
"# Stop the first and second currently running worker",
"final-pm stop running:worker/0 running:worker/1",
],
},
].concat(exports.help).concat([
{
content: [
"",
"{bold Selectors}",
"",
"A selector identifies a process or an application.",
"",
"A selector can either be an {italic application name}, internal process ID (id={italic id}), " +
"or OS process ID (pid={italic pid}). " +
"Using {bold all} as a selector will target all applications found in the configuration or " +
"which are running, depending on the action. An application name followed by /{italic N} " +
"(slash {italic N}) will only select the {italic N}-th process of that application. " +
"Prefix your selector with {bold new:}, {bold running:}, {bold old:}, or {bold marked:} " +
"to only target processes in that {bold generation}. See the usage examples above.",
"",
"{bold Actions}",
"",
"Valid actions are {bold start}, {bold stop}, {bold kill}, {bold scale}, {bold show}, " +
"{bold inspect}, {bold add}, {bold delete}, {bold log}.",
"",
"{underline start / restart}",
"Upload configuration (implies {bold add}), then start N={italic instances} processes for all selected applications. " +
"When processes are selected this will start one new process for each selected one instead. " +
"May cause existing processes to be gracefully stopped when the newly started ones are ready, and " +
"will even implicitly stop more processes than were started when {italic instances} was decreased " +
"in the configuration. Note that this may replace different processes than the selected ones, or none at all, " +
"if {italic unique-instances} is set to {italic false}. In which case the oldest ones of that application " +
"will be replaced if {italic instances} was exceeded.",
"",
"{underline stop}",
"Gracefully stop all selected {italic running/new} processes or applications.",
"",
"{underline kill}",
"Immediately {bold SIGKILL} all selected processes or applications. This works on processes in any {bold generation}.",
"",
"{underline scale}",
"Upload configuration (implies {bold add}), then start or stop processes for each selected application " +
"until the number of running processes matches configured {italic instances}.",
"",
"{underline show}",
"Show an overview of (selected) processes.",
"Use {bold --verbose} to also show logging processes.",
"",
"{underline inspect}",
"Show detailed information for all selected applications / processes.",
"Use {bold --verbose} to show even more detailed information.",
"",
"{underline add}",
"Upload application configurations to the daemon, replacing older instances of the same configuration.",
"",
"{underline delete}",
"Delete application configurations from the daemon.",
"",
"{underline log}",
"Show process output. Understands {bold --follow} and {bold --lines}, which work the same as the UNIX {italic tail} command.",
]
}
]);
exports.generations = [
{
header: "Generations",
content: [
"Processes are grouped in generations:",
"The {bold queue}, {bold new}, {bold running}, {bold old}, and {bold marked generation}.",
"",
"{underline Queue Generation}",
"All processes begin in this generation and remain here until they can be started. Usually " +
"they can be started immediately unless {bold max-instances} is reached.",
"",
"{underline New Generation}",
"The {bold new generation} is where processes remain until they are considered {bold ready}. " +
"A process is considered to be {bold ready} on the cluster {bold listen} event " +
"or when it sends the {bold ready} message, depending on the configuration (config: {bold ready-on}). " +
"Once a process is {bold ready} it is moved to the {bold running generation}. " +
"If a process is asked to be stopped while in the new generation, it is moved to the {bold marked generation} instead. " +
"If a process exits abnormally while in the new generation, a new one is started (config: {bold restart-new-crashing}).",
"",
"{underline Running Generation}",
"The {bold running generation} is where processes remain until they are {bold stopped}. At most the configured amount of " +
"processes for each application may reside here. If {italic unique-instances} is set to {italic false} and the maximum " +
"{italic instances} was exceeded because new processes were started, the oldest processes will be moved to the {bold old generation}. " +
"If {italic unique-instances} is set to {italic true}, each process will replace its counterpart 1:1 instead, and only then will " +
"additional processes be stopped if {italic instances} is exceeded. If a process exits abnormally while in the running generation, " +
"a new one is started (config: {bold restart-crashing}). Note that an older process can never replace a process that was started " +
"later, ensuring always the latest processes are running even if startup time wildly varies.",
"",
"{underline Old Generation}",
"The {bold old generation} is where processes remain when they should be {bold stopped} until they finally {bold exit}. " +
"A process moved to the {bold old generation} is sent the {bold SIGINT} signal. If the process does not exit within " +
"{bold stop-timeout} (default is no timeout), it is sent {bold SIGKILL} and removed from the old generation.",
"",
"{underline Marked Generation}",
"New processes who were asked to stop are kept here, then are moved to the {bold old generation} " +
"once they are {bold ready}. This means the programmer never has to worry about handling " +
"{bold SIGINT} signals during startup."
]
}
];
exports.example = [
{
header: "Example",
},
{
content: {
options: {
noTrim: true
},
data: [
{ col: "{underline Example Config}" },
{ col: "{italic final-pm --config sample-config.js start myApp}\n" }
].concat(fileToColumns('examples/sample-config.js'))
}
},
{
content: {
options: {
noTrim: true
},
data: [
{ col: "{underline Example App}\n" }
].concat(fileToColumns('examples/sample-app.js'))
}
}
];
exports.configuration = [
{
header: "Configuration",
content: [
"Configuration may be done in either JSON or JS, as well as environment variables and command line arguments. " +
"When using a JS configuration file, you may either export the configuration directly, export a promise, or export " +
"a function that returns a promise/configuration when called.",
"On the command line configuration keys may be overriden with {bold --set} {italic key}={italic value}, where " +
"{italic key} may be any configuration key.",
"",
"To override keys within an appliaction config, prefix {italic key} with " +
"'{italic application-name}:' like so: --set myApp:ready-on=\"message\". " +
"Configuration keys can also be overriden with environment variables by replacing all dashes and colons in {italic key} " +
"with underscores, converting to uppercase, and prefixing them with 'FINAL_PM_CONFIG_'. " +
"For example: FINAL_PM_CONFIG_MYAPP_LOGGER=journald.",
"",
"{underline Logging}",
"Logging is done by a logging process started for each application, which will be fed logging output via process.send(logLine). " +
"Logger processes are started with the same CWD as your application. Keep this in mind when passing relative paths to loggers. " +
"The logging process is automatically started with your application, and is stopped once the last process of your application exits. " +
"By default all applications use the simple file-logger that ships with final-pm, but creating a custom logger is very simple. " +
"Have a look at the file-logger if you're curious how to create your own logger: ",
"https://github.com/laino/final-pm/blob/master/loggers/file.js",
"All output of logger processes themselves will end up in the daemon log file ({italic daemon-log})."
]
},
{
content: {
options: {
noTrim: true
},
data: [
{ col: "{underline Default Config}\n" },
].concat(fileToColumns('config/default-config.js'))
}
},
{
content: {
options: {
noTrim: true
},
data: [
{ col: "{underline Default Application Config}\n" }
].concat(fileToColumns('config/default-application-config.js'))
}
}
];
exports.all = exports.usage.concat(exports.generations, exports.configuration, exports.example);
exports.isKnownAction = function(arg) {
return exports.knownActions.indexOf(arg) !== -1;
};
function fileToColumns(file) {
return fs.readFileSync(path.resolve(__dirname, file))
.toString()
.split('\n')
.map(logging.escape)
.map(col => {return {col};});
}