ironfan 3.2.2 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. data/CHANGELOG.md +5 -0
  2. data/VERSION +1 -1
  3. data/ironfan.gemspec +33 -20
  4. data/lib/chef/knife/cluster_kick.rb +17 -17
  5. data/lib/chef/knife/cluster_kill.rb +13 -7
  6. data/lib/chef/knife/cluster_launch.rb +60 -66
  7. data/lib/chef/knife/cluster_pry.rb +2 -2
  8. data/lib/chef/knife/cluster_show.rb +3 -6
  9. data/lib/chef/knife/cluster_ssh.rb +5 -11
  10. data/lib/chef/knife/cluster_start.rb +2 -4
  11. data/lib/chef/knife/cluster_stop.rb +1 -3
  12. data/lib/chef/knife/cluster_sync.rb +13 -21
  13. data/lib/chef/knife/ironfan_knife_common.rb +11 -9
  14. data/lib/chef/knife/ironfan_script.rb +2 -1
  15. data/lib/gorillib/resolution.rb +119 -0
  16. data/lib/ironfan/broker/computer.rb +316 -0
  17. data/lib/ironfan/broker/drive.rb +21 -0
  18. data/lib/ironfan/broker.rb +37 -0
  19. data/lib/ironfan/builder.rb +14 -0
  20. data/lib/ironfan/deprecated.rb +16 -58
  21. data/lib/ironfan/dsl/cloud.rb +21 -0
  22. data/lib/ironfan/dsl/cluster.rb +27 -0
  23. data/lib/ironfan/dsl/compute.rb +84 -0
  24. data/lib/ironfan/dsl/ec2.rb +260 -0
  25. data/lib/ironfan/dsl/facet.rb +25 -0
  26. data/lib/ironfan/dsl/role.rb +19 -0
  27. data/lib/ironfan/dsl/server.rb +31 -0
  28. data/lib/ironfan/dsl/virtualbox.rb +8 -0
  29. data/lib/ironfan/dsl/volume.rb +45 -0
  30. data/lib/ironfan/dsl.rb +7 -0
  31. data/lib/ironfan/headers.rb +58 -0
  32. data/lib/ironfan/provider/chef/client.rb +77 -0
  33. data/lib/ironfan/provider/chef/node.rb +133 -0
  34. data/lib/ironfan/provider/chef/role.rb +69 -0
  35. data/lib/ironfan/provider/chef.rb +28 -0
  36. data/lib/ironfan/provider/ec2/ebs_volume.rb +137 -0
  37. data/lib/ironfan/provider/ec2/elastic_ip.rb +10 -0
  38. data/lib/ironfan/provider/ec2/key_pair.rb +65 -0
  39. data/lib/ironfan/provider/ec2/machine.rb +258 -0
  40. data/lib/ironfan/provider/ec2/placement_group.rb +24 -0
  41. data/lib/ironfan/provider/ec2/security_group.rb +118 -0
  42. data/lib/ironfan/provider/ec2.rb +47 -0
  43. data/lib/ironfan/provider/virtualbox/machine.rb +10 -0
  44. data/lib/ironfan/provider/virtualbox.rb +8 -0
  45. data/lib/ironfan/provider.rb +139 -0
  46. data/lib/ironfan/requirements.rb +52 -0
  47. data/lib/ironfan.rb +44 -33
  48. metadata +34 -21
  49. data/lib/chef/knife/cluster_vagrant.rb +0 -144
  50. data/lib/chef/knife/vagrant/ironfan_environment.rb +0 -18
  51. data/lib/chef/knife/vagrant/ironfan_provisioners.rb +0 -27
  52. data/lib/chef/knife/vagrant/skeleton_vagrantfile.rb +0 -119
  53. data/lib/ironfan/chef_layer.rb +0 -300
  54. data/lib/ironfan/cloud.rb +0 -323
  55. data/lib/ironfan/cluster.rb +0 -118
  56. data/lib/ironfan/compute.rb +0 -139
  57. data/lib/ironfan/discovery.rb +0 -190
  58. data/lib/ironfan/dsl_builder.rb +0 -99
  59. data/lib/ironfan/facet.rb +0 -143
  60. data/lib/ironfan/fog_layer.rb +0 -196
  61. data/lib/ironfan/private_key.rb +0 -130
  62. data/lib/ironfan/role_implications.rb +0 -58
  63. data/lib/ironfan/security_group.rb +0 -133
  64. data/lib/ironfan/server.rb +0 -291
  65. data/lib/ironfan/server_slice.rb +0 -265
  66. data/lib/ironfan/volume.rb +0 -146
@@ -1,196 +0,0 @@
1
- module Ironfan
2
- #
3
- # Ironfan::Server methods that handle Fog action
4
- #
5
- Server.class_eval do
6
-
7
- def fog_create_server
8
- step(" creating cloud server", :green)
9
- lint_fog
10
- launch_desc = fog_launch_description
11
- Chef::Log.debug(JSON.pretty_generate(launch_desc))
12
- safely do
13
- @fog_server = Ironfan.fog_connection.servers.create(launch_desc)
14
- end
15
- end
16
-
17
- def lint_fog
18
- unless cloud.image_id then raise "No image ID found: nothing in Chef::Config[:ec2_image_info] for AZ #{self.default_availability_zone} flavor #{cloud.flavor} backing #{cloud.backing} image name #{cloud.image_name}, and cloud.image_id was not set directly. See https://github.com/infochimps-labs/ironfan/wiki/machine-image-(AMI)-lookup-by-name - #{cloud.list_images}" end
19
- unless cloud.image_id then cloud.list_flavors ; raise "No machine flavor found" ; end
20
- end
21
-
22
- def fog_launch_description
23
- user_data_hsh =
24
- if client_key.body then cloud.user_data.merge({ :client_key => client_key.body })
25
- else cloud.user_data.merge({ :validation_key => cloud.validation_key }) ; end
26
- #
27
- description = {
28
- :image_id => cloud.image_id,
29
- :flavor_id => cloud.flavor,
30
- :vpc_id => cloud.vpc,
31
- :subnet_id => cloud.subnet,
32
- :groups => cloud.security_groups.keys,
33
- :key_name => cloud.keypair.to_s,
34
- # Fog does not actually create tags when it creates a server.
35
- :tags => {
36
- :name => self.fullname,
37
- :cluster => cluster_name,
38
- :facet => facet_name,
39
- :index => facet_index, },
40
- :user_data => JSON.pretty_generate(user_data_hsh),
41
- :block_device_mapping => block_device_mapping,
42
- :availability_zone => self.default_availability_zone,
43
- :monitoring => cloud.monitoring,
44
- # permanence is applied during sync
45
- }
46
- if needs_placement_group?
47
- ui.warn "1.3.1 and earlier versions of Fog don't correctly support placement groups, so your nodes will land willy-nilly. We're working on a fix"
48
- description[:placement] = { 'groupName' => cloud.placement_group.to_s }
49
- end
50
- description
51
- end
52
-
53
- #
54
- # Takes key-value pairs and idempotently sets those tags on the cloud machine
55
- #
56
- def fog_create_tags(fog_obj, desc, tags)
57
- tags['Name'] ||= tags['name'] if tags.has_key?('name')
58
- tags_to_create = tags.reject{|key, val| fog_obj.tags[key] == val.to_s }
59
- return if tags_to_create.empty?
60
- step(" tagging #{desc} with #{tags_to_create.inspect}", :green)
61
- tags_to_create.each do |key, value|
62
- Chef::Log.debug( "tagging #{desc} with #{key} = #{value}" )
63
- safely do
64
- Ironfan.fog_connection.tags.create({
65
- :key => key, :value => value.to_s, :resource_id => fog_obj.id })
66
- end
67
- end
68
- end
69
-
70
- def fog_address
71
- address_str = self.cloud.public_ip or return
72
- Ironfan.fog_addresses[address_str]
73
- end
74
-
75
- def discover_volumes!
76
- result = self.class.fields[:volumes].type.new
77
- volumes.each_pair do |vol_name, definition|
78
- next if definition.fog_volume
79
- next if Ironfan.chef_config[:cloud] == false
80
- vol = definition.dup
81
- vol.fog_volume = Ironfan.fog_volumes.find do |fv|
82
- ( # matches the explicit volume id
83
- (vol.volume_id && (fv.id == vol.volume_id) ) ||
84
- # OR this server's machine exists, and this volume is attached to
85
- # it, and in the right place
86
- ( fog_server && fv.server_id && vol.device &&
87
- (fv.server_id == fog_server.id) &&
88
- (fv.device.to_s == vol.device.to_s) ) ||
89
- # OR this volume is tagged as belonging to this machine
90
- ( fv.tags.present? &&
91
- (fv.tags['server'] == self.fullname) &&
92
- (fv.tags['device'] == vol.device.to_s) )
93
- )
94
- end
95
- next unless vol.fog_volume
96
- vol.volume_id(vol.fog_volume.id) unless vol.volume_id.present?
97
- vol.availability_zone(vol.fog_volume.availability_zone) unless vol.availability_zone.present?
98
- check_server_id_pairing(vol.fog_volume, vol.desc)
99
- result[vol.name] = vol
100
- end
101
- write_attribute(:volumes,result)
102
- end
103
-
104
- def attach_volumes
105
- return unless in_cloud?
106
- discover_volumes!
107
- return if volumes.empty?
108
- step(" attaching volumes")
109
- volumes.each_pair do |vol_name, vol|
110
- next if vol.volume_id.blank? || (vol.attachable != :ebs)
111
- if (not vol.in_cloud?) then Chef::Log.debug("Volume not found: #{vol.desc}") ; next ; end
112
- if (vol.has_server?) then check_server_id_pairing(vol.fog_volume, vol.desc) ; next ; end
113
- step(" - attaching #{vol.desc} -- #{vol.inspect}", :blue)
114
- safely do
115
- vol.fog_volume.device = vol.device
116
- vol.fog_volume.server = fog_server
117
- end
118
- end
119
- end
120
-
121
- def ensure_placement_group
122
- return unless needs_placement_group?
123
- pg_name = cloud.placement_group.to_s
124
- desc = "placement group #{pg_name} for #{self.fullname} (vs #{Ironfan.placement_groups.inspect}"
125
- return if Ironfan.placement_groups.include?(pg_name)
126
- safely do
127
- step(" creating #{desc}", :blue)
128
- unless_dry_run{ Ironfan.fog_connection.create_placement_group(pg_name, 'cluster') }
129
- Ironfan.placement_groups[pg_name] = { 'groupName' => pg_name, 'strategy' => 'cluster' }
130
- end
131
- pg_name
132
- end
133
-
134
- def needs_placement_group?
135
- cloud.flavor_info[:placement_groupable]
136
- end
137
-
138
- def associate_public_ip
139
- address = self.cloud.public_ip
140
- return unless self.in_cloud? && address
141
- desc = "elastic ip #{address} for #{self.fullname}"
142
- if (fog_address && fog_address.server_id) then check_server_id_pairing(fog_address, desc) ; return ; end
143
- safely do
144
- step(" assigning #{desc}", :blue)
145
- Ironfan.fog_connection.associate_address(self.fog_server.id, address)
146
- end
147
- end
148
-
149
- def check_server_id_pairing thing, desc
150
- return unless thing && thing.server_id && self.in_cloud?
151
- type_of_thing = thing.class.to_s.gsub(/.*::/,"")
152
- if thing.server_id != self.fog_server.id
153
- ui.warn "#{type_of_thing} mismatch: #{desc} is on #{thing.server_id} not #{self.fog_server.id}: #{thing.inspect.gsub(/\s+/m,' ')}"
154
- false
155
- else
156
- Chef::Log.debug("#{type_of_thing} paired: #{desc}")
157
- true
158
- end
159
- end
160
-
161
- def set_instance_attributes
162
- return unless self.in_cloud? && (not self.cloud.permanent.nil?)
163
- desc = "termination flag #{permanent?} for #{self.fullname}"
164
- # the EC2 API does not surface disable_api_termination as a value, so we
165
- # have to set it every time.
166
- safely do
167
- step(" setting #{desc}", :blue)
168
- unless_dry_run do
169
- Ironfan.fog_connection.modify_instance_attribute(self.fog_server.id, {
170
- 'DisableApiTermination.Value' => permanent?, })
171
- end
172
- true
173
- end
174
- end
175
-
176
- end
177
-
178
- class ServerSlice
179
- def sync_keypairs
180
- step("ensuring keypairs exist")
181
- keypairs = servers.map{|svr| [svr.cluster.cloud.keypair, svr.cloud.keypair] }.flatten.map(&:to_s).reject(&:blank?).uniq
182
- keypairs = keypairs - Ironfan.fog_keypairs.keys
183
- keypairs.each do |keypair_name|
184
- keypair_obj = Ironfan::Ec2Keypair.create!(keypair_name)
185
- Ironfan.fog_keypairs[keypair_name] = keypair_obj
186
- end
187
- end
188
-
189
- # Create security groups, their dependencies, and synchronize their permissions
190
- def sync_security_groups
191
- step("ensuring security groups exist and are correct")
192
- security_groups.each{|name,group| group.run }
193
- end
194
-
195
- end
196
- end
@@ -1,130 +0,0 @@
1
- require 'fileutils'
2
-
3
- module Ironfan
4
- #
5
- # A private key -- chef client key, ssh key, etc.
6
- #
7
- # The key is a pro
8
- class PrivateKey < Ironfan::DslBuilder
9
- attr_reader :name
10
- attr_reader :proxy
11
- attr_reader :on_update
12
-
13
- #
14
- # PrivateKey.new('bob')
15
- #
16
- # @yield a block, executed in caller's context, when the body is updated
17
- # @yieldparam the updated body
18
- def initialize(name, proxy=nil, &on_update)
19
- @name = name
20
- @proxy = proxy
21
- @on_update = on_update
22
- end
23
-
24
- def filename
25
- File.join(key_dir, "#{name}.pem")
26
- end
27
-
28
- def save
29
- return unless @body
30
- if Ironfan.chef_config[:dry_run]
31
- Chef::Log.debug(" key #{name} - dry run, not writing out key")
32
- return
33
- end
34
- ui.info( " key #{name} - writing to #{filename}" )
35
- FileUtils.mkdir_p(File.dirname(filename))
36
- File.open(filename, "w", 0600){|f| f.print( @body ) }
37
- end
38
-
39
- def load
40
- return unless File.exists?(filename)
41
- self.body = File.read(filename).chomp
42
- end
43
-
44
- def body=(content)
45
- @body = content
46
- on_update.call(content) if on_update
47
- content
48
- end
49
-
50
- def self.create!(name, *args, &block)
51
- obj = self.new(name, *args, &block)
52
- obj.create_proxy!
53
- obj
54
- end
55
-
56
- def to_s
57
- [super[0..-2], @name, @proxy, @body.to_s[32..64], '...', @body.to_s[-60..-30]].join(" ").gsub(/[\r\n\t]+/,'') + '>'
58
- end
59
- end
60
-
61
- class ChefClientKey < PrivateKey
62
- def body
63
- return @body if @body
64
- if proxy && proxy.private_key && (not proxy.private_key.empty?)
65
- @body = proxy.private_key
66
- else
67
- load
68
- end
69
- @body
70
- end
71
-
72
- def key_dir
73
- Chef::Config.client_key_dir || "/tmp/#{ENV['USER']}-client_keys"
74
- end
75
- end
76
-
77
- class DataBagKey < PrivateKey
78
- def body
79
- return @body if @body
80
- @body
81
- end
82
-
83
- def random_token
84
- require "digest/sha2"
85
- digest = Digest::SHA512.hexdigest( Time.now.to_s + (1..10).collect{ rand.to_s }.join )
86
- 5.times{ digest = Digest::SHA512.hexdigest(digest) }
87
- digest
88
- end
89
-
90
- def key_dir
91
- return Chef::Config.data_bag_key_dir if Chef::Config.data_bag_key_dir
92
- dir = "#{ENV['HOME']}/.chef/credentials/data_bag_keys"
93
- warn "Please set 'data_bag_key_dir' in your knife.rb. Will use #{dir} as a default"
94
- dir
95
- end
96
- end
97
-
98
- class Ec2Keypair < PrivateKey
99
- def body
100
- return @body if @body
101
- if proxy && proxy.private_key && (not proxy.private_key.empty?)
102
- @body = proxy.private_key
103
- else
104
- load
105
- end
106
- @body
107
- end
108
-
109
- def create_proxy!
110
- safely do
111
- step(" key #{name} - creating", :green)
112
- @proxy = Ironfan.fog_connection.key_pairs.create(:name => name.to_s)
113
- end
114
- Ironfan.fog_keypairs[name] = proxy
115
- self.body = proxy.private_key
116
- save
117
- end
118
-
119
- def key_dir
120
- if Chef::Config.ec2_key_dir
121
- return Chef::Config.ec2_key_dir
122
- else
123
- dir = "#{ENV['HOME']}/.chef/credentials/ec2_keys"
124
- warn "Please set 'ec2_key_dir' in your knife.rb. Will use #{dir} as a default"
125
- dir
126
- end
127
- end
128
- end
129
-
130
- end
@@ -1,58 +0,0 @@
1
- module Ironfan
2
- ComputeBuilder.class_eval do
3
-
4
- # organization-wide security group
5
- role_implication "systemwide" do
6
- self.cloud.security_group "systemwide" do
7
- end
8
- end
9
-
10
- # NFS server allows access from nfs_clients
11
- role_implication "nfs_server" do
12
- self.cloud.security_group "nfs_server" do
13
- authorize_group "nfs_client"
14
- end
15
- end
16
-
17
- role_implication "nfs_client" do
18
- self.cloud.security_group "nfs_client"
19
- end
20
-
21
- # Opens port 22 to the world
22
- role_implication "ssh" do
23
- self.cloud.security_group 'ssh' do
24
- authorize_port_range 22..22
25
- end
26
- end
27
-
28
- # Open the Chef server API port (4000) and the webui (4040)
29
- role_implication "chef_server" do
30
- self.cloud.security_group "chef_server" do
31
- authorize_port_range 4000..4000 # chef-server-api
32
- authorize_port_range 4040..4040 # chef-server-webui
33
- end
34
- end
35
-
36
- # web server? add the group "web_server" to open the web holes
37
- role_implication "web_server" do
38
- self.cloud.security_group("#{cluster_name}-web_server") do
39
- authorize_port_range 80..80
40
- authorize_port_range 443..443
41
- end
42
- end
43
-
44
- # if you're a redis server, open the port and authorize redis clients in your group to talk to you
45
- role_implication("redis_server") do
46
- cluster_name = self.cluster_name # hack: put cluster_name is in scope
47
- self.cloud.security_group("#{cluster_name}-redis_server") do
48
- authorize_group("#{cluster_name}-redis_client")
49
- end
50
- end
51
-
52
- # redis_clients gain rights to the redis_server
53
- role_implication("redis_client") do
54
- self.cloud.security_group("#{cluster_name}-redis_client")
55
- end
56
-
57
- end
58
- end
@@ -1,133 +0,0 @@
1
- module Ironfan
2
- module CloudDsl
3
- class SecurityGroup < Ironfan::DslBuilder
4
- magic :name, String
5
- magic :description, String
6
- magic :group_authorizations, Array
7
- magic :group_authorized_by, Array
8
- magic :range_authorizations, Array
9
-
10
- def initialize params
11
- name params[:name].to_s
12
- description "ironfan generated group #{name}"
13
- group_authorizations []
14
- group_authorized_by []
15
- range_authorizations []
16
- end
17
-
18
- def to_key
19
- name
20
- end
21
-
22
- @@all = nil
23
- def all
24
- self.class.all
25
- end
26
- def self.all
27
- return @@all if @@all
28
- get_all
29
- end
30
- def self.get_all
31
- groups_list = Ironfan.fog_connection.security_groups.all
32
- @@all = groups_list.inject(Mash.new) do |hsh, fog_group|
33
- # AWS security_groups are strangely case sensitive, allowing upper-case but colliding regardless
34
- # of the case. This forces all names to lowercase, and matches against that below.
35
- # See https://github.com/infochimps-labs/ironfan/pull/86 for more details.
36
- hsh[fog_group.name.downcase] = fog_group ; hsh
37
- end
38
- end
39
-
40
- def get
41
- all[name] || Ironfan.fog_connection.security_groups.get(name)
42
- end
43
-
44
- def self.get_or_create(group_name, description)
45
- group_name = group_name.to_s.downcase
46
- # FIXME: the '|| Ironfan.fog' part is probably unnecessary
47
- fog_group = all[group_name] || Ironfan.fog_connection.security_groups.get(group_name)
48
- unless fog_group
49
- self.step(group_name, "creating (#{description})", :green)
50
- fog_group = all[group_name] = Ironfan.fog_connection.security_groups.new(:name => group_name, :description => description, :connection => Ironfan.fog_connection)
51
- fog_group.save
52
- end
53
- fog_group
54
- end
55
-
56
- def authorize_group(group_name, owner_id=nil)
57
- group_authorizations << [group_name.to_s, owner_id]
58
- end
59
-
60
- def authorized_by_group(other_name)
61
- group_authorized_by << other_name.to_s
62
- end
63
-
64
- def authorize_port_range(range, cidr_ip = '0.0.0.0/0', ip_protocol = 'tcp')
65
- range = (range .. range) if range.is_a?(Integer)
66
- range_authorizations << [range, cidr_ip, ip_protocol]
67
- end
68
- #
69
- # def group_permission_already_set?(fog_group, other_name, authed_owner)
70
- # return false if fog_group.ip_permissions.nil?
71
- # fog_group.ip_permissions.any? do |existing_permission|
72
- # existing_permission["groups"].include?({"userId" => authed_owner, "groupName" => other_name}) &&
73
- # existing_permission["fromPort"] == 1 &&
74
- # existing_permission["toPort"] == 65535
75
- # end
76
- # end
77
- #
78
- # def range_permission_already_set?(fog_group, range, cidr_ip, ip_protocol)
79
- # return false if fog_group.ip_permissions.nil?
80
- # fog_group.ip_permissions.include?(
81
- # { "groups"=>[], "ipRanges"=>[{"cidrIp"=>cidr_ip}],
82
- # "ipProtocol"=>ip_protocol, "fromPort"=>range.first, "toPort"=>range.last})
83
- # end
84
- #
85
- # FIXME: so if you're saying to yourself, "self, this is some soupy gooey
86
- # code right here" then you and your self are correct. Much of this is to
87
- # work around old limitations in the EC2 api. You can now treat range and
88
- # group permissions the same, and we should.
89
-
90
- def run
91
- fog_group = self.class.get_or_create(name, description)
92
- @group_authorizations.uniq.each do |other_name, authed_owner|
93
- authed_owner ||= self.owner_id
94
- next if group_permission_already_set?(fog_group, other_name, authed_owner)
95
- step("authorizing access from all machines in #{other_name} to #{name}", :blue)
96
- self.class.get_or_create(other_name, "Authorized to access #{name}")
97
- begin fog_group.authorize_group_and_owner(other_name, authed_owner)
98
- rescue StandardError => err ; handle_security_group_error(err) ; end
99
- end
100
- @group_authorized_by.uniq.each do |other_name|
101
- authed_owner = self.owner_id
102
- other_group = self.class.get_or_create(other_name, "Authorized for access by #{self.name}")
103
- next if group_permission_already_set?(other_group, self.name, authed_owner)
104
- step("authorizing access to all machines in #{other_name} from #{name}", :blue)
105
- begin other_group.authorize_group_and_owner(self.name, authed_owner)
106
- rescue StandardError => err ; handle_security_group_error(err) ; end
107
- end
108
- @range_authorizations.uniq.each do |range, cidr_ip, ip_protocol|
109
- next if range_permission_already_set?(fog_group, range, cidr_ip, ip_protocol)
110
- step("opening #{ip_protocol} ports #{range} to #{cidr_ip}", :blue)
111
- begin fog_group.authorize_port_range(range, { :cidr_ip => cidr_ip, :ip_protocol => ip_protocol })
112
- rescue StandardError => err ; handle_security_group_error(err) ; end
113
- end
114
- end
115
- #
116
- # def handle_security_group_error(err)
117
- # if (/has already been authorized/ =~ err.to_s)
118
- # Chef::Log.debug err
119
- # else
120
- # ui.warn(err)
121
- # end
122
- # end
123
- #
124
- def self.step(group_name, desc, *style)
125
- ui.info(" group #{"%-15s" % (group_name+":")}\t#{ui.color(desc.to_s, *style)}")
126
- end
127
- def step(desc, *style)
128
- self.class.step(self.name, desc, *style)
129
- end
130
- end
131
-
132
- end
133
- end