ironfan 4.2.1 → 4.2.2

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
+ # v4.2.2: @mrflip rocks the house
2
+ * Terminated machines are not bogus (fixes #165)
3
+ * Ignore deleting, deleted, or errored volumes in discovery
4
+ * Rescue on duplicate security groups; don't die logging on full 'load!'
5
+ * Changed key_pair to keypair, as per the DSL
6
+ * Specs for scripts (but not really)
7
+ * Model cleanup -- now mostly round-trip to JSON
8
+ * knife cluster bootstrap sets Chef::Config[:environment] (fixes #148)
9
+
1
10
  # v4.2.1: @nickmarden rocks the house
2
- * More correct merging of cluster and facet objects, with specs
11
+ * Correct merging of cluster and facet objects, with specs (fixes #158)
3
12
  * Circumvent memory bloat by resolving just once
4
13
 
5
14
  # v4.2.0:
data/VERSION CHANGED
@@ -1 +1 @@
1
- 4.2.1
1
+ 4.2.2
data/ironfan.gemspec CHANGED
@@ -5,7 +5,7 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "ironfan"
8
- s.version = "4.2.1"
8
+ s.version = "4.2.2"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Infochimps"]
@@ -77,14 +77,18 @@ Gem::Specification.new do |s|
77
77
  "lib/ironfan/provider/ec2.rb",
78
78
  "lib/ironfan/provider/ec2/ebs_volume.rb",
79
79
  "lib/ironfan/provider/ec2/elastic_ip.rb",
80
- "lib/ironfan/provider/ec2/key_pair.rb",
80
+ "lib/ironfan/provider/ec2/keypair.rb",
81
81
  "lib/ironfan/provider/ec2/machine.rb",
82
82
  "lib/ironfan/provider/ec2/placement_group.rb",
83
83
  "lib/ironfan/provider/ec2/security_group.rb",
84
84
  "lib/ironfan/provider/virtualbox.rb",
85
85
  "lib/ironfan/provider/virtualbox/machine.rb",
86
86
  "lib/ironfan/requirements.rb",
87
+ "spec/chef/cluster_bootstrap_spec.rb",
88
+ "spec/fixtures/gunbai.rb",
89
+ "spec/fixtures/gunbai_slice.json",
87
90
  "spec/ironfan/cluster_spec.rb",
91
+ "spec/ironfan/ec2/cloud_provider.rb",
88
92
  "spec/ironfan/ec2/cloud_provider_spec.rb",
89
93
  "spec/ironfan/ec2/security_group_spec.rb",
90
94
  "spec/spec_helper.rb",
@@ -97,7 +101,7 @@ Gem::Specification.new do |s|
97
101
  s.require_paths = ["lib"]
98
102
  s.rubygems_version = "1.8.24"
99
103
  s.summary = "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."
100
- s.test_files = ["spec/spec_helper/dummy_chef.rb", "spec/ironfan/cluster_spec.rb", "spec/ironfan/ec2/cloud_provider_spec.rb", "spec/ironfan/ec2/security_group_spec.rb", "spec/spec_helper.rb", "spec/test_config.rb"]
104
+ s.test_files = ["spec/spec_helper/dummy_chef.rb", "spec/ironfan/cluster_spec.rb", "spec/ironfan/ec2/cloud_provider_spec.rb", "spec/ironfan/ec2/security_group_spec.rb", "spec/ironfan/ec2/cloud_provider.rb", "spec/chef/cluster_bootstrap_spec.rb", "spec/fixtures/gunbai_slice.json", "spec/fixtures/gunbai.rb", "spec/spec_helper.rb", "spec/test_config.rb"]
101
105
 
102
106
  if s.respond_to? :specification_version then
103
107
  s.specification_version = 3
@@ -59,6 +59,7 @@ class Chef
59
59
  end
60
60
 
61
61
  def perform_execution(target)
62
+ reconcile_chef_config(target)
62
63
  # Execute across all servers in parallel
63
64
  Ironfan.parallel(target.values) {|computer| run_bootstrap(computer)}
64
65
  # threads = target.servers.map{ |server| Thread.new(server) { |svr| run_bootstrap(svr, svr.public_hostname) } }
@@ -69,6 +70,18 @@ class Chef
69
70
  confirm_or_exit("Are you absolutely certain that you want to perform this action? (Type 'Yes' to confirm) ", 'Yes')
70
71
  end
71
72
 
73
+ protected
74
+
75
+ def reconcile_chef_config(target)
76
+ environments = target.environments
77
+ if environments.length > 1
78
+ ui.error "You cannot bootstrap machines in multiple chef environments: got #{environments.inspect} from #{target.map(&:name)}"
79
+ ui.error "Re-run this command on each subgroup of machines that share an environment"
80
+ raise StandardError, "Cannot bootstrap multiple chef environments"
81
+ end
82
+ Chef::Config[:environment] = environments.first
83
+ end
84
+
72
85
  end
73
86
  end
74
87
  end
@@ -45,9 +45,12 @@ class Chef
45
45
  cluster = target.cluster
46
46
 
47
47
  ui.info("")
48
- ui.info("You are in a cluster. There is a sign overhead reading '#{ui.color(@name_args.first, :yellow)}'.")
49
- ui.info("Next to you a burly man in a greasy apron sharpens his cleaver, and a lissom princess performs treacherous origami.")
50
- ui.info("It is Pitch Dark. You are likely to be eaten by a grue.") if target.select(&:running?).empty?
48
+ ui.info([
49
+ ui.color("You are in a cluster. There is a sign overhead reading '", :magenta),
50
+ ui.color(@name_args.first, :yellow, :bold),
51
+ ui.color("'.\nNext to you a burly man in a greasy apron sharpens his cleaver, \nand a lissom princess performs treacherous origami.", :magenta)
52
+ ].join)
53
+ ui.info(ui.color("It is Pitch Dark. You are likely to be eaten by a grue.", :black, :bold)) if target.select(&:running?).empty?
51
54
 
52
55
  # Commands to try:
53
56
  # nn = Chef::Node.load('node-name')
@@ -35,6 +35,7 @@ class Chef
35
35
  :boolean => true
36
36
 
37
37
  def run
38
+ with_verbosity(1){ config[:include_terminated] }
38
39
  load_ironfan
39
40
  die(banner) if @name_args.empty?
40
41
  configure_dry_run
@@ -4,7 +4,7 @@ require 'gorillib/hash/deep_merge'
4
4
 
5
5
  module Gorillib
6
6
 
7
- # Make a clean deep-copy of the value, via gorillib semantics if
7
+ # Make a clean deep-copy of the value, via gorillib semantics if
8
8
  # possible, otherwise via marshalling
9
9
  def self.deep_copy(value)
10
10
  case
@@ -36,7 +36,7 @@ module Gorillib
36
36
  else
37
37
  write_attribute(collection_field_name, coll)
38
38
  end
39
- rescue StandardError => err ; err.polish("#{self.class} #{collection_field_name} collection on #{args}'") rescue nil ; raise ; end
39
+ rescue StandardError => err ; err.polish("#{self.class} #{collection_field_name} collection on #{coll}'") rescue nil ; raise ; end
40
40
  end
41
41
  end
42
42
  end
@@ -44,13 +44,13 @@ module Gorillib
44
44
  end
45
45
 
46
46
  # The attribute :underlay provides an object (preferably another
47
- # Gorillib::Model or the like) that will resolve stacked
48
- # defaults. If fields are declared with a :resolver, it will
47
+ # Gorillib::Model or the like) that will resolve stacked
48
+ # defaults. If fields are declared with a :resolver, it will
49
49
  # apply that call in preference the default rules (self.field
50
50
  # -> underlay.field -> self.field.default )
51
51
  #
52
- # To provide resolve cleanly without read-write loops destroying
53
- # the separation of concerns, the resolve mechanism has been
52
+ # To provide resolve cleanly without read-write loops destroying
53
+ # the separation of concerns, the resolve mechanism has been
54
54
  # broken from the regular read-write accessors.
55
55
  #
56
56
  module Resolution
@@ -59,9 +59,9 @@ module Gorillib
59
59
  attr_accessor :underlay
60
60
 
61
61
  # Return a fully-resolved copy of this object. All objects
62
- # referenced will be clean deep_copies, and will lack the
62
+ # referenced will be clean deep_copies, and will lack the
63
63
  # :underlay accessor. This is by design, to prevent self-
64
- # referential loops (parent->collection->child->owner)
64
+ # referential loops (parent->collection->child->owner)
65
65
  # when deep_coping.
66
66
  def resolve
67
67
  result = self.class.new
@@ -91,7 +91,7 @@ module Gorillib
91
91
  end
92
92
  result
93
93
  end
94
-
94
+
95
95
  def resolve_value(value)
96
96
  return if value.nil?
97
97
  return value.resolve if value.respond_to? :resolve
@@ -4,9 +4,9 @@ module Ironfan
4
4
  class Computer < Builder
5
5
 
6
6
  field :server, Ironfan::Dsl::Server
7
- collection :resources, Whatever
8
- collection :drives, Ironfan::Broker::Drive
9
- collection :providers, Ironfan::Provider
7
+ collection :resources, Ironfan::Provider::Resource, :key_method => :name
8
+ collection :drives, Ironfan::Broker::Drive, :key_method => :name
9
+ collection :providers, Whatever, :key_method => :name
10
10
  delegate :[], :[]=, :include?, :delete, :to => :resources
11
11
 
12
12
  # Only used for bogus servers
@@ -21,6 +21,7 @@ module Ironfan
21
21
  volumes = server.volumes.values
22
22
  volumes += server.implied_volumes
23
23
  volumes.each { |v| self.drive v.name, :volume => v }
24
+ rescue StandardError => err ; err.polish("#{self.class} on #{args}'") rescue nil ; raise
24
25
  end
25
26
 
26
27
  def name
@@ -128,10 +129,6 @@ module Ironfan
128
129
  @chef_client_script_content = Ironfan.safely{ File.read(script_filename) }
129
130
  end
130
131
 
131
- def ssh_identity_file
132
- resources[:key_pair].key_filename
133
- end
134
-
135
132
  # def ensure_resource(type)
136
133
  # if type.multiple?
137
134
  # existing = resources[type.resource_id]
@@ -182,6 +179,8 @@ module Ironfan
182
179
  self[:node]= value
183
180
  end
184
181
 
182
+ def environment ; server && server.environment ; end
183
+
185
184
  #
186
185
  # client
187
186
  #
@@ -200,9 +199,21 @@ module Ironfan
200
199
  self[:machine] = value
201
200
  end
202
201
  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
202
  def bootstrap_distro ; (server && server.selected_cloud) ? server.selected_cloud.bootstrap_distro : nil ; end
205
203
 
204
+ def keypair
205
+ resources[:keypair]
206
+ end
207
+
208
+ def ssh_user ; (server && server.selected_cloud) ? server.selected_cloud.ssh_user : nil ; end
209
+
210
+ def ssh_identity_file
211
+ if keypair then keypair.key_filename
212
+ elsif server && server.selected_cloud then server.selected_cloud.ssh_identity_file(self)
213
+ else nil
214
+ end
215
+ end
216
+
206
217
  #
207
218
  # Status flags
208
219
  #
@@ -248,6 +259,14 @@ module Ironfan
248
259
  running? || node?
249
260
  end
250
261
 
262
+ def receive_providers(objs)
263
+ objs = objs.map do |obj|
264
+ if obj.is_a?(String) then obj = Gorillib::Inflector.constantize(Gorillib::Inflector.camelize(obj.gsub(/\./, '/'))) ; end
265
+ obj
266
+ end
267
+ super(objs)
268
+ end
269
+
251
270
  def to_s
252
271
  "<#{self.class}(server=#{server}, resources=#{resources && resources.inspect_compact}, providers=#{providers && providers.inspect_compact})>"
253
272
  end
@@ -300,6 +319,10 @@ module Ironfan
300
319
  Ironfan.parallel(values) {|c| c.stop }
301
320
  end
302
321
 
322
+ def environments
323
+ map{|comp| comp.environment }.uniq
324
+ end
325
+
303
326
  #
304
327
  # Utility
305
328
  #
@@ -8,10 +8,11 @@ module Ironfan
8
8
  end
9
9
 
10
10
  class Broker < Builder
11
- # Take in a Dsl::Cluster, return Computers populated with
12
- # all discovered resources that correlate, plus bogus computers
13
- # corresponding to
11
+ # Take in a Dsl::Cluster; return Computers populated with all discovered
12
+ # resources that correlate; computers corresponding to partial or
13
+ # unrecognizable resources are labeled as bogus.
14
14
  def discover!(cluster)
15
+
15
16
  # Get fully resolved servers, and build Computers using them
16
17
  computers = Computers.new(:cluster => cluster)
17
18
  #
@@ -22,6 +23,7 @@ module Ironfan
22
23
  end
23
24
  #
24
25
  Ironfan.step cluster.name, "Reconciling DSL and provider information", :cyan
26
+
25
27
  computers.correlate
26
28
  computers.validate
27
29
  #
@@ -39,5 +41,4 @@ module Ironfan
39
41
  end
40
42
 
41
43
  end
42
-
43
44
  end
@@ -4,13 +4,17 @@ module Ironfan
4
4
  class Cloud < Ironfan::Dsl
5
5
  magic :default_cloud, :boolean, :default => false
6
6
 
7
- # Factory out to subclasses
8
- def self.receive(obj,&block)
9
- obj[:_type] = case obj[:name]
10
- when :ec2; Ec2
11
- when :virtualbox; VirtualBox
12
- else; raise "Unsupported cloud #{obj[:name]}"
13
- end unless native?(obj)
7
+ # Factory out to subclasses
8
+ def self.receive(obj, &block)
9
+ if obj.is_a?(Hash)
10
+ obj = obj.symbolize_keys
11
+ obj[:_type] ||=
12
+ case obj[:name]
13
+ when :ec2 then Ec2
14
+ when :virtualbox then VirtualBox
15
+ else raise "Unsupported cloud #{obj[:name]}"
16
+ end
17
+ end
14
18
  super
15
19
  end
16
20
 
@@ -18,4 +22,4 @@ module Ironfan
18
22
  end
19
23
 
20
24
  end
21
- end
25
+ end
@@ -1,14 +1,24 @@
1
1
  module Ironfan
2
2
  class Dsl
3
3
 
4
+ class RunListItem < Hash
5
+ Gorillib::Factory.register_factory(self, [self])
6
+ def name
7
+ self[:name]
8
+ end
9
+ def self.receive(hsh)
10
+ new.merge!(hsh.symbolize_keys)
11
+ end
12
+ end
13
+
4
14
  class Compute < Ironfan::Dsl
5
15
  @@run_list_rank = 0
6
16
  field :name, String
7
17
 
8
18
  # Resolve each of the following as a merge of their container's attributes and theirs
9
- collection :run_list_items, Hash, :resolver => :merge_resolve
10
- collection :clouds, Ironfan::Dsl::Cloud, :resolver => :merge_resolve
11
- collection :volumes, Ironfan::Dsl::Volume, :resolver => :merge_resolve
19
+ collection :run_list_items, RunListItem, :resolver => :merge_resolve, :key_method => :name
20
+ collection :clouds, Ironfan::Dsl::Cloud, :resolver => :merge_resolve, :key_method => :name
21
+ collection :volumes, Ironfan::Dsl::Volume, :resolver => :merge_resolve, :key_method => :name
12
22
 
13
23
  # Resolve these normally (overriding on each layer)
14
24
  magic :environment, Symbol, :default => :_default
@@ -81,4 +91,4 @@ module Ironfan
81
91
  end
82
92
 
83
93
  end
84
- end
94
+ end
@@ -15,17 +15,17 @@ module Ironfan
15
15
  magic :flavor, String, :default => 't1.micro'
16
16
  magic :image_id, String
17
17
  magic :image_name, String
18
- magic :keypair, Whatever
18
+ magic :keypair, String
19
19
  magic :monitoring, String
20
20
  magic :mount_ephemerals, Hash, :default => {}
21
21
  magic :permanent, :boolean, :default => false
22
22
  magic :placement_group, String
23
- magic :provider, Ironfan::Provider, :default => Ironfan::Provider::Ec2
23
+ magic :provider, Whatever, :default => Ironfan::Provider::Ec2
24
24
  magic :public_ip, String
25
25
  magic :region, String, :default => ->{ default_region }
26
26
  magic :ssh_user, String, :default => ->{ image_info[:ssh_user] }
27
27
  magic :ssh_identity_dir, String, :default => ->{ Chef::Config.ec2_key_dir }
28
- collection :security_groups, Ironfan::Dsl::Ec2::SecurityGroup
28
+ collection :security_groups, Ironfan::Dsl::Ec2::SecurityGroup, :key_method => :name
29
29
  magic :subnet, String
30
30
  magic :validation_key, String, :default => ->{ IO.read(Chef::Config.validation_key) rescue '' }
31
31
  magic :vpc, String
@@ -102,6 +102,14 @@ module Ironfan
102
102
  result
103
103
  end
104
104
 
105
+ def receive_provider(obj)
106
+ if obj.is_a?(String)
107
+ write_attribute :provider, Gorillib::Inflector.constantize(Gorillib::Inflector.camelize(obj.gsub(/\./, '/')))
108
+ else
109
+ super(obj)
110
+ end
111
+ end
112
+
105
113
  class SecurityGroup < Ironfan::Dsl
106
114
  field :name, String
107
115
  field :group_authorized, Array, :default => []
@@ -1,8 +1,8 @@
1
1
  module Ironfan
2
- module Dsl
2
+ class Dsl
3
3
 
4
4
  class VirtualBox < Cloud
5
5
  end
6
6
 
7
7
  end
8
- end
8
+ end
@@ -46,7 +46,7 @@ module Ironfan
46
46
  class EbsVolume < Ironfan::Provider::Resource; end
47
47
  class ElasticIp < Ironfan::Provider::Resource; end
48
48
  class Machine < Ironfan::IaasProvider::Machine; end
49
- class KeyPair < Ironfan::Provider::Resource; end
49
+ class Keypair < Ironfan::Provider::Resource; end
50
50
  class PlacementGroup < Ironfan::Provider::Resource; end
51
51
  class SecurityGroup < Ironfan::Provider::Resource; end
52
52
  end
@@ -51,6 +51,7 @@ module Ironfan
51
51
  def self.load!(cluster=nil)
52
52
  Ec2.connection.volumes.each do |vol|
53
53
  next if vol.blank?
54
+ next if %w[deleting deleted error].include?(vol.state.to_s)
54
55
  ebs = EbsVolume.new(:adaptee => vol)
55
56
  # Already have a volume by this name
56
57
  if recall? ebs.name
@@ -64,6 +65,11 @@ module Ironfan
64
65
  end
65
66
  end
66
67
 
68
+ def receive_adaptee(obj)
69
+ obj = Ec2.connection.volumes.new(obj) if obj.is_a?(Hash)
70
+ super
71
+ end
72
+
67
73
  def on_correlate(computer)
68
74
  drive = computer.drive(drivename)
69
75
  drive.disk = self
@@ -3,8 +3,7 @@ module Ironfan
3
3
  class Ec2
4
4
 
5
5
  class ElasticIp < Ironfan::Provider::Resource
6
-
7
6
  end
8
7
  end
9
8
  end
10
- end
9
+ end
@@ -2,20 +2,22 @@ module Ironfan
2
2
  class Provider
3
3
  class Ec2
4
4
 
5
- class KeyPair < Ironfan::Provider::Resource
5
+ class Keypair < Ironfan::Provider::Resource
6
6
  delegate :_dump, :collection, :collection=, :connection,
7
7
  :connection=, :destroy, :fingerprint, :fingerprint=, :identity,
8
8
  :identity=, :name, :name=, :new_record?, :public_key,
9
9
  :public_key=, :reload, :requires, :requires_one, :save,
10
10
  :symbolize_keys, :wait_for, :writable?, :write,
11
11
  :to => :adaptee
12
- field :key_filename, String,
13
- :default => ->{ "#{KeyPair.key_dir}/#{name}.pem" }
14
12
 
15
- def self.shared?() true; end
16
- def self.multiple?() false; end
17
- def self.resource_type() :key_pair; end
18
- def self.expected_ids(computer) [computer.server.cluster_name]; end
13
+ field :key_filename, String, :default => ->{ "#{Keypair.key_dir}/#{name}.pem" }
14
+
15
+ def self.shared? ; true ; end
16
+ def self.multiple? ; false ; end
17
+ def self.resource_type ; :keypair ; end
18
+ def self.expected_ids(computer)
19
+ [computer.server.cluster_name]
20
+ end
19
21
 
20
22
  def private_key
21
23
  File.open(key_filename, "rb").read
@@ -39,6 +41,11 @@ module Ironfan
39
41
  end
40
42
  end
41
43
 
44
+ def receive_adaptee(obj)
45
+ obj = Ec2.connection.key_pairs.new(obj) if obj.is_a?(Hash)
46
+ super
47
+ end
48
+
42
49
  #
43
50
  # Manipulation
44
51
  #
@@ -63,6 +70,7 @@ module Ironfan
63
70
  warn "Please set 'ec2_key_dir' in your knife.rb. Will use #{dir} as a default"
64
71
  dir
65
72
  end
73
+
66
74
  end
67
75
 
68
76
  end
@@ -34,7 +34,6 @@ module Ironfan
34
34
  :vpc_id=, :wait_for,
35
35
  :to => :adaptee
36
36
 
37
-
38
37
  def self.shared?() false; end
39
38
  def self.multiple?() false; end
40
39
  # def self.resource_type() Ironfan::IaasProvider::Machine; end
@@ -46,7 +45,8 @@ module Ironfan
46
45
  tags["Name"] || tags["name"] || id
47
46
  end
48
47
 
49
- def public_hostname() dns_name; end
48
+ def public_hostname ; dns_name ; end
49
+ def keypair ; key_pair ; end
50
50
 
51
51
  def created?
52
52
  not ['terminated', 'shutting-down'].include? state
@@ -104,20 +104,26 @@ module Ironfan
104
104
  def self.load!(cluster=nil)
105
105
  Ec2.connection.servers.each do |fs|
106
106
  machine = new(:adaptee => fs)
107
- if recall? machine.name
107
+ if (not machine.created?)
108
+ next unless Ironfan.chef_config[:include_terminated]
109
+ remember machine, :append_id => "terminated:#{machine.id}"
110
+ elsif recall? machine.name
108
111
  raise 'duplicate'
109
112
  machine.bogus << :duplicate_machines
110
113
  recall(machine.name).bogus << :duplicate_machines
111
114
  remember machine, :append_id => "duplicate:#{machine.id}"
112
- elsif machine.created?
115
+ else # never seen it
113
116
  remember machine
114
- else
115
- remember machine, :append_id => "terminated:#{machine.id}"
116
117
  end
117
118
  Chef::Log.debug("Loaded #{machine}")
118
119
  end
119
120
  end
120
121
 
122
+ def receive_adaptee(obj)
123
+ obj = Ec2.connection.servers.new(obj) if obj.is_a?(Hash)
124
+ super
125
+ end
126
+
121
127
  # Find active machines that haven't matched, but should have,
122
128
  # make sure all bogus machines have a computer to attach to
123
129
  # for display purposes
@@ -167,7 +173,7 @@ module Ironfan
167
173
  'name' => computer.name,
168
174
  'Name' => computer.name,
169
175
  }
170
- Ec2.ensure_tags(tags,computer.machine)
176
+ Ec2.ensure_tags(tags, computer.machine)
171
177
 
172
178
  # register the new volumes for later save!, and tag appropriately
173
179
  computer.machine.volumes.each do |v|
@@ -32,6 +32,11 @@ module Ironfan
32
32
  end
33
33
  end
34
34
 
35
+ def receive_adaptee(obj)
36
+ obj = Ec2.connection.security_groups.new(obj) if obj.is_a?(Hash)
37
+ super
38
+ end
39
+
35
40
  def to_s
36
41
  if ip_permissions.present?
37
42
  perm_str = ip_permissions.map{|perm|
@@ -63,7 +68,12 @@ module Ironfan
63
68
  Ironfan.step(computer.server.cluster_name, "creating security groups", :blue)
64
69
  groups.each do |group|
65
70
  Ironfan.step(group, " creating #{group} security group", :blue)
66
- Ec2.connection.create_security_group(group.to_s,"Ironfan created group #{group}")
71
+ begin
72
+ Ec2.connection.create_security_group(group.to_s,"Ironfan created group #{group}")
73
+ rescue Fog::Compute::AWS::Error => e # InvalidPermission.Duplicate
74
+ Chef::Log.info("ignoring security group error: #{e}")
75
+ sleep 0.5 # quit racing so hard
76
+ end
67
77
  end
68
78
  load! # Get the native groups via reload
69
79
  end
@@ -134,7 +144,7 @@ module Ironfan
134
144
  begin
135
145
  fog_group.authorize_port_range(range,options)
136
146
  rescue Fog::Compute::AWS::Error => e # InvalidPermission.Duplicate
137
- Chef::Log.debug("ignoring #{e}")
147
+ Chef::Log.info("ignoring #{e}")
138
148
  end
139
149
  end
140
150
  end
@@ -5,7 +5,7 @@ module Ironfan
5
5
  self.handle = :ec2
6
6
 
7
7
  def self.resources
8
- [ Machine, EbsVolume, KeyPair, SecurityGroup ]
8
+ [ Machine, EbsVolume, Keypair, SecurityGroup ]
9
9
  end
10
10
 
11
11
  #
@@ -6,13 +6,17 @@ module Ironfan
6
6
  class Provider < Builder
7
7
  class_attribute :handle
8
8
 
9
- def self.receive(obj,&block)
10
- obj[:_type] = case obj[:name]
11
- when :chef; Chef
12
- when :ec2; Ec2
13
- when :virtualbox; VirtualBox
14
- else; raise "Unsupported provider #{obj[:name]}"
15
- end unless native?(obj)
9
+ def self.receive(obj, &block)
10
+ if obj.is_a?(Hash)
11
+ obj = obj.symbolize_keys
12
+ obj[:_type] =
13
+ case obj[:name]
14
+ when :chef then Chef
15
+ when :ec2 then Ec2
16
+ when :virtualbox then VirtualBox
17
+ else raise "Unsupported provider #{obj[:name]}"
18
+ end
19
+ end
16
20
  super
17
21
  end
18
22
 
@@ -50,6 +54,11 @@ module Ironfan
50
54
 
51
55
  def self.handle ; name.to_s.gsub(/.*::/,'').to_sym ; end
52
56
 
57
+ def self.receive(obj)
58
+ obj = obj.symbolize_keys if obj.is_a?(Hash)
59
+ super(obj)
60
+ end
61
+
53
62
  #
54
63
  # Flags
55
64
  #
@@ -2,11 +2,9 @@
2
2
  require 'gorillib/builder'
3
3
  require 'gorillib/resolution'
4
4
 
5
-
6
5
  # Pre-declaration of class hierarchy
7
6
  require 'ironfan/headers'
8
7
 
9
-
10
8
  # DSL for cluster descriptions
11
9
  require 'ironfan/dsl'
12
10
  require 'ironfan/builder'
@@ -34,7 +32,7 @@ require 'ironfan/provider/chef/role'
34
32
  require 'ironfan/provider/ec2'
35
33
  require 'ironfan/provider/ec2/ebs_volume'
36
34
  require 'ironfan/provider/ec2/machine'
37
- require 'ironfan/provider/ec2/key_pair'
35
+ require 'ironfan/provider/ec2/keypair'
38
36
  require 'ironfan/provider/ec2/placement_group'
39
37
  require 'ironfan/provider/ec2/security_group'
40
38
 
data/lib/ironfan.rb CHANGED
@@ -29,7 +29,7 @@ module Ironfan
29
29
  raise 'missing block' unless block_given?
30
30
  results = []
31
31
  [targets].flatten.each_with_index.map do |target, idx|
32
- sleep(0.1) # avoid hammering with simultaneous requests
32
+ sleep(0.25) # avoid hammering with simultaneous requests
33
33
  Thread.new(target) do |target|
34
34
  results[idx] = safely(target.inspect) do
35
35
  yield target
@@ -80,19 +80,19 @@ module Ironfan
80
80
  # doesn't define the requested cluster.
81
81
  #
82
82
  # @return [Ironfan::Cluster] the requested cluster
83
- def self.load_cluster(cluster_name)
84
- cluster = cluster_name.to_sym
85
- raise ArgumentError, "Please supply a cluster name" if cluster_name.to_s.empty?
86
- return @@clusters[cluster] if @@clusters[cluster]
83
+ def self.load_cluster(name)
84
+ name = name.to_sym
85
+ raise ArgumentError, "Please supply a cluster name" if name.to_s.empty?
86
+ return @@clusters[name] if @@clusters[name]
87
87
 
88
- cluster_file = cluster_filenames[cluster_name] or die("Couldn't find a definition for #{cluster_name} in cluster_path: #{cluster_path.inspect}")
88
+ cluster_file = cluster_filenames[name] or raise("Couldn't find a definition for #{name} in cluster_path: #{cluster_path.inspect}")
89
89
 
90
90
  Chef::Log.info("Loading cluster #{cluster_file}")
91
91
 
92
92
  require cluster_file
93
- unless @@clusters[cluster] then die("#{cluster_file} was supposed to have the definition for the #{cluster_name} cluster, but didn't") end
93
+ unless @@clusters[name] then die("#{cluster_file} was supposed to have the definition for the #{name} cluster, but didn't") end
94
94
 
95
- @@clusters[cluster]
95
+ @@clusters[name]
96
96
  end
97
97
 
98
98
  #
@@ -105,7 +105,7 @@ module Ironfan
105
105
  cluster_path.each do |cp_dir|
106
106
  Dir[ File.join(cp_dir, '*.rb') ].each do |filename|
107
107
  cluster_name = File.basename(filename).gsub(/\.rb$/, '')
108
- @cluster_filenames[cluster_name] ||= filename
108
+ @cluster_filenames[cluster_name.to_sym] ||= filename
109
109
  end
110
110
  end
111
111
  @cluster_filenames
@@ -149,7 +149,11 @@ module Ironfan
149
149
  end
150
150
 
151
151
  def self.substep(name, desc)
152
- step(name, " - #{desc}", :gray) if chef_config[:verbosity] >= 1
152
+ step(name, " - #{desc}", :gray) if verbosity >= 1
153
+ end
154
+
155
+ def self.verbosity
156
+ chef_config[:verbosity].to_i
153
157
  end
154
158
 
155
159
  # Output a TODO to the logs if you've switched on pestering
@@ -0,0 +1,51 @@
1
+ require 'spec_helper'
2
+
3
+ ironfan_go!
4
+ require 'chef/knife/cluster_bootstrap'
5
+
6
+ describe Chef::Knife::ClusterBootstrap do
7
+ let(:cluster) do
8
+ Ironfan.load_cluster(:gunbai)
9
+ end
10
+
11
+ let(:target) do
12
+ Ironfan.broker.discover!(cluster)
13
+ end
14
+
15
+ let(:computers) do
16
+ Ironfan::Broker::Computers.receive(
17
+ MultiJson.load(
18
+ File.open(Pathname.path_to(:fixtures, 'gunbai_slice.json'))))
19
+ end
20
+
21
+ subject do
22
+ described_class.new(slice)
23
+ end
24
+
25
+ context 'getting slice' do
26
+ before do
27
+ subject.stub(:relevant?){ true }
28
+ subject.stub(:run_bootstrap)
29
+ subject.config[:yes] = true
30
+ end
31
+ context 'full slice' do
32
+ let(:slice){ ['gunbai'] }
33
+ it 'fails if there are multiple environments' do
34
+ expect{ subject.run }.to raise_error("Cannot bootstrap multiple chef environments")
35
+ end
36
+ end
37
+ context 'partial slice' do
38
+ let(:slice){ ['gunbai-hub'] }
39
+ it 'runs' do
40
+ subject.should_receive(:run_bootstrap).once
41
+ subject.run
42
+ end
43
+ end
44
+
45
+ end
46
+
47
+ # it 'loads computers from json' do
48
+ # computers.length.should == 2
49
+ # computers.first.server.fullname.should == 'gunbai-hub-0'
50
+ # end
51
+ end
@@ -0,0 +1,24 @@
1
+ Ironfan.cluster 'gunbai' do
2
+ cloud(:ec2) do
3
+ permanent false
4
+ availability_zones ['us-east-1d']
5
+ flavor 't1.micro'
6
+ backing 'ebs'
7
+ image_name 'natty'
8
+ bootstrap_distro 'ubuntu10.04-ironfan'
9
+ chef_client_script 'client.rb'
10
+ mount_ephemerals
11
+ end
12
+
13
+ environment :dev
14
+
15
+ role :ssh
16
+ cloud(:ec2).security_group(:ssh).authorize_port_range(22..22)
17
+
18
+ facet :hub do
19
+ end
20
+
21
+ facet :spoke do
22
+ environment :other
23
+ end
24
+ end
@@ -0,0 +1,330 @@
1
+ [
2
+ {
3
+ "server": {
4
+ "name": "0",
5
+ "run_list_items": [
6
+ {
7
+ "name": "role[ssh]",
8
+ "rank": 1,
9
+ "placement": "normal"
10
+ },
11
+ {
12
+ "name": "role[gunbai_cluster]",
13
+ "rank": 2,
14
+ "placement": "last"
15
+ },
16
+ {
17
+ "name": "role[gunbai_hub]",
18
+ "rank": 3,
19
+ "placement": "last"
20
+ }
21
+ ],
22
+ "clouds": [
23
+ {
24
+ "name": "ec2",
25
+ "default_cloud": false,
26
+ "availability_zones": [
27
+ "us-east-1d"
28
+ ],
29
+ "backing": "ebs",
30
+ "bits": 64,
31
+ "bootstrap_distro": "ubuntu10.04-ironfan",
32
+ "chef_client_script": "client.rb",
33
+ "default_availability_zone": "us-east-1d",
34
+ "flavor": "t1.micro",
35
+ "image_id": null,
36
+ "image_name": "natty",
37
+ "keypair": null,
38
+ "monitoring": null,
39
+ "mount_ephemerals": {
40
+
41
+ },
42
+ "permanent": false,
43
+ "placement_group": null,
44
+ "provider": "Ironfan::Provider::Ec2",
45
+ "public_ip": null,
46
+ "region": "us-east-1",
47
+ "ssh_user": "ubuntu",
48
+ "ssh_identity_dir": "/Users/flip/ics/sysadmin/enterprise-homebase/knife/credentials/ec2_keys",
49
+ "security_groups": [
50
+ {
51
+ "name": "ssh",
52
+ "group_authorized": [
53
+
54
+ ],
55
+ "group_authorized_by": [
56
+
57
+ ],
58
+ "range_authorizations": [
59
+ [
60
+ "22..22",
61
+ "0.0.0.0/0",
62
+ "tcp"
63
+ ]
64
+ ],
65
+ "_type": "ironfan.dsl.ec2.security_group"
66
+ }
67
+ ],
68
+ "subnet": null,
69
+ "validation_key": "-----BEGIN RSA PRIVATE KEY-----\nSNIP\n-----END RSA PRIVATE KEY-----\n",
70
+ "vpc": null,
71
+ "_type": "ironfan.dsl.ec2"
72
+ }
73
+ ],
74
+ "volumes": [
75
+
76
+ ],
77
+ "environment": "dev",
78
+ "use_cloud": null,
79
+ "cluster_role": {
80
+ "name": "gunbai_cluster",
81
+ "override_attributes": {
82
+
83
+ },
84
+ "default_attributes": {
85
+
86
+ },
87
+ "_type": "ironfan.dsl.role"
88
+ },
89
+ "facet_role": {
90
+ "name": "gunbai_hub",
91
+ "override_attributes": {
92
+
93
+ },
94
+ "default_attributes": {
95
+
96
+ },
97
+ "_type": "ironfan.dsl.role"
98
+ },
99
+ "cluster_name": "gunbai",
100
+ "facet_name": "hub",
101
+ "_type": "ironfan.dsl.server"
102
+ },
103
+ "resources": [
104
+ {
105
+ "adaptee": {"name":"gunbai","fingerprint":"a2:9a:15:b3:bc:95:68:99:2c:91:43:9c:3b:6b:99:00:11:e9:b2:5e"},
106
+ "bogus": [
107
+
108
+ ],
109
+ "key_filename": "/Users/flip/ics/sysadmin/enterprise-homebase/knife/credentials/ec2_keys/gunbai.pem",
110
+ "_type": "ironfan.provider.ec2.keypair"
111
+ },
112
+ {
113
+ "adaptee": {"ip_permissions":[{"groups":[],"ipRanges":[{"cidrIp":"0.0.0.0/0"}],"ipProtocol":"tcp","fromPort":22,"toPort":22}],"ip_permissions_egress":[],"owner_id":"689981200839","group_id":"sg-a0f174c9","name":"ssh","description":"cluster_chef+generated+group+ssh"},
114
+ "bogus": [
115
+
116
+ ],
117
+ "ensured": false,
118
+ "_type": "ironfan.provider.ec2.security_group"
119
+ }
120
+ ],
121
+ "drives": [
122
+ {
123
+ "node": {
124
+
125
+ },
126
+ "disk": null,
127
+ "volume": {
128
+ "name": "root",
129
+ "attachable": "ebs",
130
+ "availability_zone": null,
131
+ "create_at_launch": false,
132
+ "device": "/dev/sda1",
133
+ "formattable": false,
134
+ "fstype": "ext4",
135
+ "in_raid": false,
136
+ "keep": false,
137
+ "mount_dump": null,
138
+ "mount_pass": null,
139
+ "mount_options": "defaults,nouuid,noatime",
140
+ "mount_point": "/",
141
+ "mountable": true,
142
+ "size": null,
143
+ "volume_id": null,
144
+ "resizable": false,
145
+ "snapshot_id": null,
146
+ "snapshot_name": null,
147
+ "tags": {
148
+
149
+ },
150
+ "_type": "ironfan.dsl.volume"
151
+ },
152
+ "name": "root",
153
+ "_type": "ironfan.broker.drive"
154
+ }
155
+ ],
156
+ "providers": [
157
+ "Ironfan::Provider::ChefServer",
158
+ "Ironfan::Provider::Ec2"
159
+ ],
160
+ "name": null,
161
+ "bogus": [
162
+
163
+ ],
164
+ "_type": "ironfan.broker.computer"
165
+ },
166
+ {
167
+ "server": {
168
+ "name": "0",
169
+ "run_list_items": [
170
+ {
171
+ "name": "role[ssh]",
172
+ "rank": 1,
173
+ "placement": "normal"
174
+ },
175
+ {
176
+ "name": "role[gunbai_cluster]",
177
+ "rank": 4,
178
+ "placement": "last"
179
+ },
180
+ {
181
+ "name": "role[gunbai_spoke]",
182
+ "rank": 5,
183
+ "placement": "last"
184
+ }
185
+ ],
186
+ "clouds": [
187
+ {
188
+ "name": "ec2",
189
+ "default_cloud": false,
190
+ "availability_zones": [
191
+ "us-east-1d"
192
+ ],
193
+ "backing": "ebs",
194
+ "bits": 64,
195
+ "bootstrap_distro": "ubuntu10.04-ironfan",
196
+ "chef_client_script": "client.rb",
197
+ "default_availability_zone": "us-east-1d",
198
+ "flavor": "t1.micro",
199
+ "image_id": null,
200
+ "image_name": "natty",
201
+ "keypair": null,
202
+ "monitoring": null,
203
+ "mount_ephemerals": {
204
+
205
+ },
206
+ "permanent": false,
207
+ "placement_group": null,
208
+ "provider": "Ironfan::Provider::Ec2",
209
+ "public_ip": null,
210
+ "region": "us-east-1",
211
+ "ssh_user": "ubuntu",
212
+ "ssh_identity_dir": "/Users/flip/ics/sysadmin/enterprise-homebase/knife/credentials/ec2_keys",
213
+ "security_groups": [
214
+ {
215
+ "name": "ssh",
216
+ "group_authorized": [
217
+
218
+ ],
219
+ "group_authorized_by": [
220
+
221
+ ],
222
+ "range_authorizations": [
223
+ [
224
+ "22..22",
225
+ "0.0.0.0/0",
226
+ "tcp"
227
+ ]
228
+ ],
229
+ "_type": "ironfan.dsl.ec2.security_group"
230
+ }
231
+ ],
232
+ "subnet": null,
233
+ "validation_key": "-----BEGIN RSA PRIVATE KEY-----\nSNIP\n-----END RSA PRIVATE KEY-----\n",
234
+ "vpc": null,
235
+ "_type": "ironfan.dsl.ec2"
236
+ }
237
+ ],
238
+ "volumes": [
239
+
240
+ ],
241
+ "environment": "dev",
242
+ "use_cloud": null,
243
+ "cluster_role": {
244
+ "name": "gunbai_cluster",
245
+ "override_attributes": {
246
+
247
+ },
248
+ "default_attributes": {
249
+
250
+ },
251
+ "_type": "ironfan.dsl.role"
252
+ },
253
+ "facet_role": {
254
+ "name": "gunbai_spoke",
255
+ "override_attributes": {
256
+
257
+ },
258
+ "default_attributes": {
259
+
260
+ },
261
+ "_type": "ironfan.dsl.role"
262
+ },
263
+ "cluster_name": "gunbai",
264
+ "facet_name": "spoke",
265
+ "_type": "ironfan.dsl.server"
266
+ },
267
+ "resources": [
268
+ {
269
+ "adaptee": {"name":"gunbai","fingerprint":"a2:3b:9a:15:b3:bc:95:68:99:9e:2c:91:43:9c:6b:a5:6f:e9:b2:5e"},
270
+ "bogus": [
271
+
272
+ ],
273
+ "key_filename": "/Users/flip/ics/sysadmin/enterprise-homebase/knife/credentials/ec2_keys/gunbai.pem",
274
+ "_type": "ironfan.provider.ec2.keypair"
275
+ },
276
+ {
277
+ "adaptee": {"ip_permissions":[{"groups":[],"ipRanges":[{"cidrIp":"0.0.0.0/0"}],"ipProtocol":"tcp","fromPort":22,"toPort":22}],"ip_permissions_egress":[],"owner_id":"689981200839","group_id":"sg-a0f174c9","name":"ssh","description":"cluster_chef+generated+group+ssh"},
278
+ "bogus": [
279
+
280
+ ],
281
+ "ensured": false,
282
+ "_type": "ironfan.provider.ec2.security_group"
283
+ }
284
+ ],
285
+ "drives": [
286
+ {
287
+ "node": {
288
+
289
+ },
290
+ "disk": null,
291
+ "volume": {
292
+ "name": "root",
293
+ "attachable": "ebs",
294
+ "availability_zone": null,
295
+ "create_at_launch": false,
296
+ "device": "/dev/sda1",
297
+ "formattable": false,
298
+ "fstype": "ext4",
299
+ "in_raid": false,
300
+ "keep": false,
301
+ "mount_dump": null,
302
+ "mount_pass": null,
303
+ "mount_options": "defaults,nouuid,noatime",
304
+ "mount_point": "/",
305
+ "mountable": true,
306
+ "size": null,
307
+ "volume_id": null,
308
+ "resizable": false,
309
+ "snapshot_id": null,
310
+ "snapshot_name": null,
311
+ "tags": {
312
+
313
+ },
314
+ "_type": "ironfan.dsl.volume"
315
+ },
316
+ "name": "root",
317
+ "_type": "ironfan.broker.drive"
318
+ }
319
+ ],
320
+ "providers": [
321
+ "Ironfan::Provider::ChefServer",
322
+ "Ironfan::Provider::Ec2"
323
+ ],
324
+ "name": null,
325
+ "bogus": [
326
+
327
+ ],
328
+ "_type": "ironfan.broker.computer"
329
+ }
330
+ ]
@@ -0,0 +1,35 @@
1
+ require 'spec_helper'
2
+
3
+ require 'ironfan'
4
+
5
+ describe Ironfan::Dsl::Cluster do
6
+ let (:cluster) do
7
+ Ironfan.cluster "sparky" do
8
+
9
+ cloud(:ec2) do
10
+ security_group(:ssh).authorize_port_range 22..22
11
+ flavor 't1.micro'
12
+ end
13
+
14
+ facet :web do
15
+ instances 3
16
+ end
17
+
18
+ end
19
+ end
20
+
21
+ describe 'web facet server resolution' do
22
+ before { @facet = cluster.facets.values.first }
23
+ subject { @facet }
24
+ its(:name) { should eql "web" }
25
+
26
+ it 'should have the right number of servers' do
27
+ @facet.servers.length.should == 3
28
+ end
29
+
30
+ it 'should have one cloud provider, EC2' do
31
+ @facet.servers[0].clouds.keys.should == [ :ec2 ]
32
+ end
33
+ end
34
+
35
+ end
data/spec/spec_helper.rb CHANGED
@@ -2,6 +2,31 @@ $:.unshift File.expand_path('../../lib', __FILE__)
2
2
  require 'chef'
3
3
  require 'chef/knife'
4
4
  require 'fog'
5
-
6
5
  Fog.mock!
7
6
  Fog::Mock.delay = 0
7
+
8
+ require 'gorillib/pathname'
9
+
10
+ Pathname.register_paths(
11
+ code: File.expand_path('../..', __FILE__),
12
+ fixtures: [:code, 'spec', 'fixtures'],
13
+ )
14
+
15
+ RSpec.configure do |config|
16
+ def ironfan_go!
17
+ Chef::Knife.new.configure_chef
18
+ Chef::Config.instance_eval do
19
+ knife.merge!({
20
+ :aws_access_key_id => 'access_key',
21
+ :aws_secret_access_key => 'secret',
22
+ })
23
+ cluster_path Pathname.path_to(:fixtures).to_s
24
+ end
25
+
26
+ require 'ironfan'
27
+
28
+ Ironfan.ui = Chef::Knife::UI.new(STDOUT, STDERR, STDIN, {})
29
+ Ironfan.chef_config = { :verbosity => 0 }
30
+ Ironfan.cluster_path
31
+ end
32
+ end
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: ironfan
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 4.2.1
5
+ version: 4.2.2
6
6
  platform: ruby
7
7
  authors:
8
8
  - Infochimps
@@ -203,14 +203,18 @@ files:
203
203
  - lib/ironfan/provider/ec2.rb
204
204
  - lib/ironfan/provider/ec2/ebs_volume.rb
205
205
  - lib/ironfan/provider/ec2/elastic_ip.rb
206
- - lib/ironfan/provider/ec2/key_pair.rb
206
+ - lib/ironfan/provider/ec2/keypair.rb
207
207
  - lib/ironfan/provider/ec2/machine.rb
208
208
  - lib/ironfan/provider/ec2/placement_group.rb
209
209
  - lib/ironfan/provider/ec2/security_group.rb
210
210
  - lib/ironfan/provider/virtualbox.rb
211
211
  - lib/ironfan/provider/virtualbox/machine.rb
212
212
  - lib/ironfan/requirements.rb
213
+ - spec/chef/cluster_bootstrap_spec.rb
214
+ - spec/fixtures/gunbai.rb
215
+ - spec/fixtures/gunbai_slice.json
213
216
  - spec/ironfan/cluster_spec.rb
217
+ - spec/ironfan/ec2/cloud_provider.rb
214
218
  - spec/ironfan/ec2/cloud_provider_spec.rb
215
219
  - spec/ironfan/ec2/security_group_spec.rb
216
220
  - spec/spec_helper.rb
@@ -230,7 +234,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
230
234
  requirements:
231
235
  - - ">="
232
236
  - !ruby/object:Gem::Version
233
- hash: -2791351356463943124
237
+ hash: 3459217144726680577
234
238
  segments:
235
239
  - 0
236
240
  version: "0"
@@ -252,5 +256,9 @@ test_files:
252
256
  - spec/ironfan/cluster_spec.rb
253
257
  - spec/ironfan/ec2/cloud_provider_spec.rb
254
258
  - spec/ironfan/ec2/security_group_spec.rb
259
+ - spec/ironfan/ec2/cloud_provider.rb
260
+ - spec/chef/cluster_bootstrap_spec.rb
261
+ - spec/fixtures/gunbai_slice.json
262
+ - spec/fixtures/gunbai.rb
255
263
  - spec/spec_helper.rb
256
264
  - spec/test_config.rb