UNPKG

8.1 kBJavaScriptView Raw
1var pkg = require('./package.json');
2
3var debug = require('debug')(pkg.name);
4var path = require('path');
5var fs = require('fs-extra');
6var async = require('async');
7var unflatten = require('flat').unflatten;
8var _ = require('lodash');
9var lockFile = require('lockfile');
10
11var util = require('any2api-util');
12
13
14
15util.readInput(null, function(err, apiSpec, params) {
16 if (err) throw err;
17
18 if (!params.run_list) {
19 console.error('run_list parameter missing');
20
21 process.exit(1);
22 }
23
24 var config = params.invoker_config || {};
25
26 config.access = config.access || 'local';
27 config.min_runs = config.min_runs || 1;
28 config.max_runs = config.max_runs || 3;
29
30 var runParams = params._;
31 delete params._;
32
33 //runParams.run_id = runParams.run_id || uuid.v4();
34 if (!runParams.run_path) {
35 console.error('_.run_path parameter missing');
36
37 process.exit(1);
38 }
39
40 var runOutputDir = path.join(runParams.run_path, 'out');
41 var baseDir = path.join('/', 'tmp', 'any2api-invoker-chef', runParams.executable_name);
42
43 var executable = apiSpec.executables[runParams.executable_name];
44
45 // Invoker status and remote access (local, SSH, ...)
46 var invokerStatusFile = path.resolve(__dirname, 'invoker-status.json');
47 var invokerStatus = { hosts: {} };
48 var access;
49 var host = 'localhost';
50
51 // Lock
52 var lockWait = 5000;
53 var lockFilePath = path.resolve(__dirname, 'invoker-status.lock');
54
55 // Files and directories
56 var origExecDir = path.resolve(apiSpec.apispec_path, '..', executable.path);
57 var execDir = path.join(baseDir, 'executable');
58
59 var chefDir = path.join(baseDir, 'chef_data');
60 var cookbooksDir = path.join(baseDir, 'chef_data', 'cookbooks');
61 var rolesDir = path.join(baseDir, 'chef_data', 'roles');
62
63 var runStatusFile = path.join(baseDir, '.environment_installed');
64 var chefConfigFile = path.join(baseDir, 'chef.rb');
65 var runListFile = path.join(baseDir, 'run_list.json');
66
67 var chefConfig = [
68 'file_cache_path "' + chefDir + '"',
69 'cookbook_path [ "' + cookbooksDir + '" ]',
70 'role_path "' + rolesDir + '"'
71 ].join('\n');
72
73 var commands = {
74 install: [
75 'sudo apt-get -y update',
76 'sudo apt-get -y install curl',
77 'sudo yum -y install curl',
78 'curl -L https://www.opscode.com/chef/install.sh | sudo bash'
79 ].join(' ; '),
80 run: 'sudo chef-solo -c ' + chefConfigFile + ' -j ' + runListFile,
81 };
82
83
84
85 var prepare = function(done) {
86 if (config.access === 'local') {
87 access = require('any2api-access').Local(config);
88 } else if (config.access === 'ssh') {
89 access = require('any2api-access').SSH(config);
90
91 host = config.ssh_host;
92 } else {
93 return done(new Error('access \'' + config.access + '\' not supported'));
94 }
95
96 async.series([
97 async.apply(lockFile.lock, lockFilePath, { wait: lockWait }),
98 function(callback) {
99 if (!fs.existsSync(invokerStatusFile)) return callback();
100
101 fs.readFile(invokerStatusFile, 'utf8', function(err, content) {
102 if (err) return callback(err);
103
104 invokerStatus = JSON.parse(content);
105
106 callback();
107 });
108 },
109 function(callback) {
110 if (invokerStatus.hosts[host]) {
111 var err = new Error('Chef invoker already running on ' + host);
112 host = null;
113
114 return callback(err);
115 }
116
117 invokerStatus.hosts[host] = 'running';
118
119 callback();
120 },
121 async.apply(fs.writeFile, invokerStatusFile, JSON.stringify(invokerStatus), 'utf8'),
122 async.apply(lockFile.unlock, lockFilePath)
123 ], done);
124 };
125
126 var install = function(done) {
127 var cookbookName = executable.cookbook_name;
128
129 var metadataPath = path.resolve(origExecDir, 'metadata.json');
130
131 if (!cookbookName && fs.existsSync(metadataPath)) {
132 var metadata = JSON.parse(fs.readFileSync(metadataPath, 'utf8'));
133
134 if (metadata.name) cookbookName = metadata.name;
135 }
136
137 if (!cookbookName) return done(new Error('cookbook name cannot be determined'));
138
139 var cookbookDir = path.join(cookbooksDir, cookbookName);
140
141 executable.dependencies_subdir = executable.dependencies_subdir || 'cookbook_dependencies';
142
143 async.series([
144 async.apply(access.mkdir, { path: baseDir }),
145 async.apply(access.mkdir, { path: chefDir }),
146 //async.apply(access.mkdir, { path: cookbooksDir }),
147 async.apply(access.mkdir, { path: rolesDir }),
148 async.apply(access.copyDirToRemote, { sourcePath: origExecDir, targetPath: execDir }),
149 async.apply(access.writeFile, { path: chefConfigFile, content: chefConfig }),
150 async.apply(access.mkdir, { path: path.join(execDir, executable.dependencies_subdir, '..') }),
151 async.apply(access.move, { sourcePath: path.join(execDir, executable.dependencies_subdir), targetPath: cookbooksDir }),
152 function(callback) {
153 access.mkdir({ path: cookbookDir }, callback);
154 },
155 function(callback) {
156 access.copy({ sourcePath: execDir, targetPath: cookbookDir }, callback);
157 },
158 //function(callback) {
159 // access.remove({ path: path.join(cookbookDir, executable.dependencies_subdir) }, callback);
160 //},
161 function(callback) {
162 access.exec({ command: commands.install }, function(err, stdout, stderr) {
163 if (stderr) console.error(stderr);
164 if (stdout) console.log(stdout);
165
166 if (err) {
167 err.stderr = stderr;
168 err.stdout = stdout;
169
170 return callback(err);
171 }
172
173 callback();
174 });
175 },
176 async.apply(access.writeFile, { path: runStatusFile, content: 'installed' })
177 ], done);
178 };
179
180 var run = function(done) {
181 var runs = 0;
182 var success = false;
183
184 var attributes = unflatten(params, { delimiter: '/' });
185
186 access.writeFile({ path: runListFile, content: JSON.stringify(attributes) }, function(err) {
187 async.whilst(function() {
188 return !success && runs < config.max_runs;
189 }, function(done) {
190 runs++;
191
192 access.exec({ command: commands.run }, function(err, stdout, stderr) {
193 if (stderr) console.error(stderr);
194 if (stdout) console.log(stdout);
195
196 if ((err && runs < config.max_runs) || runs < config.min_runs) {
197 return done();
198 } else if (err) {
199 err.stderr = stderr;
200 err.stdout = stdout;
201
202 return done(err);
203 } else {
204 success = true;
205
206 console.log('Number of runs:', runs);
207
208 var psOutput;
209
210 // Write outputs
211 async.series([
212 async.apply(fs.mkdirs, runOutputDir),
213 async.apply(fs.writeFile, path.resolve(runOutputDir, 'run_list.json'), JSON.stringify(attributes)),
214 function(callback) {
215 access.exec({ command: 'ps aux' }, function(err, stdout, stderr) {
216 psOutput = stdout;
217
218 callback(err);
219 });
220 },
221 function(callback) {
222 fs.writeFile(path.resolve(runOutputDir, 'ps_aux.txt'), psOutput, callback);
223 }
224 ], done);
225 }
226 });
227 }, done);
228 });
229 };
230
231
232
233 async.series([
234 async.apply(prepare),
235 function(callback) {
236 access.exists({ path: runStatusFile }, function(err, exists) {
237 if (err) callback(err);
238 else if (!exists) install(callback);
239 else callback();
240 });
241 },
242 async.apply(run)
243 ], function(err) {
244 async.series([
245 async.apply(access.remove, { path: baseDir }),
246 async.apply(access.terminate),
247 async.apply(lockFile.lock, lockFilePath, { wait: lockWait }),
248 function(callback) {
249 if (!host) return callback();
250
251 invokerStatus = JSON.parse(fs.readFileSync(invokerStatusFile, 'utf8'));
252
253 delete invokerStatus.hosts[host];
254
255 fs.writeFileSync(invokerStatusFile, JSON.stringify(invokerStatus), 'utf8');
256
257 callback();
258 },
259 async.apply(lockFile.unlock, lockFilePath)
260 ], function(err2) {
261 if (err) throw err;
262
263 if (err2) console.error(err2);
264 });
265 });
266});