UNPKG

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