cook 2.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gemtest ADDED
File without changes
data/.gitignore ADDED
@@ -0,0 +1,21 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ coverage
6
+ InstalledFiles
7
+ lib/bundler/man
8
+ pkg
9
+ rdoc
10
+ spec/reports
11
+ test/tmp
12
+ test/version_tmp
13
+ tmp
14
+
15
+ # YARD artifacts
16
+ .yardoc
17
+ _yardoc
18
+ doc/
19
+
20
+ # Not required when using Hoe ruby gem
21
+ Manifest.txt
data/History.txt ADDED
@@ -0,0 +1,19 @@
1
+ === 2.0.1 / 2014-01-15 (external GitHub)
2
+
3
+ * 2.0.1 minor testing
4
+
5
+ * first working release (tested on internal cookbooks)
6
+
7
+ === 2.0.0 / 2014-01-15 (external GitHub)
8
+
9
+ * 2 major enhancement
10
+
11
+ * initial preparation for release as a ruby gem
12
+
13
+ === 1.0.0 / 2012-02-01 (internal)
14
+
15
+ * 1 major enhancement
16
+
17
+ * Birthday!
18
+
19
+
data/README.txt ADDED
@@ -0,0 +1,89 @@
1
+ = cook
2
+
3
+ home :: https://github.com/stephengaito/rGems-cook
4
+
5
+ == DESCRIPTION:
6
+
7
+ cook is a rake extension with:
8
+
9
+ 1. configuration file,
10
+ 1. the ability to retrieve passwords from encrypted configuration files,
11
+ 1. the ability to create files using Erubis templates,
12
+ 1. the ablity to interact with both local and remote shells.
13
+
14
+ Its main file is a traditional rake Rakefile, which has recipe
15
+ commands. Each recipe is a collection of rake task files and
16
+ associated YAML configuration files, allowing tasks to make use of
17
+ extensive configuration information. The configuration is built up
18
+ from the various fragments in the conf files assocaited with each set
19
+ of rake tasks.
20
+
21
+ == FEATURES:
22
+
23
+ * Based on standard ruby rake
24
+
25
+ * Adds the ability to build up configuration from a number of separate
26
+ configuration files overlaying the more specific configuration
27
+ parameters over the more general parameters.
28
+
29
+ * Allows collections of related rake tasks and configuration files in
30
+ the form of recipe directories.
31
+
32
+ * Uses Erubis to create files from templates.
33
+
34
+ * Uses Greenletters to enable interaction with both local and remote
35
+ shells.
36
+
37
+ * Uses GPGme to obtain passwords from GPG encrypted configuration
38
+ files.
39
+
40
+ == SYNOPSIS:
41
+
42
+ cook --config dev update
43
+
44
+ or
45
+
46
+ cook -c prod update
47
+
48
+ to cook the update task with the additional dev or prod
49
+ configurations.
50
+
51
+ == REQUIREMENTS:
52
+
53
+ * rake
54
+ * construct
55
+ * erubis
56
+ * greenletters
57
+ * gpgme
58
+ * highline (for requesting GPGme passphrase from command line)
59
+
60
+ == INSTALL:
61
+
62
+ To install the cook ruby gem
63
+
64
+ $ gem install cook
65
+
66
+ == LICENSE:
67
+
68
+ (The MIT License)
69
+
70
+ Copyright (c) 2014 Stephen Gaito
71
+
72
+ Permission is hereby granted, free of charge, to any person obtaining a
73
+ copy of this software and associated documentation files (the
74
+ 'Software'), to deal in the Software without restriction, including
75
+ without limitation the rights to use, copy, modify, merge, publish,
76
+ distribute, sublicense, and/or sell copies of the Software, and to
77
+ permit persons to whom the Software is furnished to do so, subject to
78
+ the following conditions:
79
+
80
+ The above copyright notice and this permission notice shall be included
81
+ in all copies or substantial portions of the Software.
82
+
83
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS
84
+ OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
85
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
86
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
87
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
88
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
89
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,45 @@
1
+ # -*- ruby -*-
2
+
3
+ # To release this ruby gem type:
4
+ # rake release VERSION=x.y.z
5
+ # where x.y.z is the appropriate version number of this gem.
6
+
7
+ require 'rubygems'
8
+ require 'hoe'
9
+
10
+ # Hoe.plugin :compiler
11
+ # Hoe.plugin :gem_prelude_sucks
12
+ # Hoe.plugin :inline
13
+ # Hoe.plugin :minitest
14
+ # Hoe.plugin :racc
15
+ # Hoe.plugin :rubyforge
16
+
17
+ # generate the Manifest.txt file (before we invoke Hoe.spec)
18
+ manifest = FileList[
19
+ '.gitignore',
20
+ 'History.*',
21
+ 'README.*',
22
+ 'Rakefile',
23
+ 'bin/**/*',
24
+ 'lib/**/*.rb',
25
+ 'test/**/*.rb',
26
+ ];
27
+ File.open('Manifest.txt', 'w') do | manifestFile |
28
+ manifestFile.puts("Manifest.txt");
29
+ manifestFile.write(manifest.to_a.join("\n"));
30
+ end
31
+
32
+ # For dependency documentation see:
33
+ # http://guides.rubygems.org/patterns/
34
+
35
+ Hoe.spec 'cook' do
36
+ developer('Stephen Gaito', 'stephen@perceptisys.co.uk')
37
+ dependency('rake', '~> 0.9.2');
38
+ dependency('construct', '~> 0.1.7');
39
+ dependency('erubis', '~> 2.7');
40
+ dependency('greenletters', '~> 0.2');
41
+ dependency('gpgme', '~> 2.0');
42
+ dependency('highline', '~> 1.6.1');
43
+ end
44
+
45
+ # vim: syntax=ruby
data/bin/cook ADDED
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ #--
4
+ #
5
+ # Copyright (c) 2014 Stephen Gaito
6
+ #
7
+ # Permission is hereby granted, free of charge, to any person obtaining
8
+ # a copy of this software and associated documentation files (the
9
+ # "Software"), to deal in the Software without restriction, including
10
+ # without limitation the rights to use, copy, modify, merge, publish,
11
+ # distribute, sublicense, and/or sell copies of the Software, and to
12
+ # permit persons to whom the Software is furnished to do so, subject to
13
+ # the following conditions:
14
+ #
15
+ # The above copyright notice and this permission notice shall be
16
+ # included in all copies or substantial portions of the Software.
17
+ #
18
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
22
+ # BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
23
+ # ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
24
+ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25
+ # SOFTWARE.
26
+ #
27
+ # ++
28
+
29
+ begin
30
+ require 'rubygems';
31
+ gem 'rake';
32
+ rescue LoadError
33
+ end
34
+
35
+ require 'rake'
36
+ require 'rake/extensions.rb'; # provides mesg used by others
37
+ require 'rake/config.rb';
38
+ require 'rake/cookUtils.rb';
39
+
40
+ # simulate the --trace option
41
+ Rake.application.options.trace = true;
42
+ Rake.verbose(true);
43
+
44
+ Rake.application.run
data/bin/cookDecrypt ADDED
@@ -0,0 +1,75 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ #--
4
+ #
5
+ # Copyright (c) 2014 Stephen Gaito
6
+ #
7
+ # Permission is hereby granted, free of charge, to any person obtaining
8
+ # a copy of this software and associated documentation files (the
9
+ # "Software"), to deal in the Software without restriction, including
10
+ # without limitation the rights to use, copy, modify, merge, publish,
11
+ # distribute, sublicense, and/or sell copies of the Software, and to
12
+ # permit persons to whom the Software is furnished to do so, subject to
13
+ # the following conditions:
14
+ #
15
+ # The above copyright notice and this permission notice shall be
16
+ # included in all copies or substantial portions of the Software.
17
+ #
18
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
22
+ # BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
23
+ # ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
24
+ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25
+ # SOFTWARE.
26
+ #
27
+ # ++
28
+
29
+
30
+ # based on ideas taken from:
31
+ # Encrypting and decrypting some data
32
+ # at: http://www.ruby-doc.org/stdlib-1.9.3/libdoc/openssl/rdoc/OpenSSL/Cipher.html
33
+ # see also:
34
+ # http://www.ruby-doc.org/stdlib-1.9.3/libdoc/openssl/rdoc/OpenSSL/PKCS5.html
35
+
36
+ require 'rake'
37
+ require 'rake/extensions.rb'; # provides mesg used by others
38
+ require 'rake/config.rb';
39
+ require 'rake/cookUtils';
40
+
41
+ if ARGV.length < 1 then
42
+ puts "usage: cookDecrypt <<cypher text file to decrypt>>.enc";
43
+ exit(-1);
44
+ end
45
+
46
+ # check to ensure the file name conforms to our naming conventions...
47
+ #
48
+ if ARGV[0] !~ /\.dec$/ then
49
+ puts "The file to be decrypted (#{ARGV[0]}) MUST have the file extension '.enc'";
50
+ exit(-1);
51
+ end
52
+
53
+ # check to make sure the file exists...
54
+ #
55
+ if !File.exists?(ARGV[0]) then
56
+ puts "The file to be decrypted (#{ARGV[0]}) does not exist";
57
+ exit(-1);
58
+ end
59
+
60
+ # Ensure the configuration has been loaded...
61
+ Rake.application.raw_load_rakefile;
62
+
63
+ decrypted = "";
64
+ begin
65
+ decrypted = gpgDecryptFile2Data(ARGV[0]);
66
+ rescue CookDecryptionError => e
67
+ puts "Failed decryption using GnuPG";
68
+ puts "(#{e.message})...";
69
+ puts "\ntrying OpenSSL...";
70
+ decrypted = openSslDecryptFile2Data(ARGV[0]);
71
+ end
72
+
73
+ File.open(ARGV[0].sub(/\.enc/,'.dec'),'w') do | decryptedFile |
74
+ decryptedFile.puts decrypted;
75
+ end
data/bin/cookEncrypt ADDED
@@ -0,0 +1,95 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ #--
4
+ #
5
+ # Copyright (c) 2014 Stephen Gaito
6
+ #
7
+ # Permission is hereby granted, free of charge, to any person obtaining
8
+ # a copy of this software and associated documentation files (the
9
+ # "Software"), to deal in the Software without restriction, including
10
+ # without limitation the rights to use, copy, modify, merge, publish,
11
+ # distribute, sublicense, and/or sell copies of the Software, and to
12
+ # permit persons to whom the Software is furnished to do so, subject to
13
+ # the following conditions:
14
+ #
15
+ # The above copyright notice and this permission notice shall be
16
+ # included in all copies or substantial portions of the Software.
17
+ #
18
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
22
+ # BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
23
+ # ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
24
+ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25
+ # SOFTWARE.
26
+ #
27
+ # ++
28
+
29
+ # based on ideas taken from:
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
+ require 'rake'
36
+ require 'rake/extensions.rb'; # provides mesg used by others
37
+ require 'rake/config.rb';
38
+ require 'rake/cookUtils';
39
+
40
+ if ARGV.length < 1 then
41
+ puts "usage: cookEncrypt <<plain text file to encrypt>>.dec";
42
+ exit(-1);
43
+ end
44
+
45
+ # check to ensure the file name conforms to our naming conventions...
46
+ #
47
+ if ARGV[0] !~ /\.dec$/ then
48
+ puts "The file to be encrypted (#{ARGV[0]}) MUST have the file extension '.dec'";
49
+ exit(-1);
50
+ end
51
+
52
+ # check to make sure the file exists...
53
+ #
54
+ if !File.exists?(ARGV[0]) then
55
+ puts "The file to be encrypted (#{ARGV[0]}) does not exist";
56
+ exit(-1);
57
+ end
58
+
59
+ # get the plain text...
60
+ #
61
+ plainText = File.open(ARGV[0]).read;
62
+
63
+ # Ensure the configuration has been loaded...
64
+ Rake.application.raw_load_rakefile;
65
+
66
+ # encrypt the file...
67
+ #
68
+ begin
69
+ gpgEncryptData2File(plainText, ARGV[0].sub(/\.dec/,'.enc'));
70
+ rescue CookEncryptionError => e
71
+ puts "Failed to encrypt using GnuPG";
72
+ puts "(#{e.message})...";
73
+ puts "\ntrying OpenSSL...";
74
+ openSslEncryptData2File(plainText, ARGV[0].sub(/\.dec/,'.enc'));
75
+ end
76
+
77
+ ##
78
+ # Returns +true+ if the +program+ executable is found in the user's path.
79
+ # Taken from: http://stackoverflow.com/questions/2108727/which-in-ruby-checking-if-program-exists-in-path-from-ruby/3380168#3380168
80
+ #
81
+ def has_program?(program)
82
+ ENV['PATH'].split(File::PATH_SEPARATOR).any? do |directory|
83
+ File.executable?(File.join(directory, program.to_s))
84
+ end
85
+ end
86
+
87
+ if File.exists?(ARGV[0]) then
88
+ if has_program?('srm') then
89
+ puts "srm (secure remove) may require more entropy... so please move the mouse";
90
+ system("srm -v #{ARGV[0]}");
91
+ else
92
+ puts "if you had 'srm' installed then we could more securely remove the decrypted passwords in the file #{ARGV[0]}";
93
+ system ("rm #{ARGV[0]");
94
+ end
95
+ end
data/lib/cook.rb ADDED
@@ -0,0 +1,3 @@
1
+ class Cook
2
+ VERSION = '2.0.1'
3
+ end
@@ -0,0 +1,642 @@
1
+
2
+ begin
3
+ require 'rubygems';
4
+ gem 'construct';
5
+ rescue LoadError
6
+ end
7
+
8
+ require 'construct';
9
+ require 'yaml';
10
+ require 'fileutils';
11
+ #require 'regexp';
12
+
13
+ class Construct
14
+
15
+ def empty?()
16
+ @data.empty?();
17
+ end
18
+
19
+ def has_key?(key)
20
+ @data.has_key?(key);
21
+ end
22
+
23
+ def each_pair(*args, &block)
24
+ @data.each_pair(*args, &block)
25
+ end
26
+
27
+ def each_key(*args, &block)
28
+ @data.each_key(*args, &block)
29
+ end
30
+
31
+ alias_method :orig_key_value_assignment, :[]= ;
32
+ def []=(key, value)
33
+ value = Hash.new() if value.nil?;
34
+
35
+ if @data.include?(key) && @data[key].is_a?(Construct) && (value.is_a?(Hash) || value.is_a?(Construct)) then
36
+ value = @data[key].merge(value);
37
+ end
38
+ orig_key_value_assignment(key, value);
39
+ end
40
+
41
+ def merge(valueToMerge)
42
+ if valueToMerge.is_a?(Hash) || valueToMerge.is_a?(Construct) then
43
+ valueToMerge.each do | key, value |
44
+ key = key.to_sym;
45
+ if value.is_a?(Hash) || value.is_a?(Construct) then
46
+ self[key] = Construct.new() unless self.has_key?(key);
47
+ if !self[key].is_a?(Construct) then
48
+ raise ArgumentError, "attempting to merge a Hash/Construct into an existing non Hash/Construct key [#{key}]";
49
+ end
50
+ self[key].merge(value);
51
+ elsif value.is_a?(Array) then
52
+ self[key] = Array.new() unless self.has_key?(key);
53
+ if !self[key].is_a?(Array) then
54
+ raise ArgumentError, "attempting to merge an Array into an existing non Array key [#{key}]";
55
+ end
56
+ value.each do | item |
57
+ self[key].push(item);
58
+ end
59
+ else
60
+ self[key] = value;
61
+ end
62
+ end
63
+ end
64
+ if valueToMerge.is_a?(Construct) then
65
+ @schema.merge(valueToMerge.schema);
66
+ end
67
+ return self;
68
+ end
69
+
70
+ def to_stringHash()
71
+ result = Hash.new();
72
+ data.each_pair do | aKey, aValue |
73
+ if aValue.is_a?(Construct) then
74
+ aValue = aValue.to_stringHash();
75
+ end
76
+ result[aKey.to_s] = aValue;
77
+ end
78
+ return result;
79
+ end
80
+
81
+ end
82
+
83
+ class ConfigurationError < StandardError
84
+ end
85
+
86
+ class ResourceNotFoundError < StandardError
87
+ end
88
+
89
+ class Conf
90
+
91
+ @@data = Construct.new();
92
+ @@encryptedData = Construct.new();
93
+ @@passPhrase = nil;
94
+ @@configFileNames = Array.new();
95
+ @@recipePaths = Array.new();
96
+
97
+ def self.get_pass_phrase(check = false)
98
+ if @@passPhrase.nil? then
99
+ require 'highline';
100
+ @@passPhrase = HighLine.new.ask("Pass phrase for encrypted configuration:") { | q | q.echo='*'; }
101
+ while check do
102
+ secondPassPhrase = HighLine.new.ask("Pass phrase (again):") { | q | q.echo='*'; }
103
+ if @@passPhrase != secondPassPhrase then
104
+ puts "The pass phrases do not match... please try again\n\n";
105
+ @@passPhrase = HighLine.new.ask("Pass phrase for encrypted configuration:") { | q | q.echo='*'; }
106
+ else
107
+ check = false;
108
+ end
109
+ end
110
+ end
111
+ @@passPhrase;
112
+ end
113
+
114
+ def self.data
115
+ return @@data;
116
+ end
117
+
118
+ def self.encryptedData
119
+ return @@encryptedData;
120
+ end
121
+
122
+ def self.has_key?(aKey)
123
+ return @@data.has_key?(aKey);
124
+ end
125
+
126
+ def self.has_encrypted_key?(aKey)
127
+ return @@encryptedData.has_key?(aKey);
128
+ end
129
+
130
+ def self.load(yaml)
131
+ loadedHash = YAML::load(yaml);
132
+ if loadedHash then
133
+ @@data.merge(loadedHash);
134
+ end
135
+ end
136
+
137
+ def self.load_file(yamlFileName)
138
+ Rake::Application.mesg "loading configuration file [#{yamlFileName}]\n in #{Dir.getwd}" if Rake.application.options.trace;
139
+ loadedHash = YAML::load_file(yamlFileName);
140
+ if loadedHash then
141
+ @@data.merge(loadedHash);
142
+ end
143
+ end
144
+
145
+ def self.load_encrypted_file(yamlFileName)
146
+ Rake::Application.mesg "loading encrypted configuration file [#{yamlFileName}]\n in #{Dir.getwd}" if Rake.application.options.trace;
147
+ yamlPlainText = "";
148
+ begin
149
+ yamlPlainText = gpgDecryptFile2Data(yamlFileName);
150
+ rescue
151
+ yamlPlainText = openSslDecryptFile2Data(yamlFileName);
152
+ end
153
+ loadedHash = YAML::load(yamlPlainText);
154
+ if loadedHash then
155
+ @@encryptedData.merge(loadedHash);
156
+ end
157
+ end
158
+
159
+ def self.save_file(yamlFileName, branch = [])
160
+ branchData = @@data;
161
+ branch.each do | aKey |
162
+ branchData = branchData[aKey] if branchData.has_key?(aKey);
163
+ end
164
+ branchData = branchData.to_stringHash if branchData.is_a?(Construct);
165
+ branch.reverse.each do | aKey |
166
+ tmpHash = Hash.new();
167
+ tmpHash[aKey.to_s] = branchData;
168
+ branchData = tmpHash;
169
+ end
170
+ yamlFile = File.open(yamlFileName, "w");
171
+ YAML::dump(branchData, yamlFile);
172
+ yamlFile.close();
173
+ end
174
+
175
+ def self.method_missing(meth, *args)
176
+ meth_s = meth.to_s
177
+ if @@data.respond_to?(meth) ||
178
+ @@data.data.has_key?(meth) ||
179
+ @@data.schema.has_key?(meth) ||
180
+ meth_s[-1..-1] == '='then
181
+ @@data.method_missing(meth, *args);
182
+ else
183
+ raise ConfigurationError, "No configuation value specified for Conf[#{meth.to_s}]";
184
+ end
185
+ end
186
+
187
+ def self.add_config_file_name(configFileName)
188
+ configFileName.sub!(/\.[^\.]*$/,'');
189
+ Rake::Application.mesg "Adding config name: [#{configFileName}]";
190
+ @@configFileNames.push(configFileName);
191
+ end
192
+
193
+ def self.load_config_files(recipeDir)
194
+ @@configFileNames.each() do | aConfigFileName |
195
+ #
196
+ # load the yaml configuration file
197
+ #
198
+ confFile = recipeDir + '/' + aConfigFileName + '.conf';
199
+ self.load_file(confFile) if File.exists?(confFile);
200
+ encConfFile = recipeDir + '/' + aConfigFileName + '.enc';
201
+ self.load_encrypted_file(encConfFile) if File.exists?(encConfFile);
202
+ end
203
+ end
204
+
205
+ def self.load_rake_files(recipeDir)
206
+ @@configFileNames.each() do | aConfigFileName |
207
+ #
208
+ # load the corresponding rake file
209
+ #
210
+ rakeFile = recipeDir + '/' + aConfigFileName + '.rake';
211
+ Rake.load_rakefile(rakeFile) if File.exists?(rakeFile);
212
+ end
213
+ end
214
+
215
+ def self.add_recipes_path(aRecipesPath)
216
+ @@recipePaths.push(aRecipesPath);
217
+ end
218
+
219
+ def self.get_recipe_paths()
220
+ @@recipePaths;
221
+ end
222
+
223
+ def self.find_resource(aPartialResourcePath)
224
+ @@recipePaths.each do | aRecipePath |
225
+ aResourcePath = aRecipePath+aPartialResourcePath;
226
+ return aResourcePath if File.exists?(aResourcePath);
227
+ end
228
+ raise ResourceNotFoundError, "Resource file #{aPartialResourcePath} could not be found";
229
+ end
230
+
231
+ def self.each_resource(aPartialResourcePath, &aBlock)
232
+ @@recipePaths.each do | aRecipePath |
233
+ aResourcePath = aRecipePath+aPartialResourcePath;
234
+ next unless File.exists?(aResourcePath);
235
+ aBlock.call(aResourcePath);
236
+ end
237
+ end
238
+
239
+ def self.add_cookbook(aCookbookPath)
240
+ $LOAD_PATH.unshift(aCookbookPath+'/lib');
241
+ Conf.add_recipes_path(aCookbookPath+'/');
242
+ Conf.add_recipes_path(aCookbookPath+'/recipes/');
243
+ # Dir.chdir(aCookbookPath) do
244
+ #
245
+ # load the cookbook configuration file first incase they are used
246
+ # in the rakefile
247
+ confFile = aCookbookPath + '/' + 'cookbook.conf';
248
+ Conf.load_file(confFile) if File.exists?(confFile);
249
+ encConfFile = aCookbookPath + '/' + 'cookbook.enc';
250
+ Conf.load_encrypted_file(encConfFile) if File.exists?(encConfFile);
251
+
252
+ # now load the corresponding cookbook rake file
253
+ rakeFile = aCookbookPath + '/' + 'cookbook.rake';
254
+ Rake.load_rakefile(rakeFile) if File.exists?(rakeFile);
255
+ # end
256
+ end
257
+
258
+ def self.log_file_name()
259
+ return @@configFileNames.join('-') unless @@configFileNames.empty?;
260
+ return "noConfig";
261
+ end
262
+
263
+ def self.load_recipe_dir(recipeDir)
264
+ #
265
+ # extract the last directory name in the path to use as the
266
+ # recipeName
267
+ #
268
+ recipeName = recipeDir.split(/\//).pop();
269
+ #
270
+ # search for sub-directories and recursively calling
271
+ # load_recipe_dir to load any *.conf/*.enc or *.rake fragments
272
+ # found.
273
+ #
274
+ Dir.glob(recipeDir+'/*').each do | aFileName |
275
+ next unless aFileName.dup.force_encoding("UTF-8").valid_encoding?
276
+ next if aFileName =~ /\.$/;
277
+ next unless File.directory?(aFileName);
278
+ load_recipe_dir(aFileName);
279
+ end
280
+ #
281
+ # load the configurtion files first in case they are used in the
282
+ # rakefile
283
+ # ... generic first ...
284
+ confFile = recipeDir + '/' + recipeName + '.conf';
285
+ Conf.load_file(confFile) if File.exists?(confFile);
286
+ encConfFile = recipeDir + '/' + recipeName + '.enc';
287
+ Conf.load_encrypted_file(encConfFile) if File.exists?(encConfFile);
288
+ # ... then more specific ...
289
+ Conf.load_config_files(recipeDir);
290
+
291
+ # now load the corresponding rake files
292
+ # ... generic first...
293
+ rakeFile = recipeDir + '/' + recipeName + '.rake';
294
+ Rake.load_rakefile(rakeFile) if File.exists?(rakeFile);
295
+ # ... then more specific ...
296
+ Conf.load_rake_files(recipeDir);
297
+ end
298
+
299
+ def self.load_recipe(recipeName)
300
+ @@recipePaths.reverse.each do | aRecipePath |
301
+ recipeDir = aRecipePath+recipeName;
302
+ next unless File.directory?(recipeDir);
303
+ load_recipe_dir(recipeDir);
304
+ end
305
+ end
306
+
307
+ def self.load_central_config_files()
308
+ @@recipePaths.reverse.each do | aRecipePath |
309
+ load_config_files(aRecipePath.sub(/\/$/,''));
310
+ end
311
+ end
312
+
313
+ end
314
+
315
+ module Rake
316
+
317
+ class <<self
318
+ alias_method :rake_config_original_load_rakefile, :load_rakefile
319
+ def load_rakefile(*args)
320
+ Rake::Application.mesg "Loading rakefile [#{args[0]}]\n in #{Dir.getwd}" if Rake.application.options.trace;
321
+ rake_config_original_load_rakefile(*args);
322
+ end
323
+ end
324
+
325
+ class Application
326
+
327
+ alias_method :rake_config_original_standard_rake_options, :standard_rake_options ;
328
+ def standard_rake_options
329
+ options = rake_config_original_standard_rake_options();
330
+ options.push(
331
+ ['--config', '-c YAML-CONFIG-FILE-NAME', "Load YAML configuration files named YAML-CONFIG-FILE-NAME in each receipe if it exists",
332
+ lambda { |value|
333
+ confValues = value.split(/,/).each do | aValue |
334
+ Conf.add_config_file_name(aValue);
335
+ end
336
+ }
337
+ ]);
338
+ return options;
339
+ end
340
+
341
+ alias_method :rake_config_original_collect_tasks, :collect_tasks
342
+ def collect_tasks
343
+ rake_config_original_collect_tasks;
344
+ @top_level_tasks.unshift("cookPostConfig");
345
+ @top_level_tasks.unshift("cookConfig");
346
+ @top_level_tasks.unshift("cookPreConfig");
347
+ end
348
+
349
+ alias_method :rake_config_original_top_level, :top_level
350
+ def top_level
351
+ # load the central config files after all recipes
352
+ Conf.load_central_config_files();
353
+ # but before we start invoking tasks
354
+ rake_config_original_top_level;
355
+ end
356
+
357
+ def raw_load_rakefile
358
+ myCookbookDir = File.expand_path(Dir.getwd());
359
+ FileUtils.mkdir_p('logs');
360
+ Rake::Application.openLogger('logs/'+Conf.log_file_name.gsub(/\//,'_')+'-buildLog');
361
+ Rake::Application.logger.level = Logger::DEBUG;
362
+ Rake::Application.mesg "Building in #{myCookbookDir}";
363
+ Conf.add_cookbook(myCookbookDir);
364
+ end
365
+
366
+ end
367
+
368
+ class Task
369
+
370
+ # Execute the rsync command on the arguments provided using greenletters.
371
+ def self.scp_to_remote_scp(localPath, remotePath, options = {}, &aBlock)
372
+ options = Conf.scp.data.merge(options);
373
+ options[:timeout] = 10 unless options.has_key?(:timeout);
374
+ cmd = [options[:command],
375
+ options[:cmdOptions],
376
+ localPath,
377
+ "#{options[:userId]}@#{options[:remoteHost]}:#{remotePath}",
378
+ ].flatten
379
+
380
+ gl_run(*cmd, options, &aBlock);
381
+ end
382
+
383
+ # Execute the rsync command on the arguments provided using greenletters.
384
+ def self.scp_from_remote(remotePath, localPath, options = {}, &aBlock)
385
+ options = Conf.scp.data.merge(options);
386
+ options[:timeout] = 10 unless options.has_key?(:timeout);
387
+ cmd = [options[:command],
388
+ options[:cmdOptions],
389
+ "#{options[:userId]}@#{options[:remoteHost]}:#{remotePath}",
390
+ localPath,
391
+ ].flatten
392
+
393
+ gl_run(*cmd, options, &aBlock);
394
+ end
395
+
396
+ # Execute the rsync command on the arguments provided using greenletters.
397
+ def self.rsync_to_remote(localPath, remotePath, options = {}, &aBlock)
398
+ if localPath =~ /\*/ then
399
+ localPath = Dir.glob(localPath);
400
+ end
401
+ options = Conf.rsync.data.merge(options);
402
+ options[:timeout] = 10 unless options.has_key?(:timeout);
403
+ cmd = [options[:command],
404
+ options[:cmdOptions],
405
+ localPath,
406
+ "#{options[:userId]}@#{options[:remoteHost]}:#{remotePath}",
407
+ ].flatten
408
+
409
+ gl_run(*cmd, options, &aBlock);
410
+ end
411
+
412
+ # Execute the rsync command on the arguments provided using greenletters.
413
+ def self.rsync_from_remote(remotePath, localPath, options = {}, &aBlock)
414
+ options = Conf.rsync.data.merge(options);
415
+ options[:timeout] = 10 unless options.has_key?(:timeout);
416
+ cmd = [options[:command],
417
+ options[:cmdOptions],
418
+ "#{options[:userId]}@#{options[:remoteHost]}:#{remotePath}",
419
+ localPath,
420
+ ].flatten
421
+
422
+ gl_run(*cmd, options, &aBlock);
423
+ end
424
+
425
+ # Execute +command+ using ssh to remote_host using greenletters.
426
+ def self.remote_ssh(*args, &aBlock)
427
+ options = if args.last.is_a?(Hash) then args.pop else {} end
428
+ options = Conf.ssh.data.merge(options);
429
+ options[:timeout] = 10 unless options.has_key?(:timeout);
430
+ sshPort = Array.new();
431
+ sshPort = [ '-p', options[:remotePort]] if options.has_key?(:remotePort);
432
+ cmd = [options[:command],
433
+ options[:cmdOptions],
434
+ '-l',
435
+ options[:userId],
436
+ sshPort,
437
+ options[:remoteHost]
438
+ ].flatten
439
+
440
+ gl_run(*cmd, options) do | p |
441
+ p.wait_for(:output, Regexp.new(options[:commandPromptRegExp]));
442
+ Rake::Application.mesg args.flatten.join(' ') if Rake.application.options.trace;
443
+ p.puts args.flatten.join(' ') + '; exit';
444
+ end
445
+ end
446
+
447
+ # Execute +command+ using sh on local_host using greenletters.
448
+ def self.local_sh(*args, &aBlock)
449
+ options = if args.last.is_a?(Hash) then args.pop else {} end
450
+ options = Conf.sh.data.merge(options);
451
+ cmd = [options[:command],
452
+ options[:cmdOptions],
453
+ "-c",
454
+ args.flatten.join(' ')
455
+ ].flatten
456
+
457
+ gl_run(*cmd, options, &aBlock);
458
+ end
459
+
460
+ # Execute +command+ (as is) on local_host using greenletters to
461
+ # enable expect like behaviour (useful for dealling with
462
+ # passwords).
463
+ def self.gl_run(*args, &aBlock)
464
+ $TRACE = Rake.application.options.trace;
465
+
466
+ options = Hash.new;
467
+ if args.last.is_a?(Construct) then
468
+ raise ConfigurationError, "gl_run should not be passed a Construct";
469
+ elsif args.last.is_a?(Hash)
470
+ options = args.pop;
471
+ end
472
+ Conf.timeout = Hash.new() unless Conf.has_key?(:timeout);
473
+ Conf.timeout.maxTimeout = 10 unless Conf.timeout.has_key?(:maxTimeout);
474
+ if options.has_key?(:timeout) then
475
+ options[:timeout] = Conf.timeout.maxTimeout if Conf.timeout.maxTimeout.to_i < options[:timeout].to_i ;
476
+ else
477
+ options[:timeout] = 10;
478
+ end
479
+
480
+ Rake::Application.mesg args.join(' ') if $TRACE
481
+ Rake::Application.mesg "#{options}" if $TRACE && !options.empty?() && options.has_key?(:verbose);
482
+
483
+ asyncTriggersBlocks = Array.new;
484
+ if options.has_key? :asyncTriggersBlocks then
485
+ asyncTriggersBlocks = options[:asyncTriggersBlocks];
486
+ end
487
+
488
+ exitStatus = 0;
489
+ if options.has_key? :exitStatus then
490
+ exitStatus = options[:exitStatus];
491
+ end
492
+
493
+ options[:env] = ENV unless options.has_key?(:env);
494
+ options[:transcript] = Rake::Application.logger unless options.has_key?(:transcript);
495
+
496
+ cmdProcess = Greenletters::Process.new(*args, options);
497
+ cmdProcess.start!;
498
+
499
+ asyncTriggersBlocks.each do | asyncTriggersBlock |
500
+ if asyncTriggersBlock.kind_of? Proc then
501
+ asyncTriggersBlock.call(cmdProcess);
502
+ end
503
+ end
504
+
505
+ aBlock.call(cmdProcess) unless aBlock.nil?;
506
+
507
+ cmdProcess.wait_for(:exit, exitStatus);
508
+
509
+ cmdProcess
510
+ end
511
+
512
+ end
513
+
514
+ module DSL
515
+
516
+ def config(*args, &block)
517
+ Rake::Task.define_task(*args, &block);
518
+ end
519
+
520
+ def load_recipe(recipeName)
521
+ Conf.load_recipe(recipeName);
522
+ end
523
+
524
+ def add_cookbook(aCookbookPath)
525
+ Conf.add_cookbook(aCookbookPath);
526
+ end
527
+
528
+ def find_resource(aPartialResourcePath)
529
+ Conf.find_resource(aPartialResourcePath);
530
+ end
531
+
532
+ def read_resource(aPartialResourcePath)
533
+ File.open(Conf.find_resource(aPartialResourcePath)).read;
534
+ end
535
+
536
+ def each_resource(aPartialResourcePath, &aBlock)
537
+ Conf.each_resource(aPartialResourcePath, &aBlock);
538
+ end
539
+
540
+ def set_command_options(commandSymbols, options = {}, &asyncTriggersBlock)
541
+ commandSymbols = [ :local, :remote ] if commandSymbols == :all;
542
+ commandSymbols = [ :local, :remote ] if commandSymbols.kind_of? Array and commandSymbols.include? :all;
543
+ commandSymbols = [ commandSymbols ] unless commandSymbols.kind_of? Array;
544
+ commandSymbols.push(:sh).flatten! if commandSymbols.include?(:local);
545
+ commandSymbols.push([:scp, :ssh, :rsync]).flatten! if commandSymbols.include?(:remote);
546
+ commandSymbols.each do | aCommandSymbol |
547
+ aCommandSymbol = aCommandSymbol.to_sym;
548
+ next if aCommandSymbol == :all;
549
+ next if aCommandSymbol == :local;
550
+ next if aCommandSymbol == :remote;
551
+ if !Conf.data.has_key?(aCommandSymbol) then
552
+ Conf.data[aCommandSymbol] = Hash.new;
553
+ Conf.data[aCommandSymbol].command = aCommandSymbol.to_s;
554
+ Conf.data[aCommandSymbol].cmdOptions = Array.new;
555
+ Conf.data[aCommandSymbol].commandPromptRegExp = '.';
556
+ Conf.data[aCommandSymbol].asyncTriggersBlocks = Array.new;
557
+ end
558
+ Conf.data[aCommandSymbol].merge(options);
559
+ Conf.data[aCommandSymbol].asyncTriggersBlocks.push(asyncTriggersBlock);
560
+ end
561
+ end
562
+
563
+ def local_sh(*args, &block)
564
+ Rake::Task.local_sh(*args, &block);
565
+ end
566
+
567
+ def local_run(*args, &block)
568
+ Rake::Application.mesg "Use of local_run is depreciated. Please use local_sh instead";
569
+ Rake::Task.local_sh(*args, &block);
570
+ end
571
+
572
+ def remote_ssh(*args, &block)
573
+ Rake::Task.remote_ssh(*args, &block);
574
+ end
575
+
576
+ def remote_run(*args, &block)
577
+ Rake::Application.mesg "Use of remote_run is depreciated. Please use remote_ssh instead";
578
+ Rake::Task.remote_ssh(*args, &block);
579
+ end
580
+
581
+ def scp_to_remote(localPath, remotePath, options = {}, &block)
582
+ Rake::Task.scp_to_remote(localPath, remotePath, options &block);
583
+ end
584
+
585
+ def scp_from_remote(remotePath, localPath, options = {}, &block)
586
+ Rake::Task.scp_from_remote(remotePath, localPath, options, &block);
587
+ end
588
+
589
+ def rsync_to_remote(localPath, remotePath, options = {}, &aBlock)
590
+ Rake::Task.rsync_to_remote(localPath, remotePath, options, &aBlock);
591
+ end
592
+
593
+ def rsync_from_remote(remotePath, localPath, options = {}, &aBlock)
594
+ Rake::Task.rsync_from_remote(remotePath, localPath, options, &aBlock);
595
+ end
596
+
597
+ end
598
+
599
+ end
600
+
601
+ self.extend Rake::DSL
602
+
603
+
604
+ # Ensure the configuration for the sh, scp, ssh and rsync commands are setup
605
+ #Conf.sh = Hash.new;
606
+ #Conf.sh.command = "sh";
607
+ #Conf.sh.cmdOptions = Array.new;
608
+ #Conf.ssh = Hash.new;
609
+ #Conf.ssh.command = "ssh";
610
+ #Conf.ssh.cmdOptions = Array.new;
611
+ #Conf.scp = Hash.new;
612
+ #Conf.scp.command = "scp";
613
+ #Conf.scp.cmdOptions = Array.new;
614
+ #Conf.rsync = Hash.new;
615
+ #Conf.rsync.command = "rsync";
616
+ #Conf.rsync.cmdOptions = Array.new;
617
+ set_command_options(:all);
618
+
619
+ # Ensure the gpg hash exists (but may be empty)
620
+ Conf.gpg = Hash.new;
621
+
622
+ $LOAD_PATH.push('.');
623
+
624
+ config :cookPreConfig do
625
+ Rake::Application.mesg "finished (cook) preConfig stage\n\n";
626
+ end
627
+
628
+ config :cookConfig do
629
+ Rake::Application.mesg "finished (cook) config stage\n\n";
630
+ end
631
+
632
+ config :cookPostConfig do
633
+ Rake::Application.mesg "finished (cook) postConfig stage\n\n";
634
+ if Rake.application.options.trace then
635
+ Rake::Application.mesg "--------------------\nTasks defined\n\n";
636
+ Rake::Application.mesg_pp Rake.application.tasks;
637
+ Rake::Application.mesg "--------------------\nConfiguration data\n\n";
638
+ Rake::Application.mesg_pp Conf.data;
639
+ Rake::Application.mesg "--------------------\n\n";
640
+ end
641
+ end
642
+