ironfan 3.2.2 → 4.0.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.
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