cook 2.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,322 @@
1
+
2
+ require 'openssl';
3
+ require 'securerandom';
4
+ require 'base64';
5
+
6
+ class CookEncryptionError < StandardError; end
7
+ class CookDecryptionError < StandardError; end
8
+
9
+ def areYouSure?(prompt, defaultAnswer = false, askUser = true)
10
+ if askUser then
11
+ defaultPrompt = "yN";
12
+ defaultRegExp = /^[yY]/;
13
+ if defaultAnswer then
14
+ defaultPrompt = "Yn"
15
+ defaultRegExp = /^[nN]/;
16
+ end
17
+ puts "\n*************************************************************\n#{prompt}\n\tAre you sure you want to do this? [#{defaultPrompt}]";
18
+ return !defaultAnswer if STDIN.gets =~ defaultRegExp;
19
+ end
20
+ defaultAnswer;
21
+ end
22
+
23
+ def ensureDirExists(aDirectory)
24
+ unless File.directory?(aDirectory)
25
+ Rake::Task.local_sh("mkdir -p #{aDirectory}");
26
+ end
27
+ end
28
+
29
+ # encryptData2File and decryptFile2Data are both based on:
30
+ # "Encrypting and decrypting some data"
31
+ # at: http://www.ruby-doc.org/stdlib-1.9.3/libdoc/openssl/rdoc/OpenSSL/Cipher.html
32
+ # see also:
33
+ # http://www.ruby-doc.org/stdlib-1.9.3/libdoc/openssl/rdoc/OpenSSL/PKCS5.html
34
+
35
+ def openSslEncryptData2File(plainText, encryptedFilePath)
36
+ # check to ensure the file name conforms to our naming conventions...
37
+ #
38
+ if encryptedFilePath !~ /\.enc$/ then
39
+ raise CookEncryptionError, "The file in which to store the encrypted data (#{encryptedFilePath}) MUST have the file extension '.enc'";
40
+ end
41
+
42
+ salt = SecureRandom.random_bytes(32);
43
+
44
+ encrypter = OpenSSL::Cipher.new('AES-256-CBC');
45
+ encrypter.encrypt;
46
+ encrypter.key = OpenSSL::PKCS5.pbkdf2_hmac_sha1(Conf.get_pass_phrase(true),
47
+ salt, 2000, 256);
48
+ iv = encrypter.random_iv;
49
+
50
+ encrypted = encrypter.update plainText;
51
+ encrypted << encrypter.final
52
+
53
+ File.open(encryptedFilePath, 'w') do | cypherFile |
54
+ cypherFile.puts "--salt32bytes-iv-cypherText--";
55
+ cypherFile.puts Base64.encode64(salt);
56
+ cypherFile.puts Base64.encode64(iv);
57
+ cypherFile.puts Base64.encode64(encrypted);
58
+ end
59
+ end
60
+
61
+ def openSslDecryptFile2Data(encryptedFilePath)
62
+
63
+ # check to ensure the file name conforms to our naming conventions...
64
+ #
65
+ if encryptedFilePath !~ /\.enc$/ then
66
+ raise CookDecryptionError, "The encrypted file (#{encryptedFilePath}) MUST have the file extension '.enc' ";
67
+ end
68
+
69
+ # check to make sure the file exists...
70
+ #
71
+ if !File.exists?(encryptedFilePath) then
72
+ raise CookDecryptionError, "The encrypted file (#{encryptedFilePath}) does not exist";
73
+ end
74
+
75
+ cypherFile = File.open(encryptedFilePath);
76
+ headerLine = cypherFile.gets;
77
+ if headerLine !~ /^--salt32bytes-iv-cypherText--$/ then
78
+ raise CookDecryptionError, "Attempting to decrypt a file which was not encrypted using cookUtils openSslEncryptData2File (header line: [#{headerLine}]).";
79
+ end
80
+ salt = Base64.decode64(cypherFile.gets);
81
+ iv = Base64.decode64(cypherFile.gets);
82
+ cypherText = Base64.decode64(cypherFile.read);
83
+
84
+ decrypter = OpenSSL::Cipher.new('AES-256-CBC');
85
+ decrypter.decrypt;
86
+ decrypter.key = OpenSSL::PKCS5.pbkdf2_hmac_sha1(Conf.get_pass_phrase,
87
+ salt, 2000, 256);
88
+ decrypter.iv = iv;
89
+
90
+ decrypted = decrypter.update cypherText;
91
+ decrypted << decrypter.final
92
+
93
+ return decrypted;
94
+ end
95
+
96
+ # gpgEncryptData2File and gpgDecryptFile2Data are both based on the
97
+ # ruby gpgme bindings documentation. (See:
98
+
99
+ def gpgEncryptData2File(plainText, encryptedFilePath)
100
+
101
+ if !Conf.gpg.has_key?(:recipientUID) then
102
+ raise CookEncryptionError, "No GnuPG recipient UID specified in configuration";
103
+ end
104
+
105
+ if !ENV.has_key?("GPG_AGENT_INFO") then
106
+ raise CookEncryptionError, "Could not find GnuPG agent (GPG_AGENT_INFO environment variable), is it running?";
107
+ end
108
+
109
+ begin
110
+ require 'gpgme';
111
+ rescue LoadError
112
+ raise CookEncryptionError, "The required gpgme (GnuPG Made Easy) ruby gem could not be loaded";
113
+ end
114
+
115
+ crypto = GPGME::Crypto.new
116
+ encrypted = crypto.encrypt(plainText, { :recipients => Conf.gpg.recipientUID, :always_trust => true } ).read;
117
+
118
+ File.open(encryptedFilePath, 'w') do | cypherFile |
119
+ cypherFile.puts "--gpgRecipientUID-cypherText--";
120
+ cypherFile.puts Conf.gpg.recipientUID;
121
+ cypherFile.puts Base64.encode64(encrypted);
122
+ end
123
+ end
124
+
125
+ def gpgDecryptFile2Data(encryptedFilePath)
126
+
127
+ if !Conf.gpg.has_key?(:recipientUID) then
128
+ raise CookDecryptionError, "No GnuPG recipient UID (Conf.gpg.recipientUID) specified in configuration";
129
+ end
130
+
131
+ if !ENV.has_key?("GPG_AGENT_INFO") then
132
+ raise CookDecryptionError, "Could not find GnuPG agent (GPG_AGENT_INFO environment variable), is it running?";
133
+ end
134
+
135
+ begin
136
+ require 'gpgme';
137
+ rescue LoadError
138
+ raise CookDecryptionError, "The required gpgme (GnuPG Made Easy) ruby gem could not be loaded";
139
+ end
140
+
141
+ # check to ensure the file name conforms to our naming conventions...
142
+ #
143
+ if encryptedFilePath !~ /\.enc$/ then
144
+ raise CookDecryptionError, "The encrypted file (#{encryptedFilePath}) MUST have the file extension '.enc'";
145
+ end
146
+
147
+ # check to make sure the file exists...
148
+ #
149
+ if !File.exists?(encryptedFilePath) then
150
+ raise CookDecryptionError, "The encrypted file (#{encryptedFilePath}) does not exist";
151
+ end
152
+
153
+ cypherFile = File.open(encryptedFilePath);
154
+ header = cypherFile.gets;
155
+ if header !~ /^--gpgRecipientUID-cypherText--$/ then
156
+ raise CookDecryptionError, "Attempting to decrypt a file which was not encrypted using cookUtils gpgEncryptData2File (header: [#{header}]).";
157
+ end
158
+ recipientUID = cypherFile.gets.chomp;
159
+ if recipientUID != Conf.gpg.recipientUID then
160
+ require 'pp';
161
+ pp recipientUID;
162
+ pp Conf.gpg.recipientUID;
163
+ raise CookDecryptionError, "Configured recipient UID (#{Conf.gpg.recipientUID}) does not match the recipient UID (#{recipientUID}) used to encrypt the file: [#{encryptedFilePath}]";
164
+ end
165
+ cypherText = Base64.decode64(cypherFile.read);
166
+
167
+ crypto = GPGME::Crypto.new
168
+ decrypted = crypto.decrypt(cypherText, :recipients => Conf.gpg.recipientUID).read;
169
+
170
+ return decrypted;
171
+ end
172
+
173
+ #############################################################################
174
+ # Walk resources, scripts and binaries
175
+
176
+ def walkResourceScriptTemplates(templateType, serverType, targetServer, targetMachine, &aBlock)
177
+ mesg "";
178
+ targetResourceScriptsDir = 'upload/' + targetMachine.to_s + '/' + targetServer.to_s + '/' + templateType.to_s + '/resourceScripts';
179
+ targetDir = 'upload/' + targetMachine.to_s + '/' + targetServer.to_s + '/' + templateType.to_s + '/resources';
180
+ ensureDirExists(targetResourceScriptsDir);
181
+ ensureDirExists(targetDir);
182
+ resourceScripts = Hash.new();
183
+ each_resource(templateType + '/' + serverType.to_s + '/resourceScriptTemplates') do | aResourceScriptPath |
184
+ mesg "walking resource scripts in [#{aResourceScriptPath}]";
185
+ Dir.glob(aResourceScriptPath + '/*.erb').sort.each do | aResourceScriptERB |
186
+ baseName = File.basename(aResourceScriptERB, '.erb');
187
+ resourceScripts[baseName] = aResourceScriptERB;
188
+ end
189
+ end
190
+ resourceScripts.keys.sort.each do | aResourceScriptBaseName |
191
+ aResourceScriptName = targetResourceScriptsDir + '/' + aResourceScriptBaseName;
192
+ aResourceName = targetDir + '/' + aResourceScriptBaseName;
193
+ aBlock.call(resourceScripts[aResourceScriptBaseName], aResourceScriptName, aResourceName);
194
+ end
195
+ end
196
+
197
+ def walkScriptTemplates(templateType, serverType, targetServer, targetMachine, &aBlock)
198
+ mesg "";
199
+ targetDir = 'upload/' + targetMachine.to_s + '/' + targetServer.to_s + '/' + templateType.to_s + '/scripts';
200
+ ensureDirExists(targetDir);
201
+ scripts = Hash.new();
202
+ each_resource(templateType.to_s + '/' + serverType.to_s + '/scriptTemplates') do | aResourcePath |
203
+ mesg "walking scripts in [#{aResourcePath}]";
204
+ Dir.glob(aResourcePath + '/*.erb').sort.each do | aScriptERB |
205
+ baseName = File.basename(aScriptERB, '.erb');
206
+ scripts[serverType+baseName] = aScriptERB;
207
+ end
208
+ end
209
+ scripts.keys.sort.each do | aScriptBaseName |
210
+ aScriptName = targetDir + '/' + aScriptBaseName;
211
+ aBlock.call(scripts[aScriptBaseName], aScriptName);
212
+ end
213
+ end
214
+
215
+ def walkResourceTemplates(templateType, serverType, targetServer, targetMachine, &aBlock)
216
+ mesg "";
217
+ targetDir = 'upload/' + targetMachine.to_s + '/' + targetServer.to_s + '/' + templateType.to_s + '/resources';
218
+ ensureDirExists(targetDir);
219
+ resources = Hash.new();
220
+ each_resource(templateType.to_s + '/' + serverType.to_s + '/resourceTemplates') do | aResourcePath |
221
+ mesg "walking resources in [#{aResourcePath}]";
222
+ Dir.glob(aResourcePath + '/*.erb').sort.each do | aResourceERB |
223
+ baseName = File.basename(aResourceERB, '.erb');
224
+ resources[baseName] = aResourceERB;
225
+ end
226
+ end
227
+ resources.keys.sort.each do | aResourceBaseName |
228
+ aResourceName = targetDir + '/' + aResourceBaseName;
229
+ aBlock.call(resources[aResourceBaseName], aResourceName);
230
+ end
231
+ end
232
+
233
+ def walkResourceBinaries(templateType, serverType, targetServer, targetMachine, &aBlock)
234
+ mesg "";
235
+ targetDir = 'upload/' + targetMachine.to_s + '/' + targetServer.to_s + '/' + templateType.to_s + '/resources';
236
+ ensureDirExists(targetDir);
237
+ resources = Hash.new();
238
+ each_resource(templateType.to_s + '/' + serverType.to_s + '/resourceTemplates') do | aResourcePath |
239
+ mesg "walking binaries in [#{aResourcePath}]";
240
+ Dir.glob(aResourcePath + '/*.bin').sort.each do | aResourceBIN |
241
+ baseName = File.basename(aResourceBIN, '.bin');
242
+ resources[baseName] = aResourceBIN;
243
+ end
244
+ end
245
+ resources.keys.sort.each do | aResourceBaseName |
246
+ aResourceName = targetDir + '/' + aResourceBaseName;
247
+ aBlock.call(resources[aResourceBaseName], aResourceName);
248
+ end
249
+ end
250
+
251
+ #############################################################################
252
+ # Apply templates and/or copy resources
253
+
254
+ def applyTemplate(templateFileName, resultFileName, mode = nil, owner = nil, group = nil)
255
+ require 'erubis'
256
+ mesg "Applying erubis template: [#{templateFileName}]\nto produce: [#{resultFileName}]";
257
+ eruby = Erubis::Eruby.new(IO.read(templateFileName));
258
+ File.open(resultFileName, 'w') do | io |
259
+ io.write(eruby.result(binding()));
260
+ end
261
+ end
262
+
263
+ def useOverrideOrApplyTemplate(templatePath, resultPath)
264
+ overridePath = resultPath.sub(/^upload/, 'override');
265
+ if File.exists?(overridePath) then
266
+ local_sh("cp #{overridePath} #{resultPath}");
267
+ else
268
+ applyTemplate(templatePath, resultPath);
269
+ end
270
+ end
271
+
272
+ def copyOverrideOrBinary(binaryPath, resultPath)
273
+ overridePath = resultPath.sub(/^upload/, 'override');
274
+ if File.exists?(overridePath) then
275
+ local_sh("cp #{overridePath} #{resultPath}");
276
+ else
277
+ local_sh("cp #{binaryPath} #{resultPath}");
278
+ end
279
+ end
280
+
281
+ def fileMatches(fileName, aRegExp)
282
+ fileContents = File.open(fileName, "r").read;
283
+ fileContents =~ aRegExp;
284
+ end
285
+
286
+ def replaceLines(fileName, replaceLinesHash)
287
+ tmpFileName = fileName + '.tmp';
288
+ inFile = File.open(fileName, 'r');
289
+ outFile = File.open(tmpFileName, 'w');
290
+ inFile.each_line() do | aLine |
291
+ replaceLinesHash.keys.each() do | aRegExp |
292
+ if aRegExp.match(aLine) then
293
+ aLine = replaceLinesHash[aRegExp];
294
+ end
295
+ end
296
+ outFile.puts aLine;
297
+ end
298
+ outFile.close();
299
+ inFile.close();
300
+ File.rename(tmpFileName, fileName);
301
+ end
302
+
303
+ def walkThroughDirectoriesDoing(curDir, &aBlock)
304
+ Dir.entries(curDir).sort.each do | aFile |
305
+ next if aFile =~ /^\.$/;
306
+ next if aFile =~ /^\.\.$/;
307
+ fullPath = curDir + '/' + aFile;
308
+ aBlock.call(fullPath);
309
+ if File.directory?(fullPath) then
310
+ walkThroughDirectoriesDoing(fullPath, &aBlock);
311
+ end
312
+ end
313
+ end
314
+
315
+ def toSymbolHash(aHash)
316
+ aHash.keys.each do | anOldKey |
317
+ next if anOldKey.is_a?(Symbol);
318
+ if anOldKey.is_a?(String) then
319
+ aHash[anOldKey.to_sym] = aHash.delete(anOldKey);
320
+ end
321
+ end
322
+ end
@@ -0,0 +1,199 @@
1
+ require 'logger';
2
+ require 'pp'
3
+ require 'stringio'
4
+ require 'greenletters';
5
+
6
+ module Greenletters
7
+ #
8
+ # We need to add the Greenletters::Process#result method to return the
9
+ # contents of the output_buffer...
10
+ #
11
+ class Process
12
+ def result
13
+ @output_buffer.string
14
+ end
15
+
16
+ def recentResult
17
+ @history.buffer
18
+ end
19
+ end
20
+ #
21
+ # Monkey patch GreenLetters so that we can optionally ignore the
22
+ # exist status using the :any pattern.
23
+ #
24
+ class ExitTrigger < Trigger
25
+ def call(process)
26
+ if process.status then
27
+ if pattern == :any then
28
+ @block.call(process, process.status)
29
+ true
30
+ elsif pattern === process.status.exitstatus
31
+ @block.call(process, process.status)
32
+ true
33
+ else
34
+ false
35
+ end
36
+ else
37
+ false
38
+ end
39
+ end
40
+ end
41
+ end
42
+
43
+ module Rake
44
+ ##
45
+ # Base error class for all Vlad errors.
46
+ class Error < RuntimeError; end
47
+
48
+ ##
49
+ # Raised when a remote command fails.
50
+ class CommandFailedError < Error
51
+ attr_reader :status
52
+ def initialize( status )
53
+ @status = status
54
+ end
55
+ end
56
+
57
+ class Application
58
+
59
+ def self.openLogger(logFileBaseName)
60
+ timeStamp = Time.now.utc.strftime("%Y-%m-%d-%H-%M-%S-%L.log");
61
+ @logFile = File.open(logFileBaseName+'-'+timeStamp, 'w');
62
+ @logger = Logger.new(@logFile);
63
+ @logger.datetime_format = "%H-%M-%S-%L";
64
+ @logToStderr = true;
65
+ end
66
+
67
+ def self.catchStdout()
68
+ @oldStdout = $stdout;
69
+ $stdout = @logFile;
70
+ end
71
+
72
+ def self.catchStderr()
73
+ @logToStderr = false;
74
+ @oldStderr = $stderr;
75
+ $stderr = @logFile;
76
+ end
77
+
78
+ def self.setLogToStderr(logToStderr)
79
+ @logToStderr = logToStderr;
80
+ end
81
+
82
+ def self.flushLog()
83
+ @logFile.flush();
84
+ end
85
+
86
+ def self.logger()
87
+ return @logger;
88
+ end
89
+ def self.logFile()
90
+ return @logFile;
91
+ end
92
+ def self.logData(someData)
93
+ @logFile.write(someData) if @logFile;
94
+ end
95
+ def self.set_logger(aLogger, aLogFile=nil)
96
+ @logFile = aLogFile;
97
+ oldLogger = @logger;
98
+ @logger = aLogger;
99
+ return oldLogger;
100
+ end
101
+ def self.log(*args)
102
+ @logger.log(*args) if @logger;
103
+ end
104
+ def self.fatal(*args)
105
+ @logger.fatal(*args) if @logger;
106
+ end
107
+ def self.error(*args)
108
+ @logger.error(*args) if @logger;
109
+ end
110
+ def self.warn(*args)
111
+ @logger.warn(*args) if @logger;
112
+ end
113
+ def self.info(*args)
114
+ @logger.info(*args) if @logger;
115
+ end
116
+ def self.debug(*args)
117
+ @logger.debug(*args) if @logger;
118
+ end
119
+ def self.mesg(*args)
120
+ @logger.info(*args) if @logger;
121
+ $stderr.puts(*args) if @logToStderr;
122
+ end
123
+
124
+ # taken from http://www.ruby-forum.com/topic/43725 (2012/01/21)
125
+ def self.mesg_pp(*args)
126
+ old_out = $stdout
127
+ begin
128
+ s=StringIO.new
129
+ $stdout=s
130
+ pp(*args)
131
+ ensure
132
+ $stdout=old_out
133
+ end
134
+ @logger.info(s.string);
135
+ $stderr.puts(s.string) if @logToStderr;
136
+ end
137
+
138
+ end
139
+
140
+ module DSL
141
+
142
+ def mesg(*args)
143
+ Rake::Application.mesg(*args);
144
+ end
145
+
146
+ def mesg_pp(*args)
147
+ Rake::Application.mesg_pp(*args);
148
+ end
149
+
150
+ end
151
+
152
+ class Task
153
+
154
+ # Same as invoke, but explicitly pass a call chain to detect
155
+ # circular dependencies.
156
+ def invoke_with_call_chain(task_args, invocation_chain) # :nodoc:
157
+ new_chain = InvocationChain.append(self, invocation_chain)
158
+ @lock.synchronize do
159
+ if application.options.trace
160
+ Rake::Application.mesg "** Invoke #{name} #{format_trace_flags}"
161
+ end
162
+ return if @already_invoked
163
+ @already_invoked = true
164
+ invoke_prerequisites(task_args, new_chain)
165
+ execute(task_args) if needed?
166
+ end
167
+ rescue Exception => ex
168
+ add_chain_to(ex, new_chain)
169
+ raise ex
170
+ end
171
+
172
+ # Execute the actions associated with this task.
173
+ def execute(args=nil)
174
+ args ||= EMPTY_TASK_ARGS
175
+ if application.options.dryrun
176
+ Rake::Application.mesg "** Execute (dry run) #{name}"
177
+ return
178
+ end
179
+ if application.options.trace
180
+ Rake::Application.mesg "** Execute #{name}"
181
+ end
182
+ application.enhance_with_matching_rule(name) if @actions.empty?
183
+ @actions.each do |act|
184
+ case act.arity
185
+ when 1
186
+ act.call(self)
187
+ else
188
+ act.call(self, args)
189
+ end
190
+ end
191
+ end
192
+
193
+ end
194
+
195
+ end
196
+
197
+ self.extend Rake::DSL
198
+
199
+