ironfan 4.0.9 → 4.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ # v4.1.0: several bug-fixes and code cleanup
2
+ * Splat the args to DSL::Volume.snapshot_id so we can call it properly (fixes #161)
3
+ * cloud(:ec2) lets you declare bitness (fixes #147)
4
+ * Better logging; errors within Ironfan.parallel don't crash the world (pull #167)
5
+ * Like any good hitman, knife cluster kill should 'take care of' errant clients (pull #168)
6
+
1
7
  # v4.0.9:
2
8
  * Making bootstrap work again (fixes #159)
3
9
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 4.0.9
1
+ 4.1.0
data/ironfan.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "ironfan"
8
- s.version = "4.0.9"
8
+ s.version = "4.1.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Infochimps"]
12
- s.date = "2012-09-20"
12
+ s.date = "2012-09-24"
13
13
  s.description = "Ironfan allows you to orchestrate not just systems but clusters of machines. It includes a powerful layer on top of knife and a collection of cloud cookbooks."
14
14
  s.email = "coders@infochimps.com"
15
15
  s.extra_rdoc_files = [
@@ -79,10 +79,12 @@ class Chef
79
79
  ui.info("")
80
80
  section("Launching computers", :green)
81
81
  display(target)
82
- target.launch
82
+ launched = target.launch
83
83
 
84
84
  # As each server finishes, configure it
85
- Ironfan.parallel(target.values) do |computer|
85
+ Ironfan.parallel(launched) do |computer|
86
+ if (computer.is_a?(Exception)) then ui.warn "Error launching #{computer.inspect}; skipping after-launch tasks."; next; end
87
+ Ironfan.step(computer.name, 'launching', :white)
86
88
  perform_after_launch_tasks(computer)
87
89
  end
88
90
  # progressbar_for_threads(watcher_threads)
@@ -91,12 +93,14 @@ class Chef
91
93
  end
92
94
 
93
95
  def perform_after_launch_tasks(computer)
96
+ Ironfan.step(computer.name, 'waiting for ready', :white)
94
97
  # Wait for machine creation on amazon side
95
98
  # server.fog_server.wait_for{ ready? }
96
99
  computer.machine.wait_for{ ready? }
97
100
 
98
101
  # Try SSH
99
102
  unless config[:dry_run]
103
+ Ironfan.step(computer.name, 'trying ssh', :white)
100
104
  # nil until tcp_test_ssh(server.fog_server.dns_name){ sleep @initial_sleep_delay ||= 10 }
101
105
  nil until tcp_test_ssh(computer.machine.dns_name){ sleep @initial_sleep_delay ||= 10 }
102
106
  end
@@ -107,11 +111,14 @@ class Chef
107
111
 
108
112
  # Attach volumes, etc
109
113
  # server.sync_to_cloud
114
+ Ironfan.step(computer.name, 'final provisioning', :white)
110
115
  computer.save
111
116
 
112
117
  # Run Bootstrap
113
- Chef::Log.warn "UNTESTED --bootstrap"
114
- run_bootstrap(computer) if config[:bootstrap]
118
+ if config[:bootstrap]
119
+ Chef::Log.warn "UNTESTED --bootstrap"
120
+ run_bootstrap(computer)
121
+ end
115
122
  end
116
123
 
117
124
  def tcp_test_ssh(hostname)
@@ -42,19 +42,48 @@ class Chef
42
42
  # Load the cluster/facet/slice/whatever
43
43
  target = get_slice(* @name_args)
44
44
 
45
+ dump_command_config
46
+ dump_chef_config
45
47
  #
46
- # Dump entire contents of objects if -VV flag given
47
- #
48
- if config[:verbosity] >= 2
49
- target.each do |computer|
50
- Chef::Log.debug( "Computer #{computer.name}: #{JSON.pretty_generate(computer.to_wire)}" )
51
- end
48
+ target.each do |computer|
49
+ dump_computer(computer)
52
50
  end
53
51
 
54
52
  # Display same
55
53
  display(target)
54
+ end
55
+
56
+ protected
57
+
58
+ def dump_computer(computer)
59
+ with_verbosity 1 do
60
+ dump("Computer #{computer.name} (#{computer.class})", computer.to_wire)
61
+ end
62
+ end
56
63
 
64
+ def dump_command_config
65
+ with_verbosity 2 do
66
+ Chef::Log.info( ["", "*"*50, "", "Command Config", ""].join("\n") )
67
+ dump("Command config", self.config)
68
+ end
57
69
  end
70
+
71
+ def dump_chef_config
72
+ with_verbosity 2 do
73
+ chef_config_hash = Hash[Chef::Config.keys.map{|key| [key, Chef::Config[key]]}]
74
+ dump("Chef Config", chef_config_hash)
75
+ end
76
+ end
77
+
78
+ def dump(title, hsh)
79
+ Chef::Log.info( ["", "*"*50, "", "#{title}: ", ""].join("\n") )
80
+ Chef::Log.info( MultiJson.dump(hsh, pretty: true ) )
81
+ end
82
+
83
+ def with_verbosity(num)
84
+ yield if config[:verbosity] >= num
85
+ end
86
+
58
87
  end
59
88
  end
60
89
  end
@@ -37,9 +37,13 @@ module Ironfan
37
37
  ui.warn("Please specify server slices joined by dashes and not separate args:\n\n knife cluster #{sub_command} #{slice_string}\n\n")
38
38
  end
39
39
  cluster_name, facet_name, slice_indexes = slice_string.split(/[\s\-]/, 3)
40
- ui.info("Inventorying servers in #{predicate_str(cluster_name, facet_name, slice_indexes)}")
41
- cluster = Ironfan.load_cluster(cluster_name)
40
+ desc = predicate_str(cluster_name, facet_name, slice_indexes)
41
+ #
42
+ ui.info("Inventorying servers in #{desc}")
43
+ cluster = Ironfan.load_cluster(cluster_name)
42
44
  computers = broker.discover! cluster
45
+ Chef::Log.info("Inventoried #{computers.size} computers")
46
+ #
43
47
  computers.slice(facet_name, slice_indexes)
44
48
  end
45
49
 
@@ -80,7 +84,7 @@ module Ironfan
80
84
  def display(target, display_style=nil, &block)
81
85
  display_style ||= (config[:verbosity] == 0 ? :default : :expanded)
82
86
  # target.display(ui, display_style, &block)
83
- broker.display(target,display_style)
87
+ broker.display(target, display_style)
84
88
  end
85
89
 
86
90
  #
@@ -115,25 +119,24 @@ module Ironfan
115
119
  end
116
120
 
117
121
  def bootstrapper(computer)
118
- server = computer.server
119
- cloud = server.selected_cloud
120
- hostname = computer.machine.dns_name
121
-
122
+ server = computer.server
123
+ hostname = computer.dns_name
124
+ #
122
125
  bootstrap = Chef::Knife::Bootstrap.new
123
126
  bootstrap.config.merge!(config)
124
-
127
+ #
125
128
  bootstrap.name_args = [ hostname ]
126
129
  bootstrap.config[:computer] = computer
127
130
  bootstrap.config[:server] = server
128
131
  bootstrap.config[:run_list] = server.run_list
129
- bootstrap.config[:ssh_user] = config[:ssh_user] || cloud.ssh_user
132
+ bootstrap.config[:ssh_user] = config[:ssh_user] || computer.ssh_user
130
133
  bootstrap.config[:attribute] = config[:attribute]
131
134
  bootstrap.config[:identity_file] = config[:identity_file] || computer.ssh_identity_file
132
- bootstrap.config[:distro] = config[:distro] || cloud.bootstrap_distro
135
+ bootstrap.config[:distro] = config[:distro] || computer.bootstrap_distro
133
136
  bootstrap.config[:use_sudo] = true unless config[:use_sudo] == false
134
137
  bootstrap.config[:chef_node_name] = server.fullname
135
138
  bootstrap.config[:client_key] = ( computer.client.private_key rescue nil )
136
-
139
+ #
137
140
  bootstrap
138
141
  end
139
142
 
@@ -143,14 +146,11 @@ module Ironfan
143
146
  ui.info "Skipping: bootstrap #{computer.name} with #{JSON.pretty_generate(bs.config)}"
144
147
  return
145
148
  end
146
- begin
149
+ #
150
+ Ironfan.step(computer.name, "Running bootstrap")
151
+ Chef::Log.info("Bootstrapping:\n Computer #{computer}\n Bootstrap config #{bs.config}")
152
+ Ironfan.safely([computer, bs.config].inspect) do
147
153
  bs.run
148
- rescue StandardError => e
149
- ui.warn e
150
- ui.warn e.backtrace
151
- ui.warn ""
152
- ui.warn computer.inspect
153
- ui.warn ""
154
154
  end
155
155
  end
156
156
 
@@ -2,16 +2,16 @@ module Ironfan
2
2
  class Broker
3
3
 
4
4
  class Computer < Builder
5
+
6
+ field :server, Ironfan::Dsl::Server
5
7
  collection :resources, Whatever
6
8
  collection :drives, Ironfan::Broker::Drive
7
9
  collection :providers, Ironfan::Provider
8
- field :server, Ironfan::Dsl::Server
9
- delegate :[],:[]=,:include?,:delete,
10
- :to => :resources
10
+ delegate :[], :[]=, :include?, :delete, :to => :resources
11
11
 
12
12
  # Only used for bogus servers
13
13
  field :name, String
14
- field :bogus, Array, :default => []
14
+ field :bogus, Array, :default => []
15
15
 
16
16
  def initialize(*args)
17
17
  super
@@ -61,14 +61,21 @@ module Ironfan
61
61
  target_resources = chosen_resources(options)
62
62
  resources.each do |res|
63
63
  next unless target_resources.include? res.class
64
- res.destroy unless res.shared?
64
+ descriptor = "#{res.class} named #{res.name}"
65
+ if res.shared?
66
+ Chef::Log.debug("Not killing shared resource #{descriptor}")
67
+ else
68
+ Ironfan.step(self.name, "Killing #{descriptor}")
69
+ res.destroy
70
+ end
65
71
  end
66
72
  end
67
73
 
68
74
  def launch
69
75
  ensure_dependencies
70
- providers[:iaas].machine_class.create! self
76
+ iaas_provider.machine_class.create! self
71
77
  save
78
+ self
72
79
  end
73
80
 
74
81
  def stop
@@ -87,6 +94,9 @@ module Ironfan
87
94
  chosen_resources(options).each {|res| res.save! self}
88
95
  end
89
96
 
97
+ def chef_provider ; providers[:chef] ; end
98
+ def iaas_provider ; providers[:iaas] ; end
99
+
90
100
  #
91
101
  # Utility
92
102
  #
@@ -105,7 +115,7 @@ module Ironfan
105
115
 
106
116
  def ensure_dependencies
107
117
  chosen_resources.each do |res|
108
- # ensure_resource res unless res < Ironfan::IaasProvider::Machine
118
+ # ensure_resource res unless res < Ironfan::IaasProvider::Machine
109
119
  res.create! self unless res < Ironfan::IaasProvider::Machine
110
120
  end
111
121
  end
@@ -126,7 +136,7 @@ module Ironfan
126
136
  # if type.multiple?
127
137
  # existing = resources[type.resource_id]
128
138
  # else
129
- #
139
+ #
130
140
  # end
131
141
  # end
132
142
 
@@ -165,21 +175,33 @@ module Ironfan
165
175
  def bogus
166
176
  resources.values.map(&:bogus).flatten
167
177
  end
178
+ def node
179
+ self[:node]
180
+ end
181
+ def node=(value)
182
+ self[:node]= value
183
+ end
184
+
185
+ #
186
+ # client
187
+ #
168
188
  def client
169
189
  self[:client]
170
190
  end
191
+ def private_key ; client? ? client.private_key : nil ; end
192
+
193
+ #
194
+ # Machine
195
+ #
171
196
  def machine
172
197
  self[:machine]
173
198
  end
174
199
  def machine=(value)
175
200
  self[:machine] = value
176
201
  end
177
- def node
178
- self[:node]
179
- end
180
- def node=(value)
181
- self[:node]= value
182
- end
202
+ def dns_name ; machine? ? machine.dns_name : nil ; end
203
+ def ssh_user ; (server && server.selected_cloud) ? server.selected_cloud.ssh_user : nil ; end
204
+ def bootstrap_distro ; (server && server.selected_cloud) ? server.selected_cloud.bootstrap_distro : nil ; end
183
205
 
184
206
  #
185
207
  # Status flags
@@ -197,7 +219,7 @@ module Ironfan
197
219
  not machine.nil?
198
220
  end
199
221
  def killable?
200
- not permanent? and (node? or created?)
222
+ not permanent? and (node? || client? || created?)
201
223
  end
202
224
  def launchable?
203
225
  not bogus? and not created?
@@ -220,14 +242,22 @@ module Ironfan
220
242
  machine? and machine.stopped?
221
243
  end
222
244
 
245
+ # @return [Boolean] true if machine is likely to be reachable by ssh
246
+ def sshable?
247
+ # if there's any hope of success, give it a try.
248
+ running? || node?
249
+ end
250
+
251
+ def to_s
252
+ "<#{self.class}(server=#{server}, resources=#{resources && resources.inspect_compact}, providers=#{providers && providers.inspect_compact})>"
253
+ end
223
254
  end
224
255
 
225
256
  class Computers < Gorillib::ModelCollection
226
- self.item_type = Computer
227
- self.key_method = :object_id
228
- delegate :first, :map, :any?,
229
- :to => :values
230
- attr_accessor :cluster
257
+ self.item_type = Computer
258
+ self.key_method = :object_id
259
+ delegate :first, :map, :any?, :to => :values
260
+ attr_accessor :cluster
231
261
 
232
262
  def initialize(*args)
233
263
  super
@@ -236,19 +266,19 @@ module Ironfan
236
266
  create_expected!
237
267
  end
238
268
 
239
- #
269
+ #
240
270
  # Discovery
241
271
  #
242
272
  def correlate
243
- values.each {|c| c.correlate }
273
+ values.each{|c| c.correlate }
244
274
  end
245
275
 
246
276
  def validate
247
277
  providers = values.map {|c| c.providers.values}.flatten
248
278
  computers = self
249
279
 
250
- values.each {|c| c.validate }
251
- providers.each {|p| p.validate computers }
280
+ values.each{|c| c.validate }
281
+ providers.each{|p| p.validate computers }
252
282
  end
253
283
 
254
284
  #
@@ -258,16 +288,16 @@ module Ironfan
258
288
  Ironfan.parallel(values) {|c| c.kill(options) }
259
289
  end
260
290
  def launch
261
- Ironfan.parallel(values) {|c| c. launch }
291
+ Ironfan.parallel(values) {|c| c.launch }
262
292
  end
263
293
  def save(options={})
264
- Ironfan.parallel(values) {|c| c. save(options) }
294
+ Ironfan.parallel(values) {|c| c.save(options) }
265
295
  end
266
296
  def start
267
- Ironfan.parallel(values) {|c| c. start }
297
+ Ironfan.parallel(values) {|c| c.start }
268
298
  end
269
299
  def stop
270
- Ironfan.parallel(values) {|c| c. stop }
300
+ Ironfan.parallel(values) {|c| c.stop }
271
301
  end
272
302
 
273
303
  #
@@ -307,6 +337,7 @@ module Ironfan
307
337
  end
308
338
  result
309
339
  end
340
+
310
341
  def build_slice_array(slice_indexes)
311
342
  return [] if slice_indexes.nil?
312
343
  raise "Bad slice_indexes: #{slice_indexes}" if slice_indexes =~ /[^0-9\.,]/
@@ -317,7 +348,11 @@ module Ironfan
317
348
  def joined_names
318
349
  values.map(&:name).join(", ").gsub(/, ([^,]*)$/, ' and \1')
319
350
  end
351
+
352
+ def to_s
353
+ "#{self.class}[#{values.map(&:name).join(",")}]"
354
+ end
320
355
  end
321
356
 
322
357
  end
323
- end
358
+ end
@@ -1,6 +1,6 @@
1
1
  # This module is intended to read in a cluster DSL description, and broker
2
2
  # out to the various cloud providers, to control instance life-cycle and
3
- # handle provider-specific amenities (SecurityGroup, Volume, etc.) for
3
+ # handle provider-specific amenities (SecurityGroup, Volume, etc.) for
4
4
  # them.
5
5
  module Ironfan
6
6
  def self.broker
@@ -10,15 +10,21 @@ module Ironfan
10
10
  class Broker < Builder
11
11
  # Take in a Dsl::Cluster, return Computers populated with
12
12
  # all discovered resources that correlate, plus bogus computers
13
- # corresponding to
13
+ # corresponding to
14
14
  def discover!(cluster)
15
15
  # Get fully resolved servers, and build Computers using them
16
16
  computers = Computers.new(:cluster => cluster.resolve)
17
- providers = computers.map{|c| c.providers.values}.flatten.uniq
18
-
19
- providers.each {|p| p.load cluster }
17
+ #
18
+ providers = computers.map{|c| c.providers.values }.flatten.uniq
19
+ providers.each do |provider|
20
+ Ironfan.step cluster.name, "Loading #{provider.handle}", :cyan
21
+ provider.load cluster
22
+ end
23
+ #
24
+ Ironfan.step cluster.name, "Reconciling DSL and provider information", :cyan
20
25
  computers.correlate
21
26
  computers.validate
27
+ #
22
28
  computers
23
29
  end
24
30
 
@@ -34,4 +40,4 @@ module Ironfan
34
40
 
35
41
  end
36
42
 
37
- end
43
+ end
@@ -2,7 +2,7 @@ module Ironfan
2
2
  def self.deprecated call, replacement=nil
3
3
  correction = ", " if replacement
4
4
  ui.error "The '#{call}' statement is deprecated#{correction} (in #{caller(2).first.inspect}). It looks like you are using an outdated DSL definition: please see https://github.com/infochimps-labs/ironfan/wiki/Upgrading-to-v4 for all the necessary upgrade steps."
5
- exit(1)
5
+ raise StandardError, "Deprecated call to #{call} - #{replacement}", caller
6
6
  end
7
7
 
8
8
  class Dsl
@@ -11,7 +11,7 @@ module Ironfan
11
11
  Ironfan.deprecated 'defaults'
12
12
  end
13
13
  end
14
-
14
+
15
15
  class Compute
16
16
  def cloud(provider=nil)
17
17
  if provider.nil?
@@ -8,38 +8,46 @@ module Ironfan
8
8
  class Ec2 < Cloud
9
9
  magic :availability_zones, Array, :default => ['us-east-1d']
10
10
  magic :backing, String, :default => 'ebs'
11
- magic :bootstrap_distro, String
11
+ magic :bits, Integer, :default => ->{ flavor_info[:bits] }
12
+ magic :bootstrap_distro, String, :default => ->{ image_info[:bootstrap_distro] }
12
13
  magic :chef_client_script, String
13
- magic :default_availability_zone, String, :default => ->{availability_zones.first}
14
+ magic :default_availability_zone, String, :default => ->{ availability_zones.first }
14
15
  magic :flavor, String, :default => 't1.micro'
15
- magic :image_name, String
16
16
  magic :image_id, String
17
+ magic :image_name, String
17
18
  magic :keypair, Whatever
18
- magic :mount_ephemerals, Hash, :default => {}
19
19
  magic :monitoring, String
20
+ magic :mount_ephemerals, Hash, :default => {}
20
21
  magic :permanent, :boolean, :default => false
21
- magic :public_ip, String
22
22
  magic :placement_group, String
23
- magic :region, String, :default => -> do
24
- default_availability_zone.gsub(/^(\w+-\w+-\d)[a-z]/, '\1') if default_availability_zone
25
- end
26
- collection :security_groups, Ironfan::Dsl::Ec2::SecurityGroup
27
- magic :ssh_user, String, :default => 'ubuntu'
23
+ magic :provider, Ironfan::Provider, :default => Ironfan::Provider::Ec2
24
+ magic :public_ip, String
25
+ magic :region, String, :default => ->{ default_region }
26
+ magic :ssh_user, String, :default => ->{ image_info[:ssh_user] }
28
27
  magic :ssh_identity_dir, String, :default => ->{ Chef::Config.ec2_key_dir }
29
- magic :ssh_identity_file, String #, :default => "#{keypair}.pem"
28
+ collection :security_groups, Ironfan::Dsl::Ec2::SecurityGroup
30
29
  magic :subnet, String
31
30
  magic :validation_key, String, :default => ->{ IO.read(Chef::Config.validation_key) rescue '' }
32
31
  magic :vpc, String
33
32
 
34
- magic :provider, Ironfan::Provider, :default => Ironfan::Provider::Ec2
35
-
36
- def ec2_image_info
37
- keys = [region, flavor_info[:bits], backing, image_name]
38
- Chef::Config[:ec2_image_info][ keys ] || {}
33
+ def image_info
34
+ bit_str = "#{self.bits.to_i}-bit" # correct for legacy image info.
35
+ keys = [region, bit_str, backing, image_name]
36
+ info = Chef::Config[:ec2_image_info][ keys ]
37
+ ui.warn("Can't find image for #{[region, bits.to_s, backing, image_name].inspect}") if info.blank?
38
+ return info || {}
39
39
  end
40
40
 
41
41
  def image_id
42
- result = read_attribute(:image_id) || ec2_image_info[:image_id]
42
+ result = read_attribute(:image_id) || image_info[:image_id]
43
+ end
44
+
45
+ def ssh_key_name(computer)
46
+ keypair ? keypair.to_s : computer.server.cluster_name
47
+ end
48
+
49
+ def default_region
50
+ default_availability_zone ? default_availability_zone.gsub(/^(\w+-\w+-\d)[a-z]/, '\1') : nil
43
51
  end
44
52
 
45
53
  def to_display(style,values={})
@@ -127,20 +135,22 @@ end
127
135
 
128
136
  Chef::Config[:ec2_flavor_info] ||= {}
129
137
  Chef::Config[:ec2_flavor_info].merge!({
130
- 't1.micro' => { :price => 0.02, :bits => '64-bit', :ram => 686, :cores => 1, :core_size => 0.25, :inst_disks => 0, :inst_disk_size => 0, :ephemeral_volumes => 0 },
131
- 'm1.small' => { :price => 0.08, :bits => '64-bit', :ram => 1740, :cores => 1, :core_size => 1, :inst_disks => 1, :inst_disk_size => 160, :ephemeral_volumes => 1 },
132
- 'm1.medium' => { :price => 0.165, :bits => '32-bit', :ram => 3840, :cores => 2, :core_size => 1, :inst_disks => 1, :inst_disk_size => 410, :ephemeral_volumes => 1 },
133
- 'c1.medium' => { :price => 0.17, :bits => '32-bit', :ram => 1740, :cores => 2, :core_size => 2.5, :inst_disks => 1, :inst_disk_size => 350, :ephemeral_volumes => 1 },
134
- 'm1.large' => { :price => 0.32, :bits => '64-bit', :ram => 7680, :cores => 2, :core_size => 2, :inst_disks => 2, :inst_disk_size => 850, :ephemeral_volumes => 2 },
135
- 'm2.xlarge' => { :price => 0.45, :bits => '64-bit', :ram => 18124, :cores => 2, :core_size => 3.25, :inst_disks => 1, :inst_disk_size => 420, :ephemeral_volumes => 1 },
136
- 'c1.xlarge' => { :price => 0.64, :bits => '64-bit', :ram => 7168, :cores => 8, :core_size => 2.5, :inst_disks => 4, :inst_disk_size => 1690, :ephemeral_volumes => 4 },
137
- 'm1.xlarge' => { :price => 0.66, :bits => '64-bit', :ram => 15360, :cores => 4, :core_size => 2, :inst_disks => 4, :inst_disk_size => 1690, :ephemeral_volumes => 4 },
138
- 'm2.2xlarge' => { :price => 0.90, :bits => '64-bit', :ram => 35020, :cores => 4, :core_size => 3.25, :inst_disks => 2, :inst_disk_size => 850, :ephemeral_volumes => 2 },
139
- 'm2.4xlarge' => { :price => 1.80, :bits => '64-bit', :ram => 70041, :cores => 8, :core_size => 3.25, :inst_disks => 4, :inst_disk_size => 1690, :ephemeral_volumes => 4 },
140
- 'cc1.4xlarge' => { :price => 1.30, :bits => '64-bit', :ram => 23552, :cores => 8, :core_size => 4.19, :inst_disks => 4, :inst_disk_size => 1690, :ephemeral_volumes => 2, :placement_groupable => true, :virtualization => 'hvm' },
141
- 'cc1.8xlarge' => { :price => 2.40, :bits => '64-bit', :ram => 61952, :cores =>16, :core_size => 5.50, :inst_disks => 8, :inst_disk_size => 3370, :ephemeral_volumes => 4, :placement_groupable => true, :virtualization => 'hvm' },
142
- 'cg1.4xlarge' => { :price => 2.10, :bits => '64-bit', :ram => 22528, :cores => 8, :core_size => 4.19, :inst_disks => 4, :inst_disk_size => 1690, :ephemeral_volumes => 2, :placement_groupable => true, :virtualization => 'hvm' },
143
- })
138
+ # 32-or-64: m1.small, m1.medium, t1.micro, c1.medium
139
+ 't1.micro' => { :price => 0.02, :bits => 64, :ram => 686, :cores => 1, :core_size => 0.25, :inst_disks => 0, :inst_disk_size => 0, :ephemeral_volumes => 0 },
140
+ 'm1.small' => { :price => 0.08, :bits => 64, :ram => 1740, :cores => 1, :core_size => 1, :inst_disks => 1, :inst_disk_size => 160, :ephemeral_volumes => 1 },
141
+ 'm1.medium' => { :price => 0.165, :bits => 32, :ram => 3840, :cores => 2, :core_size => 1, :inst_disks => 1, :inst_disk_size => 410, :ephemeral_volumes => 1 },
142
+ 'c1.medium' => { :price => 0.17, :bits => 32, :ram => 1740, :cores => 2, :core_size => 2.5, :inst_disks => 1, :inst_disk_size => 350, :ephemeral_volumes => 1 },
143
+ #
144
+ 'm1.large' => { :price => 0.32, :bits => 64, :ram => 7680, :cores => 2, :core_size => 2, :inst_disks => 2, :inst_disk_size => 850, :ephemeral_volumes => 2 },
145
+ 'm2.xlarge' => { :price => 0.45, :bits => 64, :ram => 18124, :cores => 2, :core_size => 3.25, :inst_disks => 1, :inst_disk_size => 420, :ephemeral_volumes => 1 },
146
+ 'c1.xlarge' => { :price => 0.64, :bits => 64, :ram => 7168, :cores => 8, :core_size => 2.5, :inst_disks => 4, :inst_disk_size => 1690, :ephemeral_volumes => 4 },
147
+ 'm1.xlarge' => { :price => 0.66, :bits => 64, :ram => 15360, :cores => 4, :core_size => 2, :inst_disks => 4, :inst_disk_size => 1690, :ephemeral_volumes => 4 },
148
+ 'm2.2xlarge' => { :price => 0.90, :bits => 64, :ram => 35020, :cores => 4, :core_size => 3.25, :inst_disks => 2, :inst_disk_size => 850, :ephemeral_volumes => 2 },
149
+ 'm2.4xlarge' => { :price => 1.80, :bits => 64, :ram => 70041, :cores => 8, :core_size => 3.25, :inst_disks => 4, :inst_disk_size => 1690, :ephemeral_volumes => 4 },
150
+ 'cc1.4xlarge' => { :price => 1.30, :bits => 64, :ram => 23552, :cores => 8, :core_size => 4.19, :inst_disks => 4, :inst_disk_size => 1690, :ephemeral_volumes => 2, :placement_groupable => true, :virtualization => 'hvm' },
151
+ 'cc1.8xlarge' => { :price => 2.40, :bits => 64, :ram => 61952, :cores =>16, :core_size => 5.50, :inst_disks => 8, :inst_disk_size => 3370, :ephemeral_volumes => 4, :placement_groupable => true, :virtualization => 'hvm' },
152
+ 'cg1.4xlarge' => { :price => 2.10, :bits => 64, :ram => 22528, :cores => 8, :core_size => 4.19, :inst_disks => 4, :inst_disk_size => 1690, :ephemeral_volumes => 2, :placement_groupable => true, :virtualization => 'hvm' },
153
+ })
144
154
 
145
155
  Chef::Config[:ec2_image_info] ||= {}
146
156
  Chef::Config[:ec2_image_info].merge!({
@@ -28,7 +28,17 @@ module Ironfan
28
28
  values["Env"] = environment
29
29
  values
30
30
  end
31
+
32
+ # we should always show up in owners' inspect string
33
+ def inspect_compact ; inspect ; end
34
+
35
+ # @returns [Hash{String, Array}] of 'what you did wrong' => [relevant, info]
36
+ def lint
37
+ errors = []
38
+ errors['missing cluster/facet/server'] = [cluster_name, facet_name, name] unless (cluster_name && facet_name && name)
39
+ errors
40
+ end
31
41
  end
32
42
 
33
43
  end
34
- end
44
+ end
@@ -29,8 +29,8 @@ module Ironfan
29
29
  :blank_xfs => 'snap-d9c1edb1',
30
30
  })
31
31
 
32
- def snapshot_id(id)
33
- Chef::Log.warn("CODE SMELL: EBS specific information in Dsl::Volume::VOLUME_IDS")
32
+ def snapshot_id(*)
33
+ Ironfan.todo("CODE SMELL: EBS specific information in Dsl::Volume::VOLUME_IDS")
34
34
  super || VOLUME_IDS[snapshot_name]
35
35
  end
36
36
  end
@@ -3,11 +3,11 @@ module Ironfan
3
3
  class ChefServer
4
4
 
5
5
  class Client < Ironfan::Provider::Resource
6
- delegate :add_to_index, :admin, :cdb_destroy, :cdb_save,
7
- :class_from_file, :couchdb, :couchdb=, :couchdb_id, :couchdb_id=,
8
- :couchdb_rev, :couchdb_rev=, :create, :create_keys,
9
- :delete_from_index, :destroy, :from_file, :index_id, :index_id=,
10
- :index_object_type, :name, :public_key, :save, :set_or_return,
6
+ delegate :add_to_index, :admin, :cdb_destroy, :cdb_save,
7
+ :class_from_file, :couchdb, :couchdb=, :couchdb_id, :couchdb_id=,
8
+ :couchdb_rev, :couchdb_rev=, :create, :create_keys,
9
+ :delete_from_index, :destroy, :from_file, :index_id, :index_id=,
10
+ :index_object_type, :name, :public_key, :save, :set_or_return,
11
11
  :to_hash, :validate, :with_indexer_metadata,
12
12
  :to => :adaptee
13
13
  field :key_filename, String,
@@ -18,6 +18,11 @@ module Ironfan
18
18
  self.adaptee ||= Chef::ApiClient.new
19
19
  end
20
20
 
21
+ def to_s
22
+ "<%-15s %-23s %s>" % [
23
+ self.class.handle, name, key_filename]
24
+ end
25
+
21
26
  def self.shared?() false; end
22
27
  def self.multiple?() false; end
23
28
  def self.resource_type() :client; end
@@ -40,13 +45,16 @@ module Ironfan
40
45
  # Discovery
41
46
  #
42
47
  def self.load!(cluster=nil)
48
+ Ironfan.substep(cluster.name, "chef clients")
43
49
  nameq = "name:#{cluster.name}-* OR clientname:#{cluster.name}-*"
44
- ChefServer.search(:client, nameq) do |client|
45
- register client unless client.blank?
50
+ ChefServer.search(:client, nameq) do |raw|
51
+ next unless raw.present?
52
+ client = register(raw)
53
+ Chef::Log.debug("Loaded #{client}")
46
54
  end
47
55
  end
48
56
 
49
- #
57
+ #
50
58
  # Manipulation
51
59
  #
52
60
  def self.create!(computer)
@@ -30,6 +30,11 @@ module Ironfan
30
30
  self.adaptee ||= Chef::Node.new
31
31
  end
32
32
 
33
+ def to_s
34
+ "<%-15s %-23s %s>" % [
35
+ self.class.handle, name, run_list]
36
+ end
37
+
33
38
  def self.shared?() false; end
34
39
  def self.multiple?() false; end
35
40
  # def self.resource_type() self; end
@@ -83,11 +88,11 @@ module Ironfan
83
88
  # Discovery
84
89
  #
85
90
  def self.load!(cluster=nil)
91
+ Ironfan.substep(cluster.name, "nodes")
86
92
  ChefServer.search(:node,"name:#{cluster.name}-*") do |raw|
87
- next if raw.blank?
88
- node = Node.new
89
- node.adaptee = raw
90
- remember node
93
+ next unless raw.present?
94
+ node = register(raw)
95
+ Chef::Log.debug("Loaded #{node}")
91
96
  end
92
97
  end
93
98
 
@@ -38,13 +38,20 @@ module Ironfan
38
38
  self
39
39
  end
40
40
 
41
- #
41
+ def to_s
42
+ "<%-15s %-23s %s>" % [
43
+ self.class.handle, name, run_list]
44
+ end
45
+
46
+ #
42
47
  # Discovery
43
48
  #
44
49
  def self.load!(cluster)
50
+ Ironfan.substep(cluster.name, "roles")
45
51
  ChefServer.search(:role,"name:#{cluster.name}_*") do |raw|
46
- next if raw.blank?
47
- remember Role.new(:adaptee => raw)
52
+ next unless raw.present?
53
+ role = register(raw)
54
+ Chef::Log.debug("Loaded #{role}")
48
55
  end
49
56
  end
50
57
 
@@ -2,7 +2,8 @@ module Ironfan
2
2
  class Provider
3
3
 
4
4
  class ChefServer < Ironfan::Provider
5
-
5
+ self.handle = :chef
6
+
6
7
  def self.resources
7
8
  [ Client, Node, Role ]
8
9
  end
@@ -25,4 +26,4 @@ module Ironfan
25
26
  end
26
27
 
27
28
  end
28
- end
29
+ end
@@ -16,6 +16,11 @@ module Ironfan
16
16
  :to => :adaptee
17
17
  field :dsl_volume, Ironfan::Dsl::Volume
18
18
 
19
+ def to_s
20
+ "<%-15s %-12s %-25s %-32s %-10s %-12s %-15s %-5s %s:%s>" % [
21
+ self.class.handle, id, created_at, tags['name'], state, device, tags['mount_point'], size, server_id, attached_at ]
22
+ end
23
+
19
24
  def self.shared?() true; end
20
25
  def self.multiple?() true; end
21
26
  def self.resource_type() :ebs_volume; end
@@ -44,6 +49,7 @@ module Ironfan
44
49
  # Discovery
45
50
  #
46
51
  def self.load!(cluster=nil)
52
+ Ironfan.substep(cluster.name, "volumes")
47
53
  Ec2.connection.volumes.each do |vol|
48
54
  next if vol.blank?
49
55
  ebs = EbsVolume.new(:adaptee => vol)
@@ -55,6 +61,7 @@ module Ironfan
55
61
  else
56
62
  remember ebs
57
63
  end
64
+ Chef::Log.debug("Loaded #{ebs}")
58
65
  end
59
66
  end
60
67
 
@@ -100,4 +107,4 @@ module Ironfan
100
107
 
101
108
  end
102
109
  end
103
- end
110
+ end
@@ -25,12 +25,18 @@ module Ironfan
25
25
  File.open(key_filename, "w", 0600){|f| f.print( body ) }
26
26
  end
27
27
 
28
+ def to_s
29
+ "<%-15s %-12s>" % [self.class.handle, name]
30
+ end
31
+
28
32
  #
29
33
  # Discovery
30
34
  #
31
35
  def self.load!(cluster=nil)
36
+ Ironfan.substep(cluster && cluster.name, "keypairs")
32
37
  Ec2.connection.key_pairs.each do |keypair|
33
38
  register keypair unless keypair.blank?
39
+ Chef::Log.debug("Loaded <%-15s %s>" % [handle, keypair.name])
34
40
  end
35
41
  end
36
42
 
@@ -62,4 +68,4 @@ module Ironfan
62
68
 
63
69
  end
64
70
  end
65
- end
71
+ end
@@ -93,10 +93,16 @@ module Ironfan
93
93
  keypair = cloud.keypair || computer.server.cluster_name
94
94
  end
95
95
 
96
+ def to_s
97
+ "<%-15s %-12s %-25s %-25s %-15s %-15s %-12s %-12s %s:%s>" % [
98
+ self.class.handle, id, created_at, tags['name'], private_ip_address, public_ip_address, flavor_id, availability_zone, key_name, groups.join(',') ]
99
+ end
100
+
96
101
  #
97
102
  # Discovery
98
103
  #
99
104
  def self.load!(cluster=nil)
105
+ Ironfan.substep(cluster.name, "machines")
100
106
  Ec2.connection.servers.each do |fs|
101
107
  machine = new(:adaptee => fs)
102
108
  if recall? machine.name
@@ -109,6 +115,7 @@ module Ironfan
109
115
  else
110
116
  remember machine, :append_id => "terminated:#{machine.id}"
111
117
  end
118
+ Chef::Log.debug("Loaded #{machine}")
112
119
  end
113
120
  end
114
121
 
@@ -122,11 +129,11 @@ module Ironfan
122
129
  machine.bogus << :unexpected_machine
123
130
  end
124
131
  next unless machine.bogus?
125
- fake = Ironfan::Broker::Computer.new
126
- fake[:machine] = machine
127
- fake.name = machine.name
128
- machine.users << fake
129
- computers << fake
132
+ fake = Ironfan::Broker::Computer.new
133
+ fake[:machine] = machine
134
+ fake.name = machine.name
135
+ machine.users << fake
136
+ computers << fake
130
137
  end
131
138
  end
132
139
 
@@ -134,11 +141,14 @@ module Ironfan
134
141
  # Manipulation
135
142
  #
136
143
  def self.create!(computer)
137
- Chef::Log.warn("CODE SMELL: overly large method: #{caller}")
144
+ Ironfan.todo("CODE SMELL: overly large method: #{caller}")
138
145
  return if computer.machine? and computer.machine.created?
139
146
  Ironfan.step(computer.name,"creating cloud machine", :green)
140
- # lint_fog
141
- launch_desc = fog_launch_description(computer)
147
+ #
148
+ errors = lint(computer)
149
+ if errors.present? then raise ArgumentError, "Failed validation: #{errors.inspect}" ; end
150
+ #
151
+ launch_desc = launch_description(computer)
142
152
  Chef::Log.debug(JSON.pretty_generate(launch_desc))
143
153
 
144
154
  Ironfan.safely do
@@ -162,7 +172,7 @@ module Ironfan
162
172
 
163
173
  # register the new volumes for later save!, and tag appropriately
164
174
  computer.machine.volumes.each do |v|
165
- Chef::Log.warn "CODE SMELL: Machine is too familiar with EbsVolume problems"
175
+ Ironfan.todo "CODE SMELL: Machine is too familiar with EbsVolume problems"
166
176
  ebs_vol = Ec2::EbsVolume.register v
167
177
  drive = computer.drives.values.select do |drive|
168
178
  drive.volume.device == ebs_vol.device
@@ -178,30 +188,43 @@ module Ironfan
178
188
  Ec2.ensure_tags(tags,ebs_vol)
179
189
  end
180
190
  end
181
- def self.fog_launch_description(computer)
182
- cloud = computer.server.cloud(:ec2)
191
+
192
+ # @returns [Hash{String, Array}] of 'what you did wrong' => [relevant, info]
193
+ def self.lint(computer)
194
+ cloud = computer.server.cloud(:ec2)
195
+ info = [computer.name, cloud.inspect]
196
+ errors = {}
197
+ server_errors = computer.server.lint
198
+ errors["Unhappy Server"] = server_errors if server_errors.present?
199
+ errors["No AMI found"] = info if cloud.image_id.blank?
200
+ errors['Missing client'] = info unless computer.client?
201
+ errors['Missing private_key'] = computer.client unless computer.private_key
202
+ errors
203
+ end
204
+
205
+ def self.launch_description(computer)
206
+ cloud = computer.server.cloud(:ec2)
183
207
  user_data_hsh = {
184
208
  :chef_server => Chef::Config[:chef_server_url],
185
- #:validation_client_name => Chef::Config[:validation_client_name],
209
+ # :validation_client_name => Chef::Config[:validation_client_name],
186
210
  #
187
211
  :node_name => computer.name,
188
212
  :organization => Chef::Config[:organization],
189
213
  :cluster_name => computer.server.cluster_name,
190
214
  :facet_name => computer.server.facet_name,
191
215
  :facet_index => computer.server.index,
192
- :client_key => computer[:client].private_key
216
+ :client_key => computer.private_key
193
217
  }
194
218
 
195
219
  # Fog does not actually create tags when it creates a server;
196
220
  # they and permanence are applied during sync
197
- keypair = cloud.keypair || computer.server.cluster_name
198
221
  description = {
199
222
  :image_id => cloud.image_id,
200
223
  :flavor_id => cloud.flavor,
201
224
  :vpc_id => cloud.vpc,
202
225
  :subnet_id => cloud.subnet,
203
226
  :groups => cloud.security_groups.keys,
204
- :key_name => keypair.to_s,
227
+ :key_name => cloud.ssh_key_name(computer),
205
228
  :user_data => JSON.pretty_generate(user_data_hsh),
206
229
  :block_device_mapping => block_device_mapping(computer),
207
230
  :availability_zone => cloud.default_availability_zone,
@@ -214,9 +237,10 @@ module Ironfan
214
237
  end
215
238
  description
216
239
  end
240
+
217
241
  # An array of hashes with dorky-looking keys, just like Fog wants it.
218
242
  def self.block_device_mapping(computer)
219
- Chef::Log.warn "CODE SMELL: Machine is too familiar with EbsVolume problems"
243
+ Ironfan.todo "CODE SMELL: Machine is too familiar with EbsVolume problems"
220
244
  computer.drives.values.map do |drive|
221
245
  next if drive.disk # Don't create any disc already satisfied
222
246
  volume = drive.volume or next
@@ -263,4 +287,3 @@ module Ironfan
263
287
  end
264
288
  end
265
289
  end
266
-
@@ -11,14 +11,20 @@ module Ironfan
11
11
  self["groupName"]
12
12
  end
13
13
 
14
+ def to_s
15
+ "<%-15s %-12s %-12s>" % [ self.class.handle, '', name ]
16
+ end
17
+
14
18
  def self.load!(cluster)
19
+ Ironfan.substep(cluster.name, "placement groups")
15
20
  result = Ec2.connection.describe_placement_groups
16
21
  result.body["placementGroupSet"].each do |group|
17
22
  register group unless group.blank?
23
+ Chef::Log.debug("Loaded #{group.inspect}")
18
24
  end
19
25
  end
20
26
  end
21
27
 
22
28
  end
23
29
  end
24
- end
30
+ end
@@ -24,8 +24,28 @@ module Ironfan
24
24
  # Discovery
25
25
  #
26
26
  def self.load!(cluster=nil)
27
- Ec2.connection.security_groups.each do |sg|
28
- remember SecurityGroup.new(:adaptee => sg) unless sg.blank?
27
+ Ironfan.substep(cluster.name, "security groups")
28
+
29
+ Ec2.connection.security_groups.each do |raw|
30
+ next if raw.blank?
31
+ sg = SecurityGroup.new(:adaptee => raw)
32
+ remember(sg)
33
+ Chef::Log.debug("Loaded #{sg}")
34
+ end
35
+ end
36
+
37
+ def to_s
38
+ if ip_permissions.present?
39
+ perm_str = ip_permissions.map{|perm|
40
+ "%s:%s-%s (%s | %s)" % [
41
+ perm['ipProtocol'], perm['fromPort'], perm['toPort'],
42
+ perm['groups' ].map{|el| el['groupName'] }.join(','),
43
+ perm['ipRanges'].map{|el| el['cidrIp'] }.join(','),
44
+ ]
45
+ }
46
+ return "<%-15s %-12s %-25s %s>" % [ self.class.handle, group_id, name, perm_str]
47
+ else
48
+ return "<%-15s %-12s %s>" % [ self.class.handle, group_id, name ]
29
49
  end
30
50
  end
31
51
 
@@ -35,7 +55,7 @@ module Ironfan
35
55
 
36
56
  def self.create!(computer)
37
57
  return unless Ec2.applicable computer
38
-
58
+
39
59
  ensure_groups(computer)
40
60
  groups = self.expected_ids(computer)
41
61
  # Only handle groups that don't already exist
@@ -95,7 +115,7 @@ module Ironfan
95
115
  # Ensure the security_groups include those for cluster & facet
96
116
  # FIXME: This violates the DSL's immutability; it should be
97
117
  # something calculated from within the DSL construction
98
- Chef::Log.warn("CODE SMELL: violation of DSL immutability: #{caller}")
118
+ Ironfan.todo("CODE SMELL: violation of DSL immutability: #{caller}")
99
119
  cloud = computer.server.cloud(:ec2)
100
120
  c_group = cloud.security_group(computer.server.cluster_name)
101
121
  c_group.authorized_by_group(c_group.name)
@@ -123,4 +143,4 @@ module Ironfan
123
143
 
124
144
  end
125
145
  end
126
- end
146
+ end
@@ -2,6 +2,7 @@ module Ironfan
2
2
  class Provider
3
3
 
4
4
  class Ec2 < Ironfan::IaasProvider
5
+ self.handle = :ec2
5
6
 
6
7
  def self.resources
7
8
  [ Machine, EbsVolume, KeyPair, SecurityGroup ]
@@ -18,7 +19,7 @@ module Ironfan
18
19
  :region => Chef::Config[:knife][:region]
19
20
  })
20
21
  end
21
-
22
+
22
23
  def self.aws_account_id()
23
24
  Chef::Config[:knife][:aws_account_id]
24
25
  end
@@ -44,4 +45,4 @@ module Ironfan
44
45
  end
45
46
 
46
47
  end
47
- end
48
+ end
@@ -2,7 +2,8 @@ module Ironfan
2
2
  class Provider
3
3
 
4
4
  class VirtualBox < Ironfan::IaasProvider
5
+ self.handle = :virtualbox
5
6
  end
6
7
 
7
8
  end
8
- end
9
+ end
@@ -1,9 +1,10 @@
1
1
  # Providers present a lightweight wrapper for various third-party services,
2
2
  # such as Chef's node and client APIs, and Amazon's EC2 APIs. This allows
3
- # Ironfan ask specialized questions (such as whether a given resource
3
+ # Ironfan ask specialized questions (such as whether a given resource
4
4
  # matches
5
5
  module Ironfan
6
6
  class Provider < Builder
7
+ class_attribute :handle
7
8
 
8
9
  def self.receive(obj,&block)
9
10
  obj[:_type] = case obj[:name]
@@ -42,9 +43,11 @@ module Ironfan
42
43
 
43
44
  def bogus?() !bogus.empty?; end
44
45
 
46
+ def self.handle ; name.to_s.gsub(/.*::/,'').to_sym ; end
47
+
45
48
  #
46
49
  # Flags
47
- #
50
+ #
48
51
  # Non-shared resources live and die with the computer
49
52
  def self.shared?() true; end
50
53
  # Can multiple instances of this resource be associated with the computer?
@@ -82,8 +85,9 @@ module Ironfan
82
85
 
83
86
  # Register and return the (adapted) object with the collection
84
87
  def self.register(native)
85
- result = new(:adaptee => native)
86
- remember result unless result.nil?
88
+ result = new(:adaptee => native) or return
89
+ remember result
90
+ result
87
91
  end
88
92
 
89
93
  def self.recall?(id)
@@ -135,4 +139,4 @@ module Ironfan
135
139
  end
136
140
  end
137
141
 
138
- end
142
+ end
data/lib/ironfan.rb CHANGED
@@ -25,12 +25,18 @@ module Ironfan
25
25
  def self.chef_config() @chef_config ; end
26
26
 
27
27
  # execute against multiple targets in parallel
28
- def self.parallel(targets,options={})
28
+ def self.parallel(targets)
29
29
  raise 'missing block' unless block_given?
30
- [targets].flatten.map do |target|
30
+ results = []
31
+ [targets].flatten.each_with_index.map do |target, idx|
31
32
  sleep(0.1) # avoid hammering with simultaneous requests
32
- Thread.new(target) {|target| yield target }
33
+ Thread.new(target) do |target|
34
+ results[idx] = safely(target.inspect) do
35
+ yield target
36
+ end
37
+ end
33
38
  end.each(&:join) # wait for all the blocks to return
39
+ results
34
40
  end
35
41
 
36
42
  #
@@ -120,13 +126,15 @@ module Ironfan
120
126
  # Ironfan.fog_connection.associate_address(self.fog_server.id, address)
121
127
  # end
122
128
  #
123
- def self.safely
129
+ def self.safely(info="")
124
130
  begin
125
131
  yield
126
- rescue StandardError => boom
127
- ui.info( boom )
128
- Chef::Log.error( boom )
129
- Chef::Log.error( boom.backtrace.join("\n") )
132
+ rescue StandardError => err
133
+ ui.warn("Error running #{info}:")
134
+ ui.warn(err)
135
+ Chef::Log.error( err )
136
+ Chef::Log.error( err.backtrace.join("\n") )
137
+ return err
130
138
  end
131
139
  end
132
140
 
@@ -137,6 +145,15 @@ module Ironfan
137
145
  ui.info(" #{"%-15s" % (name.to_s+":")}\t#{ui.color(desc.to_s, *style)}")
138
146
  end
139
147
 
148
+ def self.substep(name, desc)
149
+ step(name, " - #{desc}", :gray) if chef_config[:verbosity] >= 1
150
+ end
151
+
152
+ # Output a TODO to the logs if you've switched on pestering
153
+ def self.todo(*args)
154
+ Chef::Log.debug(*args) if Chef::Config[:show_todo]
155
+ end
156
+
140
157
  #
141
158
  # Utility to do mock out a step during a dry-run
142
159
  #
@@ -152,8 +169,8 @@ module Ironfan
152
169
  chef_config[:dry_run]
153
170
  end
154
171
 
155
- # Intentionally skipping an implied step
172
+ # Intentionally skipping an implied step
156
173
  def self.noop(source,method,*params)
157
- Chef::Log.debug("Nothing to do for #{source}.#{method}(#{params.join(',')}), skipping")
174
+ # Chef::Log.debug("#{method} is a no-op for #{source} -- skipping (#{params.join(',')})")
158
175
  end
159
176
  end
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: ironfan
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 4.0.9
5
+ version: 4.1.0
6
6
  platform: ruby
7
7
  authors:
8
8
  - Infochimps
@@ -10,7 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2012-09-20 00:00:00 Z
13
+ date: 2012-09-24 00:00:00 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: chef
@@ -232,7 +232,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
232
232
  requirements:
233
233
  - - ">="
234
234
  - !ruby/object:Gem::Version
235
- hash: 2386087126154656208
235
+ hash: 3712152495684054514
236
236
  segments:
237
237
  - 0
238
238
  version: "0"