-
Notifications
You must be signed in to change notification settings - Fork 14
/
fish-pepper.js
executable file
·397 lines (356 loc) · 11.7 KB
/
fish-pepper.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
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
#!/bin/sh
':' //; exec "`command -v node || command -v nodejs`" "$0" "$@"
var fs = require('fs');
var path = require('path');
require('colors');
var _ = require('lodash');
var yaml = require('js-yaml');
var pjson = require('./package.json');
// Own modules:
var templateEngine = require('./fp/template-engine');
var dockerBackend = require('./fp/docker-backend');
var blockLoader = require('./fp/block-loader');
var imageBuilder = require('./fp/image-builder');
var util = require('./fp/util');
// Set to true for extra debugging
var DEBUG = false;
(function () {
var ctx;
try {
ctx = setupContext();
// All supported servers which must be present as a sub-directory
var images = getImages(ctx);
if (!images) {
console.log("No images found.".yellow);
process.exit(0);
}
processImages(ctx, images)
} catch (e) {
console.log(e.message.red);
if (!ctx || ctx.options.verbose) {
console.log(e.stack.grey);
}
}
}).future()();
// ===============================================================================
function processImages(ctx, images) {
// Create build files
createDockerFileDirs(ctx, images);
// If desired create Docker images
if (ctx.commands.build) {
buildImages(ctx, images);
}
}
// == COMMMANDS ===========================================================================
// "make"
function createDockerFileDirs(ctx, images) {
console.log("\n* " + "Creating Docker Builds".cyan);
images.forEach(function (image) {
console.log(" " + image.dir.magenta);
var blocks = blockLoader.loadLocal(ctx.root + "/" + image.dir + "/blocks");
var params = extractParams(image, ctx);
templateEngine.fillTemplates(ctx, image, params, _.extend(blocks,ctx.blocks), createParamIgnoreMap(image));
});
}
// "build"
function buildImages(ctx, images) {
console.log("\n* " + "Building Images".cyan);
var docker = dockerBackend.create(ctx.options);
images.forEach(function(image) {
console.log(" " + image.dir.magenta);
var params = extractParams(image, ctx);
var valuesExpanded = [];
util.foreachParamValue(params,function(values) {
valuesExpanded.push(values);
},createParamIgnoreMap(image));
imageBuilder.build(ctx.root, docker, params.types, valuesExpanded, image, { nocache: ctx.options.nocache, debug: DEBUG });
});
}
// ===================================================================================
function getImages(ctx) {
var imageNames;
if (!ctx.root) {
return undefined;
}
var allImageNames = extractImages(ctx.root);
if (ctx.options.image) {
imageNames = _.filter(allImageNames, function (image) {
return _.contains(ctx.options.image, image);
});
} else if (ctx.options.all) {
imageNames = allImageNames;
} else {
// Determine image name from the current working directory
// which is somewhere below
var currentDir = process.cwd();
var imageMatch =
currentDir.match("^" + ctx.root + "/(.+)(/images/?.*)") ||
currentDir.match("^" + ctx.root + "/(.+)");
if (imageMatch && !imageMatch[1].startsWith("images")) {
// Include multiple images if we are 'in between' the root dir
// and the image directory
imageNames = _.filter(allImageNames,function(name) {
return name.match("^" + imageMatch[1]);
});
} else {
imageNames = allImageNames;
}
}
return _.map(imageNames, function (name) {
var config = getImageConfig(ctx, name);
var repoUser = config.fpConfig('repoUser');
repoUser = repoUser ? repoUser + "/" : "";
var fullImageName = config.fpConfig('name') || repoUser + name.replace(/\//g,'-'); // replace dir seps with '-' for deeper image defs
return {
"dir": name,
"name": fullImageName,
"config": config};
})
}
function extractImages(root) {
var ret = [];
function _findConfigs(dir) {
if (existsConfig(dir,"images")) {
ret.push(dir.replace(new RegExp("^" + root + "/?"),""));
} else {
fs.readdirSync(dir).forEach(function (f) {
var full = dir + "/" + f;
if (fs.statSync(full).isDirectory()) {
_findConfigs(full);
}
});
}
}
_findConfigs(root);
return ret;
}
function getImageConfig(ctx, image) {
var ret =
_.merge(
{},
ctx.config,
readConfig(ctx.root + "/" + image, "images"));
ret.fpConfig = function(key) {
return ret['fish-pepper'] ? ret['fish-pepper'][key] : undefined;
};
return ret;
}
// Return all params in the right order and the individual configuration per param
function extractParams(image, ctx) {
var config = image.config;
var types = config.fpConfig('params').slice(0);
var paramValues = undefined;
var paramConfigs = config.config;
if (ctx) {
var opts = ctx.options ? ctx.options : {all: true};
paramValues = extractFixedParamValues(opts, ctx.root + "/" + image.dir);
paramConfigs = opts.experimental || paramValues ? config.config : removeExperimentalConfigs(config.config);
}
// Filter out configuration which are not selected by the user
var reducedParamConfig = reduceConfig(types,paramConfigs,paramValues);
return {
// Copy objects
types: types,
config: reducedParamConfig
};
}
function reduceConfig(types,config,paramValues) {
var ret = _.extend({},config);
if (paramValues) {
for (var i = 0; i < paramValues.length; i++) {
var type = types[i];
var param = paramValues[i];
if (!config[type][param]) {
throw new Error("No parameter value '" + param + "' defined for type " + type);
}
_.keys(config[type]).forEach(function(key) {
if (key != param && key != "default") {
delete ret[type][key];
}
});
}
}
return ret;
}
// Return a set of parameter values (in the right order) if the user
// select parameters either explicitly or implicitly. Or undefined if no
// parameter restriction applies.
function extractFixedParamValues(opts,topDir) {
// Include all for sure
if (opts.all) {
return undefined;
}
// Specified on command line
if (opts.param) {
return opts.param.split(/\s*,\s*/);
}
// Implicit determined by current working dir
var currentDir = process.cwd();
var paramRest = currentDir.match(new RegExp("^" + topDir + "/?images/([^/]+)/?.*$"));
if (paramRest) {
return paramRest[1].split(/\//);
} else {
return undefined;
}
}
// The param-ignore-map contains the information which prio parameter value combination triggers
// to ignore a certain parameter for building an image
function createParamIgnoreMap(image) {
var config = image.config.config;
var ret = {};
forEachImageFishPepperConfig(config,function(type,paramValue,fpConfig) {
if (fpConfig['ignore-for']) {
ret[type] = ret[type] || {};
ret[type][paramValue] = fpConfig['ignore-for'].slice(0);
}
});
return Object.keys(ret).length > 0 ? ret : undefined;
}
function removeExperimentalConfigs(config) {
var ret = _.extend({},config);
forEachImageFishPepperConfig(config,function(type,paramValue,fpConfig) {
if (fpConfig.experimental) {
delete ret[type][paramValue];
}
});
return ret;
}
function forEachImageFishPepperConfig(config,callback) {
_.keys(config).forEach(function(type) {
_.keys(config[type]).forEach(function(paramValue) {
var typeConfig = config[type][paramValue];
if (typeConfig['fish-pepper']) {
callback(type,paramValue,typeConfig['fish-pepper']);
}
});
});
}
function setupContext() {
var Getopt = require('node-getopt');
var getopt = new Getopt([
['i', 'image=ARG+', 'Images to create (e.g. "tomcat")'],
['p', 'param=ARG', 'Params to use for the build. Should be a comma separate list, starting from top'],
['a', 'all', 'Process all parameters images'],
['c', 'connect', 'Docker URL (default: $DOCKER_HOST)'],
['d', 'dir=ARG', 'Directory holding the image definitions'],
['n', 'nocache', 'Don\'t cache when building images'],
['e', 'experimental', 'Include images which are marked as experimental'],
['v', 'verbose', 'Print out more information'],
['h', 'help', 'display this help']
]);
var opts = getopt.parseSystem();
// Get commands ...
var commands = {};
opts.argv.forEach(function(cmd) {
commands[cmd] = 1;
});
var ctx = {};
ctx.options = opts.options || {};
ctx.commands = commands;
ctx.root = getRootDir(ctx.options.dir);
if (ctx.root) {
ctx.config = readConfig(ctx.root, "fish-pepper");
ctx.blocks = _.extend(
blockLoader.loadRemote(ctx.root,ctx.config.blocks),
blockLoader.loadLocal(ctx.root + "/blocks"));
} else {
ctx.config = {};
ctx.blocks = {};
}
if (ctx.options.help) {
getopt.setHelp(createHelp(ctx));
getopt.showHelp();
return process.exit(0);
}
return ctx;
}
function getRootDir(givenDir) {
if (!givenDir) {
var fpDir = process.cwd();
return findConfig(fpDir, "fish-pepper");
} else {
if (!existsConfig(givenDir, "fish-pepper")) {
throw new Error("Cannot find fish-pepper config" + (givenDir ? " in directory " + givenDir : ""));
}
return givenDir;
}
}
function existsConfig(dir, file) {
return _.some(["json", "yml", "yaml"], function (ext) {
return fs.existsSync(dir + "/" + file + "." + ext)
});
}
function readConfig(dir, file) {
var base = dir + "/" + file;
var ret;
if (fs.existsSync(base + ".json")) {
ret = JSON.parse(fs.readFileSync(base + ".json", "utf8"));
}
_.each(["yml", "yaml"], function (ext) {
if (fs.existsSync(base + "." + ext)) {
ret = yaml.safeLoad(fs.readFileSync(base + "." + ext, "utf8"));
}
});
if (!ret) {
throw new Error("No " + file + ".json, " + file + ".yaml or " + file + ".yml found in " + dir);
}
return ret;
}
function findConfig(dir, file) {
if (dir === "/") {
return undefined;
} else if (existsConfig(dir, file)) {
return dir;
} else {
return findConfig(path.normalize(dir + "/.."), file);
}
}
function createHelp(ctx) {
var help =
"Usage: fish-pepper [OPTION] \<command\>\n" +
"\n" +
"Multidimensional Docker Build Generator\n" +
"\n" +
"[[OPTIONS]]\n" +
"\n" +
"The argument is interpreted as the command to perform. The following commands are supported:\n" +
" make -- Create Docker build files from templates\n" +
" build -- Build Docker images from generated build files. Implies 'make'\n" +
"\n" +
"The configuration is taken from the file \"fish-pepper.json\" or \"fish-pepper.yml\" from the current directory\n" +
"or from the directory provided with the option '-d'. Alternatively the first parent directory\n" +
"containing one of the configuration files is used.\n" +
"\n" +
"Examples:\n" +
"\n" +
" # Find a 'fish-pepper.yml' in this or a parent directory and use\n" +
" # the images found there to create multiple Docker build directories.\n" +
" fish-pepper\n" +
"\n" +
" # Create all image families found in \"example\" directory\n" +
" fish-pepper -d example\n" +
"\n" +
" # Create only the image family \"java\" in \"example\" and build the images, too\n" +
" fish-pepper -d example -i java build\n" +
"\n" +
"Please refer to https://github.com/rhuss/fish-pepper for further documentation.\n" +
"\n";
var images = getImages(ctx);
if (images) {
help +=
"\n" +
"Images:\n\n";
images.forEach(function (image) {
var indent = " ".substring(0,image.dir.length + 5);
var prefix = " " + image.dir + ": ";
util.foreachParamValue(extractParams(image),function(values) {
help += prefix + values.join(", ") + "\n";
prefix = indent;
});
});
} else {
help += "\nNo images found\n";
}
help +="\n-----\nfish-pepper " + pjson.version;
return help;
}