1 | var pkg = require('./package.json');
|
2 |
|
3 | var debug = require('debug')(pkg.name);
|
4 | var path = require('path');
|
5 | var fs = require('fs-extra');
|
6 | var async = require('async');
|
7 | var unflatten = require('flat').unflatten;
|
8 | var _ = require('lodash');
|
9 | var lockFile = require('lockfile');
|
10 |
|
11 | var util = require('any2api-util');
|
12 |
|
13 |
|
14 |
|
15 | util.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 |
|
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 |
|
46 | var invokerStatusFile = path.resolve(__dirname, 'invoker-status.json');
|
47 | var invokerStatus = { hosts: {} };
|
48 | var access;
|
49 | var host = 'localhost';
|
50 |
|
51 |
|
52 | var lockWait = 5000;
|
53 | var lockFilePath = path.resolve(__dirname, 'invoker-status.lock');
|
54 |
|
55 |
|
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 |
|
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 |
|
159 |
|
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 |
|
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 | });
|