ironfan 4.6.2 → 4.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG.md +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"
|