chef 0.9.8.beta.1 → 0.9.8.beta.2

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.
@@ -5,29 +5,33 @@
5
5
  # Licensed under the Apache License, Version 2.0 (the "License");
6
6
  # you may not use this file except in compliance with the License.
7
7
  # You may obtain a copy of the License at
8
- #
8
+ #
9
9
  # http://www.apache.org/licenses/LICENSE-2.0
10
- #
10
+ #
11
11
  # Unless required by applicable law or agreed to in writing, software
12
12
  # distributed under the License is distributed on an "AS IS" BASIS,
13
13
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
14
  # See the License for the specific language governing permissions and
15
15
  # limitations under the License.
16
16
  #
17
+
18
+ require 'tempfile'
17
19
  require 'chef/recipe'
18
20
  require 'fileutils'
19
21
  require 'chef/version'
20
22
  require 'chef/shef/shef_session'
23
+ require 'chef/shef/model_wrapper'
24
+ require 'chef/shef/shef_rest'
21
25
 
22
26
  module Shef
23
27
  module Extensions
24
-
25
- # Extensions to be included in object. These are methods that have to be
26
- # defined on object but are not part of the user interface. Methods that
27
- # are part of the user interface should have help text defined with the
28
- # +desc+ macro, and need to be defined directly on Object in ext.rb
29
- module Object
30
-
28
+
29
+ Help = Struct.new(:cmd, :desc, :explanation)
30
+
31
+ # Extensions to be included in every 'main' object in shef. These objects
32
+ # are extended with this module.
33
+ module ObjectCoreExtensions
34
+
31
35
  def ensure_session_select_defined
32
36
  # irb breaks if you prematurely define IRB::JobMangager
33
37
  # so these methods need to be defined at the latest possible time.
@@ -57,72 +61,86 @@ module Shef
57
61
  irb(context_obj)
58
62
  end
59
63
  end
60
-
61
- def help_banner(title=nil)
64
+
65
+ def help_banner
62
66
  banner = []
63
67
  banner << ""
64
- banner << title if title
68
+ banner << "Shef Help"
65
69
  banner << "".ljust(80, "=")
66
70
  banner << "| " + "Command".ljust(25) + "| " + "Description"
67
71
  banner << "".ljust(80, "=")
68
72
 
69
- self.class.all_help_descriptions.each do |cmd, description|
70
- banner << "| " + cmd.ljust(25) + "| " + description
73
+ self.all_help_descriptions.each do |help_text|
74
+ banner << "| " + help_text.cmd.ljust(25) + "| " + help_text.desc
71
75
  end
72
76
  banner << "".ljust(80, "=")
73
77
  banner << "\n"
78
+ banner << "Use help(:command) to get detailed help with individual commands"
79
+ banner << "\n"
74
80
  banner.join("\n")
75
81
  end
76
-
82
+
83
+ def explain_command(method_name)
84
+ help = self.all_help_descriptions.find { |h| h.cmd.to_s == method_name.to_s }
85
+ if help
86
+ puts ""
87
+ puts "Command: #{method_name}"
88
+ puts "".ljust(80, "=")
89
+ puts help.explanation || help.desc
90
+ puts "".ljust(80, "=")
91
+ puts ""
92
+ else
93
+ puts ""
94
+ puts "command #{method_name} not found or no help available"
95
+ puts ""
96
+ end
97
+ end
98
+
77
99
  # helpfully returns +:on+ so we can have sugary syntax like `tracing on'
78
100
  def on
79
101
  :on
80
102
  end
81
-
103
+
82
104
  # returns +:off+ so you can just do `tracing off'
83
105
  def off
84
106
  :off
85
107
  end
86
-
87
- module ClassMethods
88
108
 
89
- def help_descriptions
90
- @help_descriptions ||= []
91
- end
92
-
93
- def all_help_descriptions
94
- if (sc = superclass) && superclass.respond_to?(:help_descriptions)
95
- help_descriptions + sc.help_descriptions
96
- else
97
- help_descriptions
98
- end
99
- end
109
+ def help_descriptions
110
+ @help_descriptions ||= []
111
+ end
100
112
 
101
- def desc(help_text)
102
- @desc = help_text
103
- end
104
-
105
- def subcommands(subcommand_help={})
106
- @subcommand_help = subcommand_help
107
- end
113
+ def all_help_descriptions
114
+ help_descriptions
115
+ end
108
116
 
109
- def method_added(mname)
110
- if @desc
111
- help_descriptions << [mname.to_s, @desc.to_s]
112
- @desc = nil
113
- end
114
- if @subcommand_help
115
- @subcommand_help.each do |subcommand, text|
116
- help_descriptions << ["#{mname}.#{subcommand}", text.to_s]
117
- end
117
+ def desc(help_text)
118
+ @desc = help_text
119
+ end
120
+
121
+ def explain(explain_text)
122
+ @explain = explain_text
123
+ end
124
+
125
+ def subcommands(subcommand_help={})
126
+ @subcommand_help = subcommand_help
127
+ end
128
+
129
+ def singleton_method_added(mname)
130
+ if @desc
131
+ help_descriptions << Help.new(mname.to_s, @desc.to_s, @explain)
132
+ @desc, @explain = nil, nil
133
+ end
134
+ if @subcommand_help
135
+ @subcommand_help.each do |subcommand, text|
136
+ help_descriptions << Help.new("#{mname}.#{subcommand}", text.to_s, nil)
118
137
  end
119
- @subcommand_help = {}
120
138
  end
121
-
139
+ @subcommand_help = {}
122
140
  end
123
-
141
+
124
142
  end
125
-
143
+
126
144
  module String
127
145
  def on_off_to_bool
128
146
  case self
@@ -135,33 +153,396 @@ module Shef
135
153
  end
136
154
  end
137
155
  end
138
-
156
+
139
157
  module Symbol
140
158
  def on_off_to_bool
141
159
  self.to_s.on_off_to_bool
142
160
  end
143
161
  end
144
-
162
+
145
163
  module TrueClass
146
164
  def to_on_off_str
147
165
  "on"
148
166
  end
149
-
167
+
150
168
  def on_off_to_bool
151
169
  self
152
170
  end
153
171
  end
154
-
172
+
155
173
  module FalseClass
156
174
  def to_on_off_str
157
175
  "off"
158
176
  end
159
-
177
+
160
178
  def on_off_to_bool
161
179
  self
162
180
  end
163
181
  end
164
-
182
+
183
+ # Methods that have associated help text need to be dynamically added
184
+ # to the main irb objects, so we define them in a proc and later
185
+ # instance_eval the proc in the object.
186
+ ObjectUIExtensions = Proc.new do
187
+ extend Shef::Extensions::ObjectCoreExtensions
188
+
189
+ desc "prints this help message"
190
+ explain(<<-E)
191
+ ## SUMMARY ##
192
+ When called with no argument, +help+ prints a table of all shef commands. When
193
+ called with an argument COMMAND, +help+ prints a detailed explanation of the
194
+ command if available, or the description if no explanation is available.
195
+ E
196
+ def help(commmand=nil)
197
+ if commmand
198
+ explain_command(commmand)
199
+ else
200
+ puts help_banner
201
+ end
202
+ :ucanhaz_halp
203
+ end
204
+ alias :halp :help
205
+
206
+ desc "prints information about chef"
207
+ def version
208
+ puts "This is shef, the Chef shell.\n" +
209
+ " Chef Version: #{::Chef::VERSION}\n" +
210
+ " http://www.opscode.com/chef\n" +
211
+ " http://wiki.opscode.com/display/chef/Home"
212
+ :ucanhaz_automation
213
+ end
214
+ alias :shef :version
215
+
216
+ desc "switch to recipe mode"
217
+ def recipe
218
+ find_or_create_session_for Shef.session.recipe
219
+ :recipe
220
+ end
221
+
222
+ desc "switch to attributes mode"
223
+ def attributes
224
+ find_or_create_session_for Shef.session.node
225
+ :attributes
226
+ end
227
+
228
+ desc "returns the current node (i.e., this host)"
229
+ def node
230
+ Shef.session.node
231
+ end
232
+
233
+ desc "pretty print the node's attributes"
234
+ def ohai(key=nil)
235
+ pp(key ? node.attribute[key] : node.attribute)
236
+ end
237
+
238
+ desc "run chef using the current recipe"
239
+ def run_chef
240
+ Chef::Log.level = :debug
241
+ session = Shef.session
242
+ runrun = Chef::Runner.new(session.run_context).converge
243
+ Chef::Log.level = :info
244
+ runrun
245
+ end
246
+
247
+ desc "returns an object to control a paused chef run"
248
+ subcommands :resume => "resume the chef run",
249
+ :step => "run only the next resource",
250
+ :skip_back => "move back in the run list",
251
+ :skip_forward => "move forward in the run list"
252
+ def chef_run
253
+ Shef.session.resource_collection.iterator
254
+ end
255
+
256
+ desc "resets the current recipe"
257
+ def reset
258
+ Shef.session.reset!
259
+ end
260
+
261
+ desc "assume the identity of another node."
262
+ def become_node(node_name)
263
+ Shef::DoppelGangerSession.instance.assume_identity(node_name)
264
+ :doppelganger
265
+ end
266
+ alias :doppelganger :become_node
267
+
268
+ desc "turns printout of return values on or off"
269
+ def echo(on_or_off)
270
+ conf.echo = on_or_off.on_off_to_bool
271
+ end
272
+
273
+ desc "says if echo is on or off"
274
+ def echo?
275
+ puts "echo is #{conf.echo.to_on_off_str}"
276
+ end
277
+
278
+ desc "turns on or off tracing of execution. *verbose*"
279
+ def tracing(on_or_off)
280
+ conf.use_tracer = on_or_off.on_off_to_bool
281
+ tracing?
282
+ end
283
+ alias :trace :tracing
284
+
285
+ desc "says if tracing is on or off"
286
+ def tracing?
287
+ puts "tracing is #{conf.use_tracer.to_on_off_str}"
288
+ end
289
+ alias :trace? :tracing?
290
+
291
+ desc "simple ls style command"
292
+ def ls(directory)
293
+ Dir.entries(directory)
294
+ end
295
+ end
296
+
297
+ RESTApiExtensions = Proc.new do
298
+ desc "edit an object in your EDITOR"
299
+ explain(<<-E)
300
+ ## SUMMARY ##
301
+ +edit(object)+ allows you to edit any object that can be converted to JSON.
302
+ When finished editing, this method will return the edited object:
303
+
304
+ new_node = edit(existing_node)
305
+
306
+ ## EDITOR SELECTION ##
307
+ Shef looks for an editor using the following logic
308
+ 1. Looks for an EDITOR set by Shef.editor = "EDITOR"
309
+ 2. Looks for an EDITOR configured in your shef config file
310
+ 3. Uses the value of the EDITOR environment variable
311
+ E
312
+ def edit(object)
313
+ unless Shef.editor
314
+ puts "Please set your editor with Shef.editor = \"vim|emacs|mate|ed\""
315
+ return :failburger
316
+ end
317
+
318
+ filename = "shef-edit-#{object.class.name}-"
319
+ if object.respond_to?(:name)
320
+ filename += object.name
321
+ elsif object.respond_to?(:id)
322
+ filename += object.id
323
+ end
324
+
325
+ edited_data = Tempfile.open([filename, ".js"]) do |tempfile|
326
+ tempfile.sync = true
327
+ tempfile.puts object.to_json
328
+ system("#{Shef.editor.to_s} #{tempfile.path}")
329
+ tempfile.rewind
330
+ tempfile.read
331
+ end
332
+
333
+ JSON.parse(edited_data)
334
+ end
335
+
336
+ desc "Find and edit API clients"
337
+ explain(<<-E)
338
+ ## SUMMARY ##
339
+ +clients+ allows you to query you chef server for information about your api
340
+ clients.
341
+
342
+ ## LIST ALL CLIENTS ##
343
+ To see all clients on the system, use
344
+
345
+ clients.all #=> [<Chef::ApiClient...>, ...]
346
+
347
+ If the output from all is too verbose, or you're only interested in a specific
348
+ value from each of the objects, you can give a code block to +all+:
349
+
350
+ clients.all { |client| client.name } #=> [CLIENT1_NAME, CLIENT2_NAME, ...]
351
+
352
+ ## SHOW ONE CLIENT ##
353
+ To see a specific client, use
354
+
355
+ clients.show(CLIENT_NAME)
356
+
357
+ ## SEARCH FOR CLIENTS ##
358
+ You can also search for clients using +find+ or +search+. You can use the
359
+ familiar string search syntax:
360
+
361
+ clients.search("KEY:VALUE")
362
+
363
+ Just as the +all+ subcommand, the +search+ subcommand can use a code block to
364
+ filter or transform the information returned from the search:
365
+
366
+ clients.search("KEY:VALUE") { |c| c.name }
367
+
368
+ You can also use a Hash based syntax, multiple search conditions will be
369
+ joined with AND.
370
+
371
+ clients.find :KEY => :VALUE, :KEY2 => :VALUE2, ...
372
+
373
+ ## BULK-EDIT CLIENTS ##
374
+ **BE CAREFUL, THIS IS DESTRUCTIVE**
375
+ You can bulk edit API Clients using the +transform+ subcommand, which requires
376
+ a code block. Each client will be saved after the code block is run. If the
377
+ code block returns +nil+ or +false+, that client will be skipped:
378
+
379
+ clients.transform("*:*") do |client|
380
+ if client.name =~ /borat/i
381
+ client.admin(false)
382
+ true
383
+ else
384
+ nil
385
+ end
386
+ end
387
+
388
+ This will strip the admin privileges from any client named after borat.
389
+ E
390
+ subcommands :all => "list all api clients",
391
+ :show => "load an api client by name",
392
+ :search => "search for API clients",
393
+ :transform => "edit all api clients via a code block and save them"
394
+ def clients
395
+ @clients ||= Shef::ModelWrapper.new(Chef::ApiClient, :client)
396
+ end
397
+
398
+ desc "Find and edit cookbooks"
399
+ subcommands :all => "list all cookbooks",
400
+ :show => "load a cookbook by name",
401
+ :transform => "edit all cookbooks via a code block and save them"
402
+ def cookbooks
403
+ @cookbooks ||= Shef::ModelWrapper.new(Chef::CookbookVersion)
404
+ end
405
+
406
+ desc "Find and edit nodes via the API"
407
+ explain(<<-E)
408
+ ## SUMMARY ##
409
+ +nodes+ Allows you to query your chef server for information about your nodes.
410
+
411
+ ## LIST ALL NODES ##
412
+ You can list all nodes using +all+ or +list+
413
+
414
+ nodes.all #=> [<Chef::Node...>, <Chef::Node...>, ...]
415
+
416
+ To limit the information returned for each node, pass a code block to the +all+
417
+ subcommand:
418
+
419
+ nodes.all { |node| node.name } #=> [NODE1_NAME, NODE2_NAME, ...]
420
+
421
+ ## SHOW ONE NODE ##
422
+ You can show the data for a single node using the +show+ subcommand:
423
+
424
+ nodes.show("NODE_NAME") => <Chef::Node @name="NODE_NAME" ...>
425
+
426
+ ## SEARCH FOR NODES ##
427
+ You can search for nodes using the +search+ or +find+ subcommands:
428
+
429
+ nodes.find(:name => "app*") #=> [<Chef::Node @name="app1.example.com" ...>, ...]
430
+
431
+ Similarly to +all+, you can pass a code block to limit or transform the
432
+ information returned:
433
+
434
+ nodes.find(:name => "app#") { |node| node.ec2 }
435
+
436
+ ## BULK EDIT NODES ##
437
+ **BE CAREFUL, THIS OPERATION IS DESTRUCTIVE**
438
+
439
+ Bulk edit nodes by passing a code block to the +transform+ or +bulk_edit+
440
+ subcommand. The block will be applied to each matching node, and then the node
441
+ will be saved. If the block returns +nil+ or +false+, that node will be
442
+ skipped.
443
+
444
+ nodes.transform do |node|
445
+ if node.fqdn =~ /.*\\.preprod\\.example\\.com/
446
+ node.set[:environment] = "preprod"
447
+ end
448
+ end
449
+
450
+ This will assign the attribute to every node with a FQDN matching the regex.
451
+ E
452
+ subcommands :all => "list all nodes",
453
+ :show => "load a node by name",
454
+ :search => "search for nodes",
455
+ :transform => "edit all nodes via a code block and save them"
456
+ def nodes
457
+ @nodes ||= Shef::ModelWrapper.new(Chef::Node)
458
+ end
459
+
460
+ desc "Find and edit roles via the API"
461
+ explain(<<-E)
462
+ ## SUMMARY ##
463
+ +roles+ allows you to query and edit roles on your Chef server.
464
+
465
+ ## SUBCOMMANDS ##
466
+ * all (list)
467
+ * show (load)
468
+ * search (find)
469
+ * transform (bulk_edit)
470
+
471
+ ## SEE ALSO ##
472
+ See the help for +nodes+ for more information about the subcommands.
473
+ E
474
+ subcommands :all => "list all roles",
475
+ :show => "load a role by name",
476
+ :search => "search for roles",
477
+ :transform => "edit all roles via a code block and save them"
478
+ def roles
479
+ @roles ||= Shef::ModelWrapper.new(Chef::Role)
480
+ end
481
+
482
+ desc "Find and edit +databag_name+ via the api"
483
+ explain(<<-E)
484
+ ## SUMMARY ##
485
+ +databags(DATABAG_NAME)+ allows you to query and edit data bag items on your
486
+ Chef server. Unlike other commands for working with data on the server,
487
+ +databags+ requires the databag name as an argument, for example:
488
+ databags(:users).all
489
+
490
+ ## SUBCOMMANDS ##
491
+ * all (list)
492
+ * show (load)
493
+ * search (find)
494
+ * transform (bulk_edit)
495
+
496
+ ## SEE ALSO ##
497
+ See the help for +nodes+ for more information about the subcommands.
498
+
499
+ E
500
+ subcommands :all => "list all items in the data bag",
501
+ :show => "load a data bag item by id",
502
+ :search => "search for items in the data bag",
503
+ :transform => "edit all items via a code block and save them"
504
+ def databags(databag_name)
505
+ @named_databags_wrappers ||= {}
506
+ @named_databags_wrappers[databag_name] ||= Shef::NamedDataBagWrapper.new(databag_name)
507
+ end
508
+
509
+ desc "A REST Client configured to authenticate with the API"
510
+ def api
511
+ @rest = Shef::ShefREST.new(Chef::Config[:chef_server_url])
512
+ end
513
+
514
+ end
515
+
516
+ RecipeUIExtensions = Proc.new do
517
+ alias :original_resources :resources
518
+
519
+ desc "list all the resources on the current recipe"
520
+ def resources(*args)
521
+ if args.empty?
522
+ pp run_context.resource_collection.instance_variable_get(:@resources_by_name).keys
523
+ else
524
+ pp resources = original_resources(*args)
525
+ resources
526
+ end
527
+ end
528
+ end
529
+
530
+ def self.extend_context_object(obj)
531
+ obj.instance_eval(&ObjectUIExtensions)
532
+ obj.instance_eval(&RESTApiExtensions)
533
+ obj.extend(FileUtils)
534
+ obj.extend(Chef::Mixin::Language)
535
+ end
536
+
537
+ def self.extend_context_node(node_obj)
538
+ node_obj.instance_eval(&ObjectUIExtensions)
539
+ end
540
+
541
+ def self.extend_context_recipe(recipe_obj)
542
+ recipe_obj.instance_eval(&ObjectUIExtensions)
543
+ recipe_obj.instance_eval(&RecipeUIExtensions)
544
+ end
545
+
165
546
  end
166
547
  end
167
548
 
@@ -181,129 +562,3 @@ class FalseClass
181
562
  include Shef::Extensions::FalseClass
182
563
  end
183
564
 
184
- class Object
185
- extend Shef::Extensions::Object::ClassMethods
186
- include Shef::Extensions::Object
187
- include FileUtils
188
-
189
- desc "prints this help message"
190
- def shef_help(title="Help: Shef")
191
- #puts Shef::Extensions::Object.help_banner("Shef Help")
192
- puts help_banner(title)
193
- :ucanhaz_halp
194
- end
195
- alias :halp :shef_help
196
-
197
- desc "prints information about chef"
198
- def version
199
- puts "This is shef, the Chef shell.\n" +
200
- " Chef Version: #{::Chef::VERSION}\n" +
201
- " http://www.opscode.com/chef\n" +
202
- " http://wiki.opscode.com/display/chef/Home"
203
- :ucanhaz_automation
204
- end
205
- alias :shef :version
206
-
207
- desc "switch to recipe mode"
208
- def recipe
209
- find_or_create_session_for Shef.session.recipe
210
- :recipe
211
- end
212
-
213
- desc "switch to attributes mode"
214
- def attributes
215
- find_or_create_session_for Shef.session.node
216
- :attributes
217
- end
218
-
219
- desc "returns the current node (i.e., this host)"
220
- def node
221
- Shef.session.node
222
- end
223
-
224
- desc "pretty print the node's attributes"
225
- def ohai(key=nil)
226
- pp(key ? node.attribute[key] : node.attribute)
227
- end
228
-
229
- desc "run chef using the current recipe"
230
- def run_chef
231
- Chef::Log.level = :debug
232
- session = Shef.session
233
- runrun = Chef::Runner.new(session.run_context).converge
234
- Chef::Log.level = :info
235
- runrun
236
- end
237
-
238
- desc "returns an object to control a paused chef run"
239
- subcommands :resume => "resume the chef run",
240
- :step => "run only the next resource",
241
- :skip_back => "move back in the run list",
242
- :skip_forward => "move forward in the run list"
243
- def chef_run
244
- Shef.session.resource_collection.iterator
245
- end
246
-
247
- desc "resets the current recipe"
248
- def reset
249
- Shef.session.reset!
250
- end
251
-
252
- desc "turns printout of return values on or off"
253
- def echo(on_or_off)
254
- conf.echo = on_or_off.on_off_to_bool
255
- end
256
-
257
- desc "says if echo is on or off"
258
- def echo?
259
- puts "echo is #{conf.echo.to_on_off_str}"
260
- end
261
-
262
- desc "turns on or off tracing of execution. *verbose*"
263
- def tracing(on_or_off)
264
- conf.use_tracer = on_or_off.on_off_to_bool
265
- tracing?
266
- end
267
- alias :trace :tracing
268
-
269
- desc "says if tracing is on or off"
270
- def tracing?
271
- puts "tracing is #{conf.use_tracer.to_on_off_str}"
272
- end
273
- alias :trace? :tracing?
274
-
275
- desc "simple ls style command"
276
- def ls(directory)
277
- Dir.entries(directory)
278
- end
279
-
280
- end
281
-
282
- class String
283
- undef_method :version
284
- end
285
-
286
- class NilClass
287
- undef_method :version
288
- end
289
-
290
- class Chef
291
- class Recipe
292
-
293
- def shef_help
294
- super("Help: Shef/Recipe")
295
- end
296
-
297
- alias :original_resources :resources
298
-
299
- desc "list all the resources on the current recipe"
300
- def resources(*args)
301
- if args.empty?
302
- pp run_context.resource_collection.instance_variable_get(:@resources_by_name).keys
303
- else
304
- pp resources = original_resources(*args)
305
- resources
306
- end
307
- end
308
- end
309
- end