cook 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
+