-
Notifications
You must be signed in to change notification settings - Fork 7
Expand file tree
/
Copy pathdocker-cmd-manager.js
More file actions
281 lines (261 loc) · 13.3 KB
/
docker-cmd-manager.js
File metadata and controls
281 lines (261 loc) · 13.3 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
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
'use strict';
var DockerCmd = require('./docker-cmd');
var fs = require('fs');
var path = require('path');
var util = require('util');
var extend = require('extend');
/**
* This class manages the DockerCmd and handles dockerdesc.json
* @constructor
* @param {string} [dockerdescPath='./dockerdesc.json'] - Path to `dockerdesc.json` file or parent dir
*/
function DockerCmdManager(dockerdescPath) {
dockerdescPath = dockerdescPath || './dockerdesc.json';
if (!fs.existsSync(dockerdescPath)) {
throw new Error(util.format('The path "%s" does not exists.', dockerdescPath));
}
/** @type {string} */
this.dockerdescDir = path.dirname(dockerdescPath);
var dockerdescPathStat = fs.statSync(dockerdescPath);
if (dockerdescPathStat.isDirectory()) {
this.dockerdescDir = dockerdescPath;
dockerdescPath = path.join(dockerdescPath, 'dockerdesc.json');
}
/** @type {Dockerdesc} */
var dockerdescContent = fs.readFileSync(dockerdescPath);
try {
this.dockerdesc = JSON.parse(dockerdescContent);
} catch (err) {
throw new Error('Problem in the dockerdesc.json file format.\n' + err.stack);
}
}
DockerCmdManager.prototype.build = _dockerCommandBuilder('build', ['t', 'tag'],
/** @type {DockerCmdManager~CommandHandler} */ function(commandName, descriptionTarget, descriptionOptions, commandOptions, dockerOptions, finalDescription, dockerdescName, callback) {
// getting the Dockerfile path and parent dir
var dockerfilePath = finalDescription.path || (commandOptions._ || [])[0] || descriptionTarget;
if (path.resolve(dockerfilePath) !== path.normalize(dockerfilePath)) { // checking if absolute path, thanks to http://stackoverflow.com/a/24225816/535203
// this is not an absolute path let's prepend dockerdesc.json dir
dockerfilePath = path.join(this.dockerdescDir, dockerfilePath);
}
if (!fs.existsSync(dockerfilePath)) {
throw new Error(util.format('The path "%s" defined for image name "%s" does not exists.', dockerfilePath, descriptionTarget));
}
var dockerfileDirPath;
var dockerfilePathStat = fs.statSync(dockerfilePath);
if (dockerfilePathStat.isDirectory()) {
// this path defines actually the directory to the Dockerfile
dockerfileDirPath = dockerfilePath;
dockerfilePath = path.join(dockerfilePath, 'Dockerfile');
} else if (dockerfilePathStat.isFile()) {
// this path correctly points to the Dockerfile, let's take its parent as dir path
dockerfileDirPath = path.dirname(dockerfilePath);
}
// checking whether to build its parent or not
if (finalDescription.buildParent) {
// read the Dockerfile, looking for the "FROM" docker directive & check if this is described in the dockerdesc
var dockerfileContent = fs.readFileSync(dockerfilePath);
var fromRe = /^\s*from\s*(\S*)/i;
var fromMatch = fromRe.exec(dockerfileContent);
if (fromMatch) {
var parentImageName = fromMatch[1];
// looking for that name in descriptions
var parentLookedupDescription = this._lookupDescription('build', parentImageName, ['t', 'tag']);
if (parentLookedupDescription) {
// found the build description, let's build it
console.log(util.format('Found "%s" as a valid parent image to build before building "%s".', parentImageName, descriptionTarget));
var self = this;
this.build(parentImageName, descriptionOptions, commandOptions, dockerOptions, function(exitCode) {
if (!exitCode) {
// Parent image was correctly built, let's build now this image
self._computeOptionsThenBuild(dockerfileDirPath, descriptionTarget, descriptionOptions, commandOptions, dockerOptions, finalDescription, dockerdescName, callback);
}
});
return;
}
}
}
this._computeOptionsThenBuild(dockerfileDirPath, descriptionTarget, descriptionOptions, commandOptions, dockerOptions, finalDescription, dockerdescName, callback);
});
DockerCmdManager.prototype._computeOptionsThenBuild = function (dockerfilePath, descriptionTarget, descriptionOptions, commandOptions, dockerOptions, finalDescription, dockerdescName, callback) {
// now compute all the build options
/** @type {BuildOptions} */
var buildOptions = finalDescription.options || {};
if (finalDescription.buildTagFromBuildName && typeof buildOptions.tag === 'undefined' && typeof buildOptions.t === 'undefined') {
// current tag is not defined, and option buildTagFromBuildName is set, let's add the buildName as a tag
buildOptions.tag = descriptionTarget;
}
// set the argument for docker build to be the path to the Dockerfile
buildOptions._ = [dockerfilePath];
// replace it in the finalDescription because it is this parameter that is used by _defaultCommandHandler
finalDescription.options = buildOptions;
console.log('Building "'+descriptionTarget+'"');
_defaultCommandHandler('build', descriptionTarget, descriptionOptions, commandOptions, dockerOptions, finalDescription, dockerdescName, callback);
};
DockerCmdManager.prototype.run = _dockerCommandBuilder('run', ['name'],
/** @type {DockerCmdManager~CommandHandler} */ function(commandName, descriptionTarget, descriptionOptions, commandOptions, dockerOptions, finalDescription, dockerdescName, callback) {
var runOptions = finalDescription.options || {};
runOptions._ = runOptions._ || [];
if (finalDescription.useRunName && typeof runOptions.name === 'undefined') {
runOptions.name = descriptionTarget;
}
if (runOptions._.length < 1) {
runOptions._.push(finalDescription.image || descriptionTarget);
}
if (finalDescription.command && runOptions._.length === 1) {
runOptions._ = runOptions._.concat(finalDescription.command);
}
// replace it in the finalDescription because it is this parameter that is used by _defaultCommandHandler
finalDescription.options = runOptions;
// TODO handle dependencies between containers, and check if the image exists before running it
_defaultCommandHandler(commandName, descriptionTarget, descriptionOptions, commandOptions, dockerOptions, finalDescription, dockerdescName, callback);
});
DockerCmdManager.prototype._defaultDescriptions = {
run: {
useRunName: true
},
build: {
buildTagFromBuildName: true,
buildParent: true
}
};
/**
* @param {string} commandName - Docker command name
* @param {string} descriptionTarget
* @param {string[]} commandOptionsMappingToAName - command options name which refers to a potential descriptionTarget
* @return {{dockerdescName: string, description: Description}}
* @private
*/
DockerCmdManager.prototype._lookupDescription = function(commandName, descriptionTarget, commandOptionsMappingToAName) {
// First, look directly at the descriptions in the dockerdesc
var dockerdescForThatCommand = this.dockerdesc[commandName] || {};
var description = dockerdescForThatCommand[descriptionTarget];
var dockerdescName = descriptionTarget;
// If not found, try to search for that description with its name field
if (!description && commandOptionsMappingToAName) {
for (dockerdescName in dockerdescForThatCommand) {
/** @type {Description} */
var iDescription = dockerdescForThatCommand[dockerdescName];
var optionNameValue = null;
commandOptionsMappingToAName.forEach(function(optionName) {
optionNameValue = optionNameValue || (iDescription.options && iDescription.options[optionName]);
});
if (optionNameValue === descriptionTarget) {
// we have a match
description = iDescription;
break;
}
}
}
if (!description) {
return null;
} else {
return {
dockerdescName: dockerdescName,
description: description
};
}
};
/**
* @callback DockerCmdManager~CommandHandler
* @param {string} commandName - Docker command name
* @param {string} descriptionTarget
* @param {Object} descriptionOptions - Original description options overrides
* @param {Options} commandOptions - Original command options
* @param {Object} dockerOptions - Original docker options
* @param {Description} finalDescription - Found and enriched description
* @param {string} dockerdescName - Name of the found description
* @param {DockerCmd~callback} callback
*/
/**
* @param {string} commandName - Docker command name
* @param {string[]} commandOptionsMappingToAName - command options name which refers to a potential descriptionTarget
* @param {DockerCmdManager~CommandHandler} commandHandler
* @private
*/
function _dockerCommandBuilder(commandName, commandOptionsMappingToAName, commandHandler) {
/**
*
* @param {string} descriptionTarget
* @param {Object} descriptionOptions - overrides from Description
* @param {Options} commandOptions
* @param {Object} dockerOptions
* @param {DockerCmd~callback} callback
* @private
*/
return function(descriptionTarget, descriptionOptions, commandOptions, dockerOptions, callback) {
// the callback is always the last argument
switch (arguments.length) {
case 2:
callback = descriptionOptions;
descriptionOptions = undefined;
break;
case 3:
callback = commandOptions;
commandOptions = undefined;
break;
case 4:
callback = dockerOptions;
dockerOptions = undefined;
}
// Now will create the final description : beginning from defaults, append the fields from the "default" template, then all the templates from the given
// <code>description</code>, then the fields of the description, and finally the given commandOptions & dockerOptions
var finalDescription = extend(true, {}, this._defaultDescriptions[commandName]); // copy defaults
var templates = (this.dockerdesc.templates || {})[commandName] || {};
function applyTemplate(template) {
if (template) {
extend(true, finalDescription, template);
}
}
// Apply templates beginning with `default`
applyTemplate(templates.default);
// Look directly at the descriptions in the dockerdesc
var lookup = this._lookupDescription(commandName, descriptionTarget, commandOptionsMappingToAName);
var dockerdescName = descriptionTarget;
if (lookup) {
var description = lookup.description;
dockerdescName = lookup.dockerdescName;
var descriptionTemplates = description.templates;
if (descriptionTemplates) {
[].concat(descriptionTemplates).forEach(function (templateName) {
var template = templates[templateName];
if (!template) {
throw new Error(util.format('%s template "%s" is missing', commandName, templateName));
} else {
applyTemplate(template);
}
});
}
// Apply all fields from given description
applyTemplate(description);
} else {
console.warn(util.format('Warn: couldn\'t find the %s description named "%s"', commandName, descriptionTarget));
}
// And finally, the options passed from the command line
applyTemplate(descriptionOptions);
applyTemplate({dockerOptions: dockerOptions, options: commandOptions});
commandHandler.call(this, commandName, descriptionTarget, descriptionOptions, commandOptions, dockerOptions, finalDescription, dockerdescName, callback);
}
}
/**
* @type {DockerCmdManager~CommandHandler}
* @private
*/
function _defaultCommandHandler(commandName, descriptionTarget, descriptionOptions, commandOptions, dockerOptions, finalDescription, dockerdescName, callback) {
new DockerCmd().executeCommand(commandName, finalDescription.options, finalDescription.dockerOptions, callback);
}
/**
* @param {string} commandName - Docker command name
* @param {string[]} [commandOptionsMappingToAName] - command options name which refers to a potential descriptionTarget
*/
function _defaultDockerCommandBuilder(commandName, commandOptionsMappingToAName) {
return _dockerCommandBuilder(commandName, commandOptionsMappingToAName, _defaultCommandHandler);
}
/// copy default commands if not already exists
Object.getOwnPropertyNames(DockerCmd.prototype).forEach(function(dockerCmdPropertyName) {
var dockerCmdProperty = DockerCmd.prototype[dockerCmdPropertyName];
if (typeof dockerCmdProperty === 'function' && !DockerCmdManager.prototype[dockerCmdPropertyName]) {
// let's define the not yet defined function linked to corresponding DockerCmd function
DockerCmdManager.prototype[dockerCmdPropertyName] = _defaultDockerCommandBuilder(dockerCmdPropertyName);
}
});
module.exports = DockerCmdManager;