knife-chop 0.2.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +15 -0
  2. data/Gemfile +24 -0
  3. data/Gemfile.lock +154 -0
  4. data/LICENSE.txt +20 -0
  5. data/README.rdoc +23 -0
  6. data/Rakefile +54 -0
  7. data/TODO.rdoc +46 -0
  8. data/VERSION +1 -0
  9. data/bin/chop +48 -0
  10. data/knife-chop.gemspec +118 -0
  11. data/lib/chef/knife/chop/chef_data_bag_item.rb +43 -0
  12. data/lib/chef/knife/chop/chef_environment.rb +47 -0
  13. data/lib/chef/knife/chop/chef_knife.rb +85 -0
  14. data/lib/chef/knife/chop/chef_part.rb +191 -0
  15. data/lib/chef/knife/chop/chef_role.rb +48 -0
  16. data/lib/chef/knife/chop/cookbook_upload.rb +143 -0
  17. data/lib/chef/knife/chop/data_bag_from_file.rb +87 -0
  18. data/lib/chef/knife/chop/environment_from_file.rb +79 -0
  19. data/lib/chef/knife/chop/errors.rb +5 -0
  20. data/lib/chef/knife/chop/logging.rb +245 -0
  21. data/lib/chef/knife/chop/role_from_file.rb +45 -0
  22. data/lib/chef/knife/chop/translate.rb +23 -0
  23. data/lib/chef/knife/chop/translate/eden.rb +23 -0
  24. data/lib/chef/knife/chop/translate/rbeautify.rb +24 -0
  25. data/lib/chef/knife/chop/ui.rb +110 -0
  26. data/lib/chef/knife/chop/version.rb +9 -0
  27. data/lib/chef/knife/chop_base.rb +821 -0
  28. data/lib/chef/knife/chop_translate.rb +161 -0
  29. data/lib/chef/knife/chop_upload.rb +199 -0
  30. data/lib/ruby-beautify/Gemfile +4 -0
  31. data/lib/ruby-beautify/LICENSE +22 -0
  32. data/lib/ruby-beautify/README.md +39 -0
  33. data/lib/ruby-beautify/RELEASE.md +13 -0
  34. data/lib/ruby-beautify/Rakefile +2 -0
  35. data/lib/ruby-beautify/bin/rbeautify +28 -0
  36. data/lib/ruby-beautify/lib/beautifier.rb +168 -0
  37. data/lib/ruby-beautify/lib/ruby-beautify.rb +26 -0
  38. data/lib/ruby-beautify/lib/ruby-beautify/block_end.rb +23 -0
  39. data/lib/ruby-beautify/lib/ruby-beautify/block_matcher.rb +153 -0
  40. data/lib/ruby-beautify/lib/ruby-beautify/block_start.rb +119 -0
  41. data/lib/ruby-beautify/lib/ruby-beautify/config/ruby.rb +131 -0
  42. data/lib/ruby-beautify/lib/ruby-beautify/language.rb +37 -0
  43. data/lib/ruby-beautify/lib/ruby-beautify/line.rb +53 -0
  44. data/lib/ruby-beautify/lib/ruby-beautify/version.rb +3 -0
  45. data/lib/ruby-beautify/ruby-beautify.gemspec +17 -0
  46. data/lib/ruby-beautify/spec/fixtures/ruby.yml +408 -0
  47. data/lib/ruby-beautify/spec/rbeautify/block_matcher_spec.rb +89 -0
  48. data/lib/ruby-beautify/spec/rbeautify/block_start_spec.rb +51 -0
  49. data/lib/ruby-beautify/spec/rbeautify/config/ruby_spec.rb +183 -0
  50. data/lib/ruby-beautify/spec/rbeautify/line_spec.rb +73 -0
  51. data/lib/ruby-beautify/spec/rbeautify_spec.rb +1 -0
  52. data/lib/ruby-beautify/spec/spec_helper.rb +124 -0
  53. data/spec/knife-chop_spec.rb +7 -0
  54. data/spec/spec_helper.rb +12 -0
  55. metadata +233 -0
@@ -0,0 +1,23 @@
1
+ #
2
+ # Author:: Christo De Lange (<opscode@dldinternet.com>)
3
+ # Copyright:: Copyright (c) 2013 DLDInternet, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ require 'json'
20
+ #require 'chef/knife/chop/translate/eden'
21
+ require 'chef/knife/chop/translate/rbeautify'
22
+ #require 'ripper'
23
+ #require 'sorcerer'
@@ -0,0 +1,23 @@
1
+ #
2
+ # Author:: Christo De Lange (<opscode@dldinternet.com>)
3
+ # Copyright:: Copyright (c) 2013 DLDInternet, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ path = File.expand_path(File.dirname(__FILE__)+"/../../../../")
20
+ path = File.expand_path("eden/lib", path)
21
+ $:.unshift(path)
22
+
23
+ require 'eden'
@@ -0,0 +1,24 @@
1
+ #
2
+ # Author:: Christo De Lange (<opscode@dldinternet.com>)
3
+ # Copyright:: Copyright (c) 2013 DLDInternet, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ path = File.expand_path(File.dirname(__FILE__)+"/../../../../")
20
+ path = File.expand_path("ruby-beautify/lib", path)
21
+ $:.unshift(path)
22
+
23
+ require 'ruby-beautify'
24
+ include RBeautify
@@ -0,0 +1,110 @@
1
+ #
2
+ # Author:: Christo De Lange (<opscode@dldinternet.com>)
3
+ # Copyright:: Copyright (c) 2013 DLDInternet, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ require 'chef/knife/core/ui'
20
+ require 'chef/knife/chop/errors'
21
+
22
+ class Chef
23
+ class Knife
24
+ class ChopUI < ::Chef::Knife::UI
25
+ include ChopErrors
26
+
27
+ attr_reader :logger
28
+
29
+ def initialize(logger, config)
30
+ super($stdout, $stderr, $stdin, config)
31
+ @logger = logger
32
+ #define_ui_methods()
33
+ end
34
+
35
+ #def define_log_methods( ui )
36
+ def msg(message)
37
+ caller = Kernel.caller[0]
38
+ match = %r/([-\.\/\(\)\w]+):(\d+)(?::in `(\w+)')?/o.match(caller)
39
+ name = shifted(match[3])
40
+ @logger.send(name, message)
41
+ end
42
+
43
+ #def define_ui_methods()
44
+ # class << self
45
+ # ::Logging::LEVELS.each{|name,level|
46
+ # code = <<-CODE
47
+ # def #{name}(str)
48
+ # msg(str)
49
+ # end
50
+ # CODE
51
+ # self.class.class_eval(code,__FILE__,__LINE__)
52
+ # }
53
+ # end
54
+ #end
55
+ def info(message)
56
+ msg(message)
57
+ end
58
+
59
+ def step(message)
60
+ msg(message)
61
+ end
62
+
63
+ def err(message)
64
+ error(message)
65
+ end
66
+
67
+ def error(message)
68
+ msg(message)
69
+ end
70
+
71
+ # Print a warning message
72
+ def warn(message)
73
+ msg(message)
74
+ end
75
+
76
+ # Print an error message
77
+ def error(message)
78
+ msg(message)
79
+ end
80
+
81
+ # Print a message describing a fatal error.
82
+ def fatal(message)
83
+ msg(message)
84
+ end
85
+
86
+ def method_missing(name, *args, &block)
87
+ msg = "#{self.class.name}: Method missing: #{name}"
88
+ @logger.fatal(msg)
89
+ raise ChopInternalError.new(msg)
90
+ end
91
+
92
+ def shifted(name)
93
+ num = ::Logging::LEVELS[name]+1
94
+ case name
95
+ when 'todo'
96
+ 'error'
97
+ when 'err'
98
+ 'error'
99
+ when 'info'
100
+ 'debug'
101
+ when 'debug'
102
+ 'trace'
103
+ else
104
+ name
105
+ end
106
+ end
107
+
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,9 @@
1
+ module Knife
2
+ module Chop
3
+ file = File.expand_path("#{File.dirname(__FILE__)}/../../../../VERSION")
4
+ lines = File.readlines(file)
5
+ version = lines[0]
6
+ VERSION = version
7
+ MAJOR, MINOR, TINY = VERSION.split('.')
8
+ end
9
+ end
@@ -0,0 +1,821 @@
1
+ #
2
+ # Author:: Christo De Lange (<opscode@dldinternet.com>)
3
+ # Copyright:: Copyright (c) 2013 DLDInternet, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ require "awesome_print"
20
+ require 'chef/knife'
21
+ require 'chef/knife/chop/version'
22
+ require 'chef/knife/chop/logging'
23
+ require 'chef/knife/chop/errors'
24
+ require 'logging'
25
+
26
+ class Chef
27
+ class Knife
28
+ attr_accessor :logger
29
+ attr_accessor :verbosity
30
+ attr_accessor :LOGLEVELS
31
+ attr_accessor :ALLPARTS
32
+ attr_accessor :ALLACTIONS
33
+
34
+ def self.loglevels=(levels)
35
+ @LOGLEVELS = levels || [:trace, :debug, :step, :info, :warn, :error, :fatal, :todo]
36
+ end
37
+
38
+ def self.allparts=(parts)
39
+ @ALLPARTS = parts || [:environments, :roles, :databags, :cookbooks]
40
+ end
41
+
42
+ def self.allactions=(acts)
43
+ @ALLACTIONS = acts || [:upload, :translate]
44
+ end
45
+
46
+ def self.loglevels
47
+ @LOGLEVELS
48
+ end
49
+
50
+ def self.allparts
51
+ @ALLPARTS
52
+ end
53
+
54
+ def self.allactions
55
+ @ALLACTIONS
56
+ end
57
+
58
+ self.loglevels = nil
59
+ self.allparts = nil
60
+ self.allactions = nil
61
+
62
+
63
+ module ChopBase
64
+ class ::TrueClass
65
+ def to_rb
66
+ to_s
67
+ end
68
+ def yesno
69
+ "yes"
70
+ end
71
+ end
72
+
73
+ class ::FalseClass
74
+ def to_rb
75
+ to_s
76
+ end
77
+ def yesno
78
+ "no"
79
+ end
80
+ end
81
+
82
+ include ChopErrors
83
+
84
+ include ChopLogging
85
+
86
+ # --------------------------------------------------------------------------------
87
+ def parsePartSymbol(v)
88
+ if v.to_sym == :all
89
+ ::Chef::Knife.allparts
90
+ else
91
+ s = v.to_sym
92
+ allparts = [::Chef::Knife.allparts, :all].flatten
93
+ unless allparts.include?(s)
94
+ allparts.each{ |p|
95
+ s = p if p.match(%r/^#{s}/)
96
+ }
97
+ end
98
+ s = ::Chef::Knife.allparts if s == :all
99
+ s
100
+ end
101
+ end
102
+
103
+ # --------------------------------------------------------------------------------
104
+ def parseActionSymbol(v)
105
+ if v.to_sym == :all
106
+ ::Chef::Knife.allactions
107
+ else
108
+ s = v.to_sym
109
+ allactions = [::Chef::Knife.allactions, :all].flatten
110
+ unless allactions.include?(s)
111
+ allactions.each{ |p|
112
+ s = p if p.match(%r/^#{s}/)
113
+ }
114
+ end
115
+ s = ::Chef::Knife.allactions if s == :all
116
+ s
117
+ end
118
+ end
119
+
120
+ # --------------------------------------------------------------------------------
121
+ def parseString(v)
122
+ v
123
+ end
124
+
125
+ # --------------------------------------------------------------------------------
126
+ def parsePath(v)
127
+ File.expand_path(parseString(v))
128
+ end
129
+
130
+ # --------------------------------------------------------------------------------
131
+ def parseList(v,s=',',method='parseString')
132
+ parts = []
133
+ a = v.split(%r/#{s}/)
134
+ a.each{ |t|
135
+ parts << send(method,t)
136
+ }
137
+ parts
138
+ end
139
+
140
+ # --------------------------------------------------------------------------------
141
+ def parseOptionString(v,s=',',method='parseString')
142
+ bags = []
143
+ if v.match(%r'#{s}')
144
+ bags << parseList(v,s,method)
145
+ else
146
+ bags << send(method,v)
147
+ end
148
+ bags.flatten
149
+ end
150
+
151
+ # --------------------------------------------------------------------------------
152
+ def parsePrecedence(v)
153
+ ::Chef::Knife.prec_max += 1
154
+ s = { v => ::Chef::Knife.prec_max }
155
+ match = v.match(%r/^(\S+):(\d+)$/)
156
+ if match
157
+ begin
158
+ a = match[1]
159
+ i = match[2].to_i
160
+ s = { a => i }
161
+ rescue => e
162
+ puts "ERROR: Unable to match precedence #{v}"
163
+ raise e
164
+ end
165
+ end
166
+ s
167
+ end
168
+
169
+ # --------------------------------------------------------------------------------
170
+ def self.included(includer)
171
+ includer.class_eval do
172
+
173
+ deps do
174
+ require 'fog'
175
+ require 'readline'
176
+ require 'colorize'
177
+ require 'inifile'
178
+ require 'chef/knife/chop/chef_knife'
179
+ require 'chef/environment'
180
+ require 'chef/knife/core/object_loader'
181
+ require 'chef/cookbook_loader'
182
+ require 'chef/cookbook_uploader'
183
+ require 'chef/knife/chop/cookbook_upload'
184
+ require 'chef/knife/chop/data_bag_from_file'
185
+ require 'chef/knife/chop/role_from_file'
186
+ require 'chef/knife/chop/environment_from_file'
187
+ require 'chef/knife/chop/chef_part'
188
+ require 'chef/knife/chop/chef_environment'
189
+ require 'chef/knife/chop/chef_role'
190
+ #require 'chef/knife/chop/chef_data_bag_item'
191
+ require 'chef/json_compat'
192
+ require 'chef/knife/bootstrap'
193
+ require 'chef/knife/chop/ui'
194
+ Chef::Knife::Bootstrap.load_deps
195
+ end
196
+
197
+ banner "knife chop (options)"
198
+
199
+ attr_reader :argv
200
+
201
+ # This will print an args summary.
202
+ option :help,
203
+ :short => "-h",
204
+ :long => "--help",
205
+ :description => "Show this message",
206
+ :show_options => true,
207
+ :exit => 1
208
+ # print the version.
209
+ option :version,
210
+ :short => '-V',
211
+ :long => "--version",
212
+ :description => "Show version",
213
+ :proc => Proc.new{ puts ::Knife::Chop::VERSION },
214
+ :exit => 2
215
+ option :verbosity,
216
+ :short => "-v",
217
+ :long => "--[no-]verbose [LEVEL]",
218
+ :description => "Run verbosely",
219
+ :proc => lambda{|s|
220
+ if s.nil? or (s == '')
221
+ $CHOP.verbosity += 1
222
+ else
223
+ $CHOP.verbosity = v.gsub(%r/['"]*/, '').to_i
224
+ end
225
+ }
226
+ option :log_path,
227
+ :long => '--log-path PATH',
228
+ :description => "Log destination path"
229
+ option :log_file,
230
+ :long => '--log-file PATH',
231
+ :description => "Log destination file"
232
+ option :log_level,
233
+ :short => '-l',
234
+ :long => ['--log_level LEVEL','--log-level LEVEL'],
235
+ :description => "Log level (#{::Chef::Knife.loglevels.to_s})",
236
+ :proc => lambda{|v|
237
+ if ::Chef::Knife.loglevels.include? v.to_sym
238
+ v.to_sym
239
+ else
240
+ level = ::Chef::Knife.loglevels.select{|l| l.to_s.match(%r(^#{v}))}
241
+ unless level.size > 0
242
+ raise OptionParser::InvalidOption.new("Invalid log level: #{v}. Valid levels are #{::Chef::Knife.loglevels.ai}")
243
+ end
244
+ level[0].to_sym
245
+ end
246
+ },
247
+ :default => :step
248
+ option :inifile,
249
+ :short => "-f",
250
+ :long => "--inifile FILE",
251
+ :description => "INI file with settings"
252
+ option :parts,
253
+ :short => "-R",
254
+ :long => "--resources PARTS",
255
+ :description => "Parts to upload #{[ :all, ::Chef::Knife.allparts].flatten }. Default: all",
256
+ :default => ::Chef::Knife.allparts,
257
+ :proc => lambda{|v|
258
+ parts = $CHOP.parseOptionString(v,',','parsePartSymbol')
259
+ parts.each{ |part|
260
+ raise ::OptionParser::InvalidOption.new("Invalid part: #{part.to_s}. Valid parts are: #{[::Chef::Knife.allparts, :all].to_s}") unless [::Chef::Knife.allparts, :all].flatten.include?(part.to_sym)
261
+ }
262
+ parts
263
+ }
264
+ option :depends,
265
+ :short => "-I",
266
+ :long => "--[no-]include-dependencies [yes|no|true|false|0|1|enable|disable]",
267
+ :description => "Include Cookbook dependencies?, Default --include-dependencies or -I [1|yes|enable|true]",
268
+ :default => true
269
+ option :dry_run,
270
+ :short => "-n",
271
+ :long => "--[no-]dry-run",
272
+ :description => "Do a dry run, Default --no-dry-run",
273
+ :default => false
274
+ option :cookbook_path,
275
+ :short => "-P",
276
+ :long => "--cookbook-path PATH",
277
+ :description => "Cookbook search path, Default chef/cookbooks/:chef/vendor-cookbooks",
278
+ :default => ["cookbooks/","vendor-cookbooks"],
279
+ :proc => lambda{|v|
280
+ $CHOP.parseOptionString(v,'[:,]','parsePath')
281
+ }
282
+ option :repo_path,
283
+ :long => "--repo-path PATH",
284
+ :description => "Chef repo path, Default ./chef",
285
+ :default => "./chef",
286
+ #:required => true,
287
+ :proc => lambda{|v|
288
+ File.expand_path(v)
289
+ }
290
+ option :cookbooks,
291
+ :short => "-c",
292
+ :long => "--cookbooks COOKBOOKS",
293
+ :description => "Cookbooks to upload (List separated by commas or --all. Default: role",
294
+ :default => ['role'],
295
+ :proc => lambda{|v|
296
+ $CHOP.parseOptionString(v)
297
+ }
298
+ option :envs,
299
+ :short => ["-e", "-E",],
300
+ :long => "--environments REGEXLIST",
301
+ :description => "Environments regex",
302
+ :proc => lambda{|v|
303
+ $CHOP.parseOptionString(v)
304
+ },
305
+ :default => ['web.*']
306
+ option :databags,
307
+ :short => "-b",
308
+ :long => "--databags BAGS",
309
+ :description => "Data bags to upload",
310
+ :default => ['aws:s3_.*_dev;s3_ro_.*','users:web.*;christo.*;tmiller.*'],
311
+ :proc => lambda{|v|
312
+ $CHOP.parseOptionString(v)
313
+ }
314
+ option :roles,
315
+ :short => "-r",
316
+ :long => "--roles ROLES",
317
+ :description => "Roles to upload",
318
+ :default => ["web.*"],
319
+ :proc => lambda{|v|
320
+ $CHOP.parseOptionString(v)
321
+ }
322
+
323
+ option :all,
324
+ :short => "-a",
325
+ :long => "--all",
326
+ :description => "Upload all items for resource group(s)"
327
+
328
+ option :trace,
329
+ :short => "-t",
330
+ :long => "--trace",
331
+ :boolean => true,
332
+ :default => false,
333
+ :description => "Trace logging locations (file::line)"
334
+
335
+ # ------------------------------------------------------------------------------------------------------------
336
+ # Cookbooks
337
+ # ------------------------------------------------------------------------------------------------------------
338
+ option :freeze,
339
+ :long => '--freeze',
340
+ :description => 'Freeze this version of the cookbook so that it cannot be overwritten',
341
+ :boolean => true
342
+
343
+ #option :all,
344
+ # :short => "-a",
345
+ # :long => "--all",
346
+ # :description => "Upload all cookbooks, rather than just a single cookbook"
347
+
348
+ option :force,
349
+ :long => '--force',
350
+ :boolean => true,
351
+ :description => "Update cookbook versions even if they have been frozen"
352
+
353
+ # ------------------------------------------------------------------------------------------------------------
354
+ # Data bags
355
+ # ------------------------------------------------------------------------------------------------------------
356
+ option :secret,
357
+ :short => "-s SECRET",
358
+ :long => "--secret ",
359
+ :description => "The secret key to use to encrypt data bag item values"
360
+
361
+ option :secret_file,
362
+ :long => "--secret-file SECRET_FILE",
363
+ :description => "A file containing the secret key to use to encrypt data bag item values"
364
+
365
+
366
+ # ------------------------------------------------------------------------------------------------------------
367
+ option :precedence,
368
+ :long => "--precedence PREC",
369
+ :description => "Precedence order of parts extensions. Default: json:1,rb:2 or json,rb == [json rb] == { json => 1, rb => 2 } == .rb files will be used when there is both a .json and .rb",
370
+ :default => %w(json rb),
371
+ :proc => lambda{|v|
372
+ prec = $CHOP.parseOptionString(v,',', 'parsePrecedence')
373
+ prec.sort{|x,y| x.values.shift <=> y.values.shift }.map{|e| e.keys.shift }
374
+ }
375
+ option :actions,
376
+ :short => '-a',
377
+ :long => "--action ACTION",
378
+ :description => "Actions to be performed #{[ :all, ::Chef::Knife.allactions].flatten }. Default: upload",
379
+ #:default => [:upload],
380
+ :proc => lambda{|v|
381
+ actions = $CHOP.parseOptionString(v,',','parseActionSymbol')
382
+ actions.each{ |act|
383
+ raise ::OptionParser::InvalidOption.new("Invalid action: #{act.to_s}. Valid actions are: #{[ :all, ::Chef::Knife.allactions].flatten.to_s}") unless [ :all, ::Chef::Knife.allactions].flatten.include?(act.to_sym)
384
+ }
385
+ actions
386
+ }
387
+ option :translate,
388
+ :long => "--translate PREC",
389
+ :description => "Translate parts. Default: json,rb == { :from => 'json', :to => 'rb' } == .json files will be read and .rb equivalents will be saved",
390
+ :default => %w(json rb),
391
+ :proc => lambda{|v|
392
+ $CHOP.parseOptionString(v)
393
+ }
394
+ end
395
+ end
396
+
397
+ # --------------------------------------------------------------------------------
398
+ # Create a new instance of the current class configured for the given
399
+ # arguments and options
400
+ def initialize(argv=[])
401
+ @argv = argv
402
+ $CHOP = self
403
+ @verbosity = 0
404
+ @inis = []
405
+ @use_knife_api = true
406
+
407
+ @stop = false
408
+ @prec_max = 0
409
+ @TODO = {}
410
+ @actors = {}
411
+
412
+ super
413
+ end
414
+
415
+ # --------------------------------------------------------------------------------
416
+ def build_option_arguments(opt_setting)
417
+ arguments = super
418
+ arguments.flatten
419
+ end
420
+
421
+ # --------------------------------------------------------------------------------
422
+ def parse_options(args,source=nil)
423
+ argv = super(args)
424
+
425
+ @config = parse_and_validate_options(@config,source ? source : "ARGV - #{__LINE__}")
426
+ v = @config[:depends]
427
+ @config[:depends] = (v === true) || ((v.is_a?(String) && v.downcase.match(%r/^(no|false|disable|0)/) ).nil? ? false : true)
428
+
429
+ unless @config[:actions]
430
+ @config[:actions] = [ argv[1].to_sym ] # self.class.name.gsub(%r(Chef::Knife::Chop), '').downcase
431
+ end
432
+ @actors[argv[1].to_sym] = self
433
+ others = @config[:actions].select{|a|
434
+ a != argv[1].to_sym
435
+ }
436
+ index = args.index '--action'
437
+ others.each{|a|
438
+ args[1] = a.to_s
439
+ unless index.nil?
440
+ args[index+1] = a.to_s
441
+ end
442
+ subcommand_class = ::Chef::Knife.subcommand_class_from(args)
443
+ subcommand_class.load_deps
444
+ instance = subcommand_class.new(args)
445
+ instance.configure_chef
446
+ @actors[a] = instance
447
+ }
448
+ argv
449
+ end
450
+
451
+ module ::Logging
452
+ class << self
453
+ def levelnames=(lnames)
454
+ remove_const(:LNAMES)
455
+ const_set(:LNAMES, lnames)
456
+ end
457
+ def levelnames()
458
+ LNAMES
459
+ end
460
+ end
461
+ end
462
+
463
+ # --------------------------------------------------------------------------------
464
+ def configure_chef
465
+ super
466
+ @config[:log_opts] = lambda{|mlll| {
467
+ :pattern => "%#{mlll}l: %m %C\n",
468
+ :date_pattern => '%Y-%m-%d %H:%M:%S',
469
+ }
470
+ }
471
+
472
+ @logger = getLogger(@config)
473
+ @ui = Chef::Knife::ChopUI.new(@logger,@config)
474
+ Chef::Log.logger = @logger
475
+ end
476
+
477
+ def run_with_pretty_exceptions
478
+ unless self.respond_to?(:run)
479
+ ui.error "You need to add a #run method to your knife command before you can use it"
480
+ end
481
+ enforce_path_sanity
482
+
483
+ raise ChopOptionError.new("The --repo-path '#{@config[:repo_path]}' is invalid!") unless File.directory?(@config[:repo_path])
484
+
485
+ run
486
+ rescue ChopOptionError => e
487
+ raise if Chef::Config[:verbosity] == 2
488
+ humanize_exception(e)
489
+ exit 100
490
+ rescue ChopError => e
491
+ humanize_exception(e)
492
+ exit 101
493
+ end
494
+
495
+ # --------------------------------------------------------------------------------
496
+ private
497
+ # --------------------------------------------------------------------------------
498
+
499
+ # --------------------------------------------------------------------------------
500
+ def parseINIFile(options=nil)
501
+ options = @config unless options
502
+ if options.key?(:inifile)
503
+ logStep "Parse INI file - #{options[:inifile]}"
504
+ raise ChopError.new("Cannot find inifile (#{options[:inifile]})") unless File.exist?(options[:inifile])
505
+ raise ChopError.new("Recursive call to inifile == '#{options[:inifile]}'") if @inis.include?(options[:inifile])
506
+ ini = nil
507
+ begin
508
+ ini = IniFile.load(options[:inifile])
509
+ @inis << options[:inifile]
510
+ ini['global'].each { |key, value|
511
+ #puts "#{key}=#{value}"
512
+ ENV[key]=value
513
+ }
514
+ argv=[]
515
+ cli = ini['cli'] || []
516
+ cli.each{ |key,value|
517
+ argv << key.gsub(%r/:[0-9]+$/, '').gsub(%r/^([^-])/, '--\1')
518
+ argv << value
519
+ }
520
+ if argv.size > 0
521
+ parse_options(argv,"INI-#{options[:inifile]}")
522
+ end
523
+ rescue => e
524
+ puts e.message.light_red
525
+ raise e
526
+ end
527
+ end
528
+ options
529
+ end
530
+
531
+ # -----------------------------------------------------------------------------
532
+ def setDefaultOptions(options)
533
+ @options.each{|name,args|
534
+ if args[:default]
535
+ options[name] = args[:default] unless options[name]
536
+ end
537
+ }
538
+ setOrigins(options,'default')
539
+ end
540
+
541
+ # -----------------------------------------------------------------------------
542
+ def validate_options(options=nil)
543
+ options = @config unless options
544
+
545
+ # Check for the necessary environment variables
546
+ logStep ("Check ENVironment")
547
+ env = ENV.to_hash
548
+ missing = {}
549
+ %w(AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY KNIFE_CHEF_SERVER_URL KNIFE_CLIENT_KEY KNIFE_CLIENT_NAME).each { |k|
550
+ missing[k] = true unless ENV.has_key?(k)
551
+ }
552
+
553
+ if missing.count() > 0
554
+ #@logger.error "Missing keys: #{missing.keys.ai}"
555
+ raise ChopError.new("Missing environment variables: #{missing.keys}")
556
+ end
557
+ end
558
+
559
+ # -----------------------------------------------------------------------------
560
+ def parse_and_validate_options(options=nil,source='ARGV')
561
+ options = @config unless options
562
+ setOrigins(options,source)
563
+
564
+ #options = parseOptions(options,source)
565
+ unless @origins and @name_key_map
566
+ # These are the essential default options which things like parseOptions depend on
567
+ {
568
+ :verbosity => @verbosity,
569
+ :auto_purge => false,
570
+ }.each{ |k,v|
571
+ options[k] = v unless options[k]
572
+ }
573
+ setOrigins(options,'hardcoded-default')
574
+
575
+ @name_key_map = {} unless @name_key_map
576
+ @options.each{ |name,args|
577
+ @name_key_map[name] = {} unless @name_key_map[name]
578
+ [:short,:long,:description].each{|key|
579
+ @name_key_map[name][key] = args[key] if args[key]
580
+ }
581
+ }
582
+ end
583
+
584
+ begin
585
+ parseINIFile(options)
586
+ setDefaultOptions(options)
587
+ # Check for all the necessary options
588
+ validate_options(options)
589
+ checkArgsSources(options)
590
+ #findRootPath(options)
591
+ rescue ChopError => e
592
+ puts e.message.light_red
593
+ exit -1
594
+ rescue Exception => e
595
+ puts e.message.light_red
596
+ exit -2
597
+ end
598
+
599
+ options
600
+ end
601
+
602
+ # ---------------------------------------------------------------------------------------------------------------
603
+ def setOrigins(options,source)
604
+ @origins = {} unless @origins
605
+ options.each { |key, val|
606
+ @origins[key] = source unless (@origins[key])
607
+ }
608
+ end
609
+
610
+ # ---------------------------------------------------------------------------------------------------------------
611
+ def checkArgsSources(options)
612
+ if @origins
613
+ missing = @origins.select{ |k,v|
614
+ v.nil?
615
+ }.map{ |k,v| k }
616
+ raise ChopError.new("Missing origins: #{missing.ai}") if missing.size > 0
617
+ end
618
+ end
619
+
620
+ ## ---------------------------------------------------------------------------------------------------------------
621
+ #def findRootPath(options)
622
+ # @root_path = ''
623
+ # cbpaths = @config[:cookbook_path]#.split(File::PATH_SEPARATOR)
624
+ # common = cbpaths[0]
625
+ # begin
626
+ # common = File.dirname(common)
627
+ # ayes = 1
628
+ # cbpaths[1..-1].each{ |cbp|
629
+ # if File.dirname(cbp).match(%r(^#{common}))
630
+ # ayes += 1
631
+ # end
632
+ # }
633
+ # @root_path = common
634
+ # end while ((common != '') and (ayes != cbpaths.size))
635
+ # @root_path
636
+ #end
637
+
638
+ # --------------------------------------------------------------------------------
639
+ def getPathSet(want, path, exts=nil)
640
+ raise ChopInternalError.new("Bad call to #{self.class.name}.getPathSet: want == nil") unless want
641
+ @logger.debug "Look for #{want.ai} in #{[path]} with #{exts} extensions"
642
+ if exts.nil?
643
+ exts = @config[:precedence]
644
+ end
645
+ file_regex=%r/^(\S+)\.(#{exts.join('|')})$/
646
+ if exts.empty?
647
+ file_regex=%r/^(\S+)()$/
648
+ exts=['']
649
+ end
650
+ regex = "^(#{want.join('|')})$"
651
+ set = {}
652
+ chef = @config[:repo_path]
653
+ raise ChopError.new "Oops! Where is the '#{chef}' directory? Also check cookbook path '#{@config[:cookbook_path]}'" unless File.directory?(chef)
654
+ abs = File.expand_path("#{chef}/#{path}")#.gsub(%r(^#{@chop_path}), '')
655
+ raise ChopError.new "Oops! Does 'chef/#{path}' directory exist?" unless File.directory?(abs)
656
+ Dir.glob("#{abs}/*").each{ |f|
657
+ match = File.basename(f).match(file_regex)
658
+ if match
659
+ name = match[1]
660
+ ext = match[2]
661
+ set[ext] = {} unless set[ext]
662
+ @logger.trace "#{name} =~ #{regex}"
663
+ set[ext][name] = f if name.match(regex)
664
+ end
665
+ }
666
+ @logger.debug "getPathSet set=#{set.ai}"
667
+ res = {}
668
+ # Iterate extension sets in increasing precedence order ...
669
+ # Survivor will be the most desireable version of the item
670
+ # i.e. the .rb environment, role, data bag, etc. will be preferred over the .json version
671
+ exts.each{ |e|
672
+ h = set[e]
673
+ if h
674
+ h.each{ |n,f|
675
+ @logger.warn "Ignoring #{File.basename(res[n])}" if res[n]
676
+ res[n] = f
677
+ }
678
+ else
679
+ @logger.warn "'#{e}' set is empty! (No #{path}/*.#{e} files found using precedence #{exts})"
680
+ end
681
+ }
682
+ set = res
683
+ set
684
+ end
685
+
686
+ # --------------------------------------------------------------------------------
687
+ def todo(msg)
688
+
689
+ # Regular expression used to parse out caller information
690
+ #
691
+ # * $1 == filename
692
+ # * $2 == line number
693
+ # * $3 == method name (might be nil)
694
+ caller_rgxp = %r/([-\.\/\(\)\w]+):(\d+)(?::in `(\w+)')?/o
695
+ #CALLER_INDEX = 2
696
+ caller_index = ((defined? JRUBY_VERSION and JRUBY_VERSION[%r/^1.6/]) or (defined? RUBY_ENGINE and RUBY_ENGINE[%r/^rbx/i])) ? 1 : 2
697
+ stack = Kernel.caller[caller_index]
698
+ return if stack.nil?
699
+
700
+ match = caller_rgxp.match(stack)
701
+ file = match[1]
702
+ line = Integer(match[2])
703
+ modl = match[3] unless match[3].nil?
704
+
705
+ unless @TODO[line]
706
+ le = ::Logging::LogEvent.new(@logger, ::Logging::LEVELS['todo'], msg, true)
707
+ @logger.logEvent(le) unless @TODO[line]
708
+ @TODO[line] = true
709
+ end
710
+ end
711
+
712
+ # --------------------------------------------------------------------------------
713
+ def matches(string, criterium)
714
+ if criterium =~ %r/[\.\+\*\(\)\|\,\{\}\?\[\]\^\$]|\\[sSdDAzwWb]/
715
+ string.match(%r/#{criterium}/)
716
+ else
717
+ string == criterium
718
+ end
719
+ end
720
+
721
+ # --------------------------------------------------------------------------------
722
+ def execute(cmd,lead)
723
+ exit 1 if stop
724
+ print lead if @logger.level < 4
725
+ system cmd
726
+ end
727
+
728
+ # --------------------------------------------------------------------------------
729
+ def watch_for_break
730
+ Thread.new do
731
+ s=$stdin.read
732
+ #puts "Consumed existing input: '#{$stdin.read}'"
733
+ loop do
734
+ s = gets.chomp
735
+ if s != ""
736
+ puts "Interrupted! You entered '#{s}'"
737
+ @stop = true
738
+ exit
739
+ end
740
+ end
741
+ end
742
+ end
743
+
744
+ # --------------------------------------------------------------------------------
745
+ def callCmdProc(cmdp, a, b, c)
746
+ ret = nil
747
+ begin
748
+ if cmdp.is_a?(String)
749
+ ret = cmdp
750
+ elsif cmdp.is_a?(Proc)
751
+ ret = cmdp.call(a, b, c)
752
+ else
753
+ raise ChopInternalError.new("'#{cmdp}' is not a Proc, Lambda or String!")
754
+ end
755
+ rescue ChopInternalError => e
756
+ raise e
757
+ rescue => e
758
+ @logger.fatal "#{e.class.name} #{e.message}"
759
+ raise ChopError.new("#{e.class.name} #{e.message}")
760
+ end
761
+ ret
762
+ end
763
+
764
+ # --------------------------------------------------------------------------------
765
+ def databags(options=nil,exts=nil)
766
+ options = @config unless options
767
+ unless @databags
768
+
769
+ want = Hash.new
770
+ options[:databags].each{ |b|
771
+ match = b.match(%r/^(.*):(.*)$/)
772
+ if match
773
+ want[match[1]] = parseOptionString(match[2],';')
774
+ end
775
+ }
776
+ @logger.debug want.ai
777
+
778
+ chef = options[:repo_path]
779
+ raise ChopError.new "Oops! Where is the '#{chef}' directory? Also check cookbook path '#{options[:cookbook_path]}'" unless File.directory?(chef)
780
+
781
+ @databags={}
782
+ Dir.glob("#{chef}/data_bags/*").each{ |d|
783
+ if File.directory?(d)
784
+ name = File.basename(d)
785
+ regex = "^(#{want.keys.join('|')})"
786
+ match = matches(name,regex)
787
+ if match
788
+ @databags[name] = getPathSet(want[name], "data_bags/#{name}", exts)
789
+ @logger.info "Data bags list: #{@databags[name].values.map{|f| "#{name}/#{File.basename(f)}" }}"
790
+ end
791
+ end
792
+ }
793
+ end
794
+ @databags
795
+ end
796
+
797
+ # --------------------------------------------------------------------------------
798
+ def roles(options=nil,exts=nil)
799
+ options = @config unless options
800
+ unless @roles
801
+ @roles = getPathSet(options[:roles], 'roles', exts)
802
+ @logger.info "Roles list: #{@roles.values.map{|f| File.basename(f)}.ai}"
803
+ end
804
+ @roles
805
+ end
806
+
807
+ # --------------------------------------------------------------------------------
808
+ def environments(options=nil,exts=nil)
809
+ options = @config unless options
810
+ unless @environments
811
+ @environments = getPathSet(options[:envs], 'environments', exts)
812
+ @logger.info "Environments list: #{@environments.values.map{|f| File.basename(f)}.ai}"
813
+ end
814
+ @environments
815
+ end
816
+
817
+ end
818
+ end
819
+ end
820
+
821
+