-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathcli.js
More file actions
225 lines (180 loc) · 6.91 KB
/
Copy pathcli.js
File metadata and controls
225 lines (180 loc) · 6.91 KB
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
#!/usr/bin/env node
const path = require('path');
const {execSync, spawnSync} = require('child_process');
const program = require('commander');
const chalk = require('chalk');
const deepAssign = require('deep-assign');
const updateNotifier = require('update-notifier');
const pkg = require('../package.json');
const {preprocessArgs, fileExists, parseConfig, collect, ensureArray} = require('./internals');
const defaultConfig = parseConfig(require('./defaultConfig.json'));
// Notify for updates
if (pkg && pkg.version) {
updateNotifier({pkg}).notify();
}
// Preprocess args
const {dockerProjectArgs, args, action} = preprocessArgs(process.argv);
// Define commander
program
.version(pkg.version || 'dev')
.option('-e, --env <name>', 'Overrides the selected environment [default: development]')
.option('-p, --path <path>', 'Path to your projects root folder, [default: CWD]')
.option('-s, --service <name>', 'Overrides the targeted docker service')
.option('-f, --file [filepath ...]', 'Overrides the targeted docker-compose file(s)', collect, [])
.option('-u, --user <user>', 'Run the command as this user')
.option('-i, --index <index>', 'Index of the service if there are multiple [default: 1]')
.option('-p, --privileged', 'Give extended privileges to the executed command process')
.option('-v, --verbose', 'Adds additional logging')
.option('-d, --detached', 'Detached mode: Run command in the background.')
.option('-l, --list', 'List the available actions that can be run as ' + chalk.gray('dopr <action>'))
.parse(dockerProjectArgs);
// Set defaults
const basePath = program.path || process.cwd();
const env = program.env || process.env.NODE_ENV || 'development';
// Try to find package json
const packageFile = path.resolve(basePath + '/package.json');
const doprFile = path.resolve(basePath + '/docker-project.json');
const packageFileExists = fileExists(packageFile);
const doprFileExists = fileExists(doprFile);
if (!packageFileExists && !doprFileExists) {
console.error(chalk.red('neither package.json nor docker-project.json found in: ' + basePath));
console.log(chalk.gray('Run this tool from your projects root directory or supply a --path.'));
process.exit(1);
}
// Ensure local configuration exists
const packageConfig = packageFileExists
? parseConfig(require(packageFile).dopr)
: null;
const doprConfigRaw = doprFileExists
? require(doprFile)
: null;
const doprConfig = parseConfig(doprConfigRaw && (doprConfigRaw.dopr || doprConfigRaw));
if (!packageConfig && !doprConfig) {
console.log(chalk.red('dopr is not configured.'));
process.exit(1);
}
// Backward compat.
if (packageConfig) {
packageConfig.file = ensureArray(packageConfig.file);
}
if (doprConfig) {
doprConfig.file = ensureArray(doprConfig.file);
}
// Construct configuration
const mergedConfig = deepAssign({}, defaultConfig, packageConfig, doprConfig);
const environmentConfig = (mergedConfig.environments && mergedConfig.environments[env]) || {};
const config = deepAssign({}, mergedConfig, environmentConfig);
if (program.list) {
console.log('Available actions:\n');
Object.keys(config.actions).forEach(action => {
const comment = config.actions[action].comment || '';
console.log(' ' + action.padEnd(16, ' ') + chalk.gray(comment));
});
process.exit(0);
}
// Default action
const defaultAction = {
file: config.file || null,
service: config.service || null,
command: config.command || ['%action% %args%'],
user: config.user || null,
privileged: config.privileged || false,
exec: true,
detached: false,
index: null
};
// Program action
const programAction = {};
['detached', 'exec', 'file', 'index', 'privileged', 'service', 'user'].forEach(key => {
const value = program[key];
if (value !== undefined && (key !== 'file' || value.length > 0)) {
programAction[key] = value;
}
});
// Final action
const configAction = (config.actions && config.actions[action]) || {};
const cliAction = Object.assign({}, defaultAction, configAction, programAction);
// Validation
if (!cliAction.file || cliAction.file.length === 0) {
console.log(chalk.red('\'file\' not configured.'));
process.exit(1);
}
if (cliAction.exec && !cliAction.service) {
console.log(chalk.red('\'service\' not configured.'));
process.exit(1);
}
cliAction.file = ensureArray(cliAction.file);
cliAction.command = ensureArray(cliAction.command);
const dockerComposeFiles = [];
// Validate/sanitize all docker-compose files!
cliAction.file.forEach((file, pos) => {
file = path.resolve(file);
// Relative to path argument.
if (program.path && !fileExists(file)) {
file = path.join(program.path, cliAction.file[pos]);
}
if (!fileExists(file)) {
console.log(chalk.yellow('docker-compose file not found at: ' + file));
return;
}
dockerComposeFiles.push('--file', file);
});
if (dockerComposeFiles.length === 0) {
console.log(chalk.red('at least one docker-compose file is required to exist'));
process.exit(1);
}
const cliOptions = {
cwd: basePath,
stdio: 'inherit',
shell: true
};
const exitHandler = code => {
if (program.verbose) {
console.log((code > 0 ? chalk.red : chalk.gray)(`command exited with code ${code}`));
}
// Pass through exit code
if (code !== 0) {
process.exit(code);
}
};
// Default args given!
if (args.length === 0 && cliAction.args !== undefined) {
args.push(cliAction.args);
}
// Run commands synchronously one after another!
cliAction.command.forEach(command => {
if (program.verbose) {
console.log(chalk.gray('ENV: ' + env));
console.log(chalk.gray('CWD: ' + basePath));
}
// Is it a reference to another action?
if (command[0] === '@') {
const refArgs = dockerProjectArgs.slice(2).concat(command.substr(1));
if (program.verbose) {
console.log(chalk.gray('CMD: dopr ' + refArgs.join(' ')));
}
// Fire!
return exitHandler(spawnSync('dopr ', refArgs, cliOptions).status);
}
// Parse command
const cliCommand = command
.replace(/%action%/g, action || '')
.replace(/%args%/g, args.join(' '));
// Command is expected to run in host context!
if (cliAction.service === '@host') {
return execSync(cliCommand, cliOptions);
}
// Args
const user = cliAction.user ? ['--user', cliAction.user] : [];
const cliArgs = dockerComposeFiles
.concat(cliAction.exec ? ['exec', ...user, cliAction.service] : [])
.concat(cliAction.detached ? ['-d'] : [])
.concat(cliAction.privileged ? ['--privileged'] : [])
.concat(cliAction.index ? ['--index', cliAction.index] : [])
.concat(cliCommand);
if (program.verbose) {
console.log(chalk.gray('CMD: docker compose ' + cliArgs.join(' ')));
}
// Fire!
exitHandler(spawnSync('docker compose', cliArgs, cliOptions).status);
});