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

Sign up to get free protection for your applications and to get access to all the features.
@@ -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