knife-chop 0.2.6

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.
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
+