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 |
|
50 |
|
51 | var lockWait = 5000;
|
52 | var lockFilePath = path.resolve(__dirname, 'invoker-status.lock');
|
53 |
|
54 |
|
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 |
|
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 |
|
153 |
|
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 |
|
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 | });
|