ironfan 4.6.2 → 4.7.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +7 -0
- data/VERSION +1 -1
- data/ironfan.gemspec +2 -2
- data/lib/chef/knife/cluster_kill.rb +4 -0
- data/lib/chef/knife/cluster_launch.rb +5 -0
- data/lib/chef/knife/cluster_proxy.rb +4 -0
- data/lib/chef/knife/cluster_sync.rb +4 -0
- data/lib/chef/knife/ironfan_script.rb +13 -0
- data/lib/ironfan/broker/computer.rb +11 -3
- data/lib/ironfan/provider.rb +9 -2
- data/lib/ironfan/provider/ec2/keypair.rb +3 -2
- data/lib/ironfan/provider/ec2/security_group.rb +123 -76
- data/spec/integration/spec/simple_cluster_spec.rb +21 -1
- metadata +3 -3
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,10 @@
|
|
1
|
+
# v4.7.0:
|
2
|
+
(@nickmarden rocks the house again)
|
3
|
+
* Added support for "prepare" phase, prior to any machine-specific actions
|
4
|
+
* Move security group creation and authorization assurance to prepare phase (fixes #189)
|
5
|
+
* Allow user/group-style security group references (fixes #207)
|
6
|
+
* Move keypair creation to prepare phase
|
7
|
+
|
1
8
|
# v4.6.2:
|
2
9
|
* Added a -f/--with-facet option to knife cluster list
|
3
10
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
4.
|
1
|
+
4.7.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.
|
8
|
+
s.version = "4.7.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-12-
|
12
|
+
s.date = "2012-12-14"
|
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 = [
|
@@ -82,6 +82,11 @@ class Chef
|
|
82
82
|
section("Syncing to chef")
|
83
83
|
target.save :providers => :chef
|
84
84
|
|
85
|
+
unless target.empty?
|
86
|
+
ui.info "Preparing shared resources:"
|
87
|
+
all_computers(*@name_args).prepare
|
88
|
+
end
|
89
|
+
|
85
90
|
# Launch computers
|
86
91
|
ui.info("")
|
87
92
|
section("Launching computers", :green)
|
@@ -44,6 +44,11 @@ module Ironfan
|
|
44
44
|
|
45
45
|
target = get_relevant_slice(* @name_args)
|
46
46
|
|
47
|
+
if prepares? and (prepares_on_noop? or not target.empty?)
|
48
|
+
ui.info "Preparing shared resources:"
|
49
|
+
all_computers(*@name_args).prepare
|
50
|
+
end
|
51
|
+
|
47
52
|
unless target.empty?
|
48
53
|
ui.info(["\n",
|
49
54
|
ui.color("Running #{sub_command}", :cyan),
|
@@ -76,6 +81,14 @@ module Ironfan
|
|
76
81
|
target.send(sub_command)
|
77
82
|
end
|
78
83
|
|
84
|
+
def prepares?
|
85
|
+
true
|
86
|
+
end
|
87
|
+
|
88
|
+
def prepares_on_noop?
|
89
|
+
false
|
90
|
+
end
|
91
|
+
|
79
92
|
def aggregates?
|
80
93
|
true
|
81
94
|
end
|
@@ -298,11 +298,19 @@ module Ironfan
|
|
298
298
|
values.map {|c| c.providers.values}.flatten.uniq.each {|p| p.validate computers }
|
299
299
|
end
|
300
300
|
|
301
|
-
def
|
301
|
+
def group_action(verb)
|
302
302
|
computers = self
|
303
|
-
provider_keys = values.map {|c| c.chosen_providers({ :providers => :iaas})}.flatten.uniq
|
303
|
+
provider_keys = values.map {|c| c.chosen_providers({ :providers => :iaas })}.flatten.uniq
|
304
304
|
providers = provider_keys.map { |pk| values.map { |c| c.providers[pk] } }.flatten.compact.uniq
|
305
|
-
providers.each { |p| p.
|
305
|
+
providers.each { |p| p.send(verb, computers) }
|
306
|
+
end
|
307
|
+
|
308
|
+
def prepare
|
309
|
+
group_action(:prepare!)
|
310
|
+
end
|
311
|
+
|
312
|
+
def aggregate
|
313
|
+
group_action(:aggregate!)
|
306
314
|
end
|
307
315
|
|
308
316
|
#
|
data/lib/ironfan/provider.rb
CHANGED
@@ -42,6 +42,12 @@ module Ironfan
|
|
42
42
|
resources.each {|r| r.validate_resources! computers }
|
43
43
|
end
|
44
44
|
|
45
|
+
def self.prepare!(computers)
|
46
|
+
resources.each do |r|
|
47
|
+
r.prepare!(computers) if r.shared?
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
45
51
|
def self.aggregate!(computers)
|
46
52
|
resources.each do |r|
|
47
53
|
r.aggregate!(computers) if r.shared?
|
@@ -87,14 +93,15 @@ module Ironfan
|
|
87
93
|
#
|
88
94
|
def self.create!(*p) Ironfan.noop(self,__method__,*p); end
|
89
95
|
def self.save!(*p) Ironfan.noop(self,__method__,*p); end
|
96
|
+
def self.prepare!(*p) Ironfan.noop(self,__method__,*p); end
|
90
97
|
def self.aggregate!(*p) Ironfan.noop(self,__method__,*p); end
|
91
98
|
def self.destroy!(*p) Ironfan.noop(self,__method__,*p); end
|
92
99
|
|
93
100
|
#
|
94
101
|
# Utilities
|
95
102
|
#
|
96
|
-
[:shared?, :multiple?, :load!,:validate_computer!,
|
97
|
-
:
|
103
|
+
[:shared?, :multiple?, :load!,:validate_computer!, :validate_resources!,
|
104
|
+
:create!, :save!, :prepare!, :aggregate!, :destroy!].each do |method_name|
|
98
105
|
define_method(method_name) {|*p| self.class.send(method_name,*p) }
|
99
106
|
end
|
100
107
|
|
@@ -50,8 +50,9 @@ module Ironfan
|
|
50
50
|
# Manipulation
|
51
51
|
#
|
52
52
|
|
53
|
-
def self.
|
54
|
-
|
53
|
+
def self.prepare!(computers)
|
54
|
+
return if computers.empty?
|
55
|
+
name = computers.values[0].server.cluster_name
|
55
56
|
return if recall? name
|
56
57
|
Ironfan.step(name, "creating key pair for #{name}", :blue)
|
57
58
|
result = Ec2.connection.create_key_pair(name)
|
@@ -20,22 +20,18 @@ module Ironfan
|
|
20
20
|
def self.resource_type() :security_group; end
|
21
21
|
def self.expected_ids(computer)
|
22
22
|
ec2 = computer.server.cloud(:ec2)
|
23
|
-
ec2.security_groups.keys.map
|
24
|
-
ec2.vpc ? "#{ec2.vpc}:#{name.to_s}" : name.to_s
|
25
|
-
end.uniq
|
23
|
+
ec2.security_groups.keys.map { |name| group_name_with_vpc(name,ec2.vpc) }.uniq
|
26
24
|
end
|
27
25
|
|
28
26
|
def name()
|
29
|
-
|
30
|
-
"#{adaptee.vpc_id}:#{adaptee.name}"
|
27
|
+
self.class.group_name_with_vpc(adaptee.name, adaptee.vpc_id)
|
31
28
|
end
|
32
29
|
|
33
30
|
#
|
34
31
|
# Discovery
|
35
32
|
#
|
36
33
|
def self.load!(cluster=nil)
|
37
|
-
Ec2.connection.security_groups.each do |raw|
|
38
|
-
next if raw.blank?
|
34
|
+
Ec2.connection.security_groups.reject { |raw| raw.blank? }.each do |raw|
|
39
35
|
sg = SecurityGroup.new(:adaptee => raw)
|
40
36
|
remember(sg)
|
41
37
|
Chef::Log.debug("Loaded #{sg}: #{sg.inspect}")
|
@@ -66,75 +62,119 @@ module Ironfan
|
|
66
62
|
# Manipulation
|
67
63
|
#
|
68
64
|
|
69
|
-
def self.
|
70
|
-
|
65
|
+
def self.prepare!(computers)
|
66
|
+
|
67
|
+
# Create any groups that don't yet exist, and ensure any authorizations
|
68
|
+
# that are required for those groups
|
69
|
+
cluster_name = nil
|
70
|
+
groups_to_create = [ ]
|
71
|
+
authorizations_to_ensure = [ ]
|
72
|
+
|
73
|
+
# First, deduce the list of all groups to which at least one instance belongs
|
74
|
+
# We'll use this later to decide whether to create groups, or authorize access,
|
75
|
+
# using a VPC security group or an EC2 security group.
|
76
|
+
groups_that_should_exist = computers.map { |c| expected_ids(c) }.flatten.sort.uniq
|
77
|
+
groups_to_create << groups_that_should_exist
|
78
|
+
|
79
|
+
computers.select { |computer| Ec2.applicable computer }.each do |computer|
|
80
|
+
ensure_groups(computer) # Add facet and cluster security groups for the computer
|
81
|
+
cloud = computer.server.cloud(:ec2)
|
82
|
+
cluster_name = computer.server.cluster_name
|
83
|
+
|
84
|
+
# Iterate over all of the security group information, keeping track of
|
85
|
+
# any groups that must exist and any authorizations that must be ensured
|
86
|
+
cloud.security_groups.values.each do |dsl_group|
|
87
|
+
|
88
|
+
groups_to_create << dsl_group.name
|
89
|
+
|
90
|
+
groups_to_create << dsl_group.group_authorized.map do |other_group|
|
91
|
+
most_appropriate_group_name(other_group, cloud.vpc, groups_that_should_exist)
|
92
|
+
end
|
93
|
+
|
94
|
+
groups_to_create << dsl_group.group_authorized_by.map do |other_group|
|
95
|
+
most_appropriate_group_name(other_group, cloud.vpc, groups_that_should_exist)
|
96
|
+
end
|
97
|
+
|
98
|
+
authorizations_to_ensure << dsl_group.group_authorized.map do |other_group|
|
99
|
+
{
|
100
|
+
:grantor => most_appropriate_group_name(dsl_group.name, cloud.vpc, groups_that_should_exist),
|
101
|
+
:grantee => most_appropriate_group_name(other_group, cloud.vpc, groups_that_should_exist),
|
102
|
+
:grantee_type => :group,
|
103
|
+
:range => WIDE_OPEN,
|
104
|
+
}
|
105
|
+
end
|
106
|
+
|
107
|
+
authorizations_to_ensure << dsl_group.group_authorized_by.map do |other_group|
|
108
|
+
{
|
109
|
+
:grantor => most_appropriate_group_name(other_group, cloud.vpc, groups_that_should_exist),
|
110
|
+
:grantee => most_appropriate_group_name(dsl_group.name, cloud.vpc, groups_that_should_exist),
|
111
|
+
:grantee_type => :group,
|
112
|
+
:range => WIDE_OPEN,
|
113
|
+
}
|
114
|
+
end
|
115
|
+
|
116
|
+
authorizations_to_ensure << dsl_group.range_authorizations.map do |range_auth|
|
117
|
+
range, cidr, protocol = range_auth
|
118
|
+
{
|
119
|
+
:grantor => group_name_with_vpc(dsl_group.name, cloud.vpc),
|
120
|
+
:grantee => { :cidr_ip => cidr, :ip_protocol => protocol },
|
121
|
+
:grantee_type => :cidr,
|
122
|
+
:range => range,
|
123
|
+
}
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
groups_to_create = groups_to_create.flatten.uniq.reject { |group| recall? group.to_s }.sort
|
128
|
+
authorizations_to_ensure = authorizations_to_ensure.flatten.uniq.sort { |a,b| a[:grantor] <=> b[:grantor] }
|
129
|
+
|
130
|
+
Ironfan.step(cluster_name, "creating security groups", :blue) unless groups_to_create.empty?
|
131
|
+
groups_to_create.each do |group|
|
132
|
+
if group =~ /\//
|
133
|
+
Ironfan.step(group, " assuming that owner/group pair #{group} already exists", :blue)
|
134
|
+
else
|
135
|
+
Ironfan.step(group, " creating #{group} security group", :blue)
|
136
|
+
begin
|
137
|
+
tokens = group.to_s.split(':')
|
138
|
+
group_id = tokens.pop
|
139
|
+
vpc_id = tokens.pop
|
140
|
+
Ec2.connection.create_security_group(group_id,"Ironfan created group #{group_id}",vpc_id)
|
141
|
+
rescue Fog::Compute::AWS::Error => e # InvalidPermission.Duplicate
|
142
|
+
Chef::Log.info("ignoring security group error: #{e}")
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
71
146
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
147
|
+
# Re-load everything so that we have a @@known list of security groups to manipulate
|
148
|
+
load! unless groups_to_create.empty?
|
149
|
+
|
150
|
+
# Now make sure that all required authorizations are present
|
151
|
+
Ironfan.step(cluster_name, "ensuring security group permissions", :blue) unless authorizations_to_ensure.empty?
|
152
|
+
authorizations_to_ensure.each do |auth|
|
153
|
+
grantor_fog = recall(auth[:grantor])
|
154
|
+
if :group == auth[:grantee_type]
|
155
|
+
if fog_grantee = recall(auth[:grantee])
|
156
|
+
options = { :group => fog_grantee.group_id }
|
157
|
+
elsif auth[:grantee] =~ /\//
|
158
|
+
options = { :group_alias => auth[:grantee] }
|
159
|
+
else
|
160
|
+
raise "Don't know what to do with authorization grantee #{auth[:grantee]}"
|
161
|
+
end
|
162
|
+
message = " ensuring access from #{auth[:grantee]} to #{auth[:grantor]}"
|
163
|
+
else
|
164
|
+
options = auth[:grantee]
|
165
|
+
message = " ensuring #{auth[:grantee][:ip_protocol]} access from #{auth[:grantee][:cidr_ip]} to #{auth[:range]}"
|
89
166
|
end
|
167
|
+
Ironfan.step(auth[:grantor], message, :blue)
|
168
|
+
safely_authorize(grantor_fog, auth[:range], options)
|
90
169
|
end
|
91
|
-
load! # Get the native groups via reload
|
92
170
|
end
|
93
171
|
|
94
|
-
def self.
|
95
|
-
|
96
|
-
recall(group_name)
|
172
|
+
def self.group_name_with_vpc(name,vpc_id=nil)
|
173
|
+
vpc_id.nil? ? name.to_s : "#{vpc_id}:#{name.to_s}"
|
97
174
|
end
|
98
175
|
|
99
|
-
def self.
|
100
|
-
|
101
|
-
cloud = computer.server.cloud(:ec2)
|
102
|
-
|
103
|
-
create!(computer) # Make sure the security groups exist
|
104
|
-
security_groups = cloud.security_groups.values
|
105
|
-
dsl_groups = security_groups.select do |dsl_group|
|
106
|
-
not (recall_with_vpc(dsl_group,cloud.vpc)) and \
|
107
|
-
not (dsl_group.range_authorizations +
|
108
|
-
dsl_group.group_authorized_by +
|
109
|
-
dsl_group.group_authorized).empty?
|
110
|
-
end.compact
|
111
|
-
return if dsl_groups.empty?
|
112
|
-
|
113
|
-
Ironfan.step(computer.server.cluster_name, "ensuring security group permissions", :blue)
|
114
|
-
dsl_groups.each do |dsl_group|
|
115
|
-
dsl_group_fog = recall_with_vpc(dsl_group.name,cloud.vpc)
|
116
|
-
dsl_group.group_authorized.each do |other_group|
|
117
|
-
other_group_fog = recall_with_vpc(other_group,cloud.vpc)
|
118
|
-
Ironfan.step(dsl_group.name, " ensuring access from #{other_group}", :blue)
|
119
|
-
options = {:group => other_group_fog.group_id}
|
120
|
-
safely_authorize(dsl_group_fog, WIDE_OPEN, options)
|
121
|
-
end
|
122
|
-
|
123
|
-
dsl_group.group_authorized_by.each do |other_group|
|
124
|
-
other_group_fog = recall_with_vpc(other_group,cloud.vpc)
|
125
|
-
Ironfan.step(dsl_group.name, " ensuring access to #{other_group}", :blue)
|
126
|
-
options = {:group => dsl_group_fog.group_id}
|
127
|
-
safely_authorize(other_group_fog, WIDE_OPEN, options)
|
128
|
-
end
|
129
|
-
|
130
|
-
dsl_group.range_authorizations.each do |range_auth|
|
131
|
-
range, cidr, protocol = range_auth
|
132
|
-
step_message = " ensuring #{protocol} access from #{cidr} to #{range}"
|
133
|
-
Ironfan.step(dsl_group.name, step_message, :blue)
|
134
|
-
options = {:cidr_ip => cidr, :ip_protocol => protocol}
|
135
|
-
safely_authorize(dsl_group_fog, range, options)
|
136
|
-
end
|
137
|
-
end
|
176
|
+
def self.most_appropriate_group_name(group, vpc_id, all_valid_groups)
|
177
|
+
all_valid_groups.include?(group_name_with_vpc(group, vpc_id)) ? group_name_with_vpc(group, vpc_id) : group
|
138
178
|
end
|
139
179
|
|
140
180
|
#
|
@@ -156,20 +196,27 @@ module Ironfan
|
|
156
196
|
# Try an authorization, ignoring duplicates (this is easier than correlating).
|
157
197
|
# Do so for both TCP and UDP, unless only one is specified
|
158
198
|
def self.safely_authorize(fog_group,range,options)
|
159
|
-
|
199
|
+
if options[:group_alias]
|
200
|
+
owner, group = options[:group_alias].split(/\//)
|
201
|
+
self.patiently(fog_group.name, Fog::Compute::AWS::Error, :ignore => Proc.new { |e| e.message =~ /InvalidPermission\.Duplicate/ }) do
|
202
|
+
Ec2.connection.authorize_security_group_ingress(
|
203
|
+
'GroupName' => fog_group.name,
|
204
|
+
'SourceSecurityGroupName' => group,
|
205
|
+
'SourceSecurityGroupOwnerId' => owner
|
206
|
+
)
|
207
|
+
end
|
208
|
+
elsif options[:ip_protocol]
|
209
|
+
self.patiently(fog_group.name, Fog::Compute::AWS::Error, :ignore => Proc.new { |e| e.message =~ /InvalidPermission\.Duplicate/ }) do
|
210
|
+
fog_group.authorize_port_range(range,options)
|
211
|
+
end
|
212
|
+
else
|
160
213
|
safely_authorize(fog_group,range,options.merge(:ip_protocol => 'tcp'))
|
161
214
|
safely_authorize(fog_group,range,options.merge(:ip_protocol => 'udp'))
|
162
215
|
safely_authorize(fog_group,Range.new(-1,-1),options.merge(:ip_protocol => 'icmp')) if(range == WIDE_OPEN)
|
163
216
|
return
|
164
217
|
end
|
165
|
-
|
166
|
-
self.patiently(fog_group.name, Fog::Compute::AWS::Error, :ignore => Proc.new { |e| e.message =~ /InvalidPermission\.Duplicate/ }) do
|
167
|
-
fog_group.authorize_port_range(range,options)
|
168
|
-
end
|
169
|
-
|
170
218
|
end
|
171
219
|
end
|
172
|
-
|
173
220
|
end
|
174
221
|
end
|
175
222
|
end
|
@@ -17,6 +17,10 @@ Ironfan.cluster "simple" do
|
|
17
17
|
|
18
18
|
facet :web do
|
19
19
|
instances 1
|
20
|
+
cloud(:ec2).security_group(:web) do
|
21
|
+
authorize_group :web_clients
|
22
|
+
authorize_group 'amazon-elb/amazon-elb-sg'
|
23
|
+
end
|
20
24
|
end
|
21
25
|
|
22
26
|
facet :db do
|
@@ -35,7 +39,7 @@ launch_cluster 'simple' do |cluster, computers|
|
|
35
39
|
|
36
40
|
describe "the web facet security groups" do
|
37
41
|
subject { cluster.facets[:web].server(0).cloud(:ec2).security_groups.keys.map(&:to_s).sort }
|
38
|
-
it { should == %w[ simple simple-web ssh systemwide ] }
|
42
|
+
it { should == %w[ simple simple-web ssh systemwide web ] }
|
39
43
|
end
|
40
44
|
|
41
45
|
describe "the db facet security groups" do
|
@@ -43,6 +47,12 @@ launch_cluster 'simple' do |cluster, computers|
|
|
43
47
|
it { should == %w[ simple simple-db ssh systemwide ] }
|
44
48
|
end
|
45
49
|
|
50
|
+
describe "the passively created security groups" do
|
51
|
+
it "should include the :web_clients group" do
|
52
|
+
Ironfan::Provider::Ec2::SecurityGroup.recall('web_clients').should_not be_nil
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
46
56
|
describe "the cluster-wide security group" do
|
47
57
|
before :each do
|
48
58
|
@sg = Ironfan::Provider::Ec2::SecurityGroup.recall('simple')
|
@@ -76,7 +86,17 @@ launch_cluster 'simple' do |cluster, computers|
|
|
76
86
|
@ordered_ipp['icmp']['fromPort'].to_i.should == -1
|
77
87
|
@ordered_ipp['icmp']['toPort'].to_i.should == -1
|
78
88
|
end
|
89
|
+
end
|
90
|
+
|
91
|
+
describe "the web security group" do
|
92
|
+
before :each do
|
93
|
+
@sg = Ironfan::Provider::Ec2::SecurityGroup.recall('web')
|
94
|
+
@ordered_ipp = Hash[ @sg.ip_permissions.map { |s| [ s['ipProtocol'], s ] } ]
|
95
|
+
end
|
79
96
|
|
97
|
+
it "allows TCP connections to web_clients and to amazon-elb-sg" do
|
98
|
+
@ordered_ipp['tcp']['groups'].map { |g| g['groupName'] }.sort.should == %w[ amazon-elb-sg web_clients ]
|
99
|
+
end
|
80
100
|
end
|
81
101
|
end
|
82
102
|
end
|
metadata
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
name: ironfan
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 4.
|
5
|
+
version: 4.7.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-12-
|
13
|
+
date: 2012-12-14 00:00:00 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: chef
|
@@ -282,7 +282,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
282
282
|
requirements:
|
283
283
|
- - ">="
|
284
284
|
- !ruby/object:Gem::Version
|
285
|
-
hash:
|
285
|
+
hash: -2214564911911745674
|
286
286
|
segments:
|
287
287
|
- 0
|
288
288
|
version: "0"
|