ironfan 4.0.9 → 4.1.0

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