ironfan 4.12.3 → 5.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +1 -0
- data/Gemfile.lock +9 -8
- data/VERSION +1 -1
- data/ironfan.gemspec +19 -4
- data/lib/chef/knife/cluster_diff.rb +95 -0
- data/lib/chef/knife/cluster_launch.rb +1 -1
- data/lib/chef/knife/cluster_list.rb +4 -2
- data/lib/chef/knife/cluster_pry.rb +1 -1
- data/lib/chef/knife/cluster_show.rb +8 -5
- data/lib/chef/knife/cluster_ssh.rb +1 -1
- data/lib/chef/knife/environment_from_realm.rb +60 -0
- data/lib/chef/knife/ironfan_knife_common.rb +22 -0
- data/lib/chef/knife/ironfan_script.rb +1 -1
- data/lib/gorillib/diff.rb +266 -0
- data/lib/gorillib/nil_check_delegate.rb +30 -0
- data/lib/ironfan/broker/computer.rb +3 -0
- data/lib/ironfan/dsl/cluster.rb +10 -0
- data/lib/ironfan/dsl/component.rb +157 -0
- data/lib/ironfan/dsl/compute.rb +21 -0
- data/lib/ironfan/dsl/facet.rb +11 -0
- data/lib/ironfan/dsl/realm.rb +17 -14
- data/lib/ironfan/dsl/server.rb +287 -0
- data/lib/ironfan/dsl/volume.rb +1 -0
- data/lib/ironfan/dsl.rb +133 -1
- data/lib/ironfan/headers.rb +7 -0
- data/lib/ironfan/plugin/base.rb +89 -0
- data/lib/ironfan/provider/ec2/security_group.rb +2 -1
- data/lib/ironfan/requirements.rb +6 -0
- data/lib/ironfan.rb +21 -4
- data/spec/ironfan/diff_spec.rb +78 -0
- data/spec/ironfan/dsl_spec.rb +115 -0
- data/spec/ironfan/manifest_spec.rb +29 -0
- data/spec/ironfan/plugin_spec.rb +138 -0
- data/spec/ironfan/realm_spec.rb +137 -0
- data/spec/spec_helper/dummy_diff_drawer.rb +94 -0
- data/spec/spec_helper.rb +15 -0
- metadata +53 -19
data/lib/ironfan/dsl/server.rb
CHANGED
@@ -1,6 +1,218 @@
|
|
1
1
|
module Ironfan
|
2
2
|
class Dsl
|
3
3
|
|
4
|
+
class MachineManifest
|
5
|
+
include Gorillib::Model
|
6
|
+
|
7
|
+
# base server fields
|
8
|
+
field :environment, Symbol
|
9
|
+
field :name, String
|
10
|
+
field :cluster_name, String
|
11
|
+
field :facet_name, String
|
12
|
+
field :components, Array, of: Ironfan::Dsl::Component, default: []
|
13
|
+
field :run_list, Array, of: String, default: []
|
14
|
+
field :cluster_default_attributes, Hash
|
15
|
+
field :cluster_override_attributes, Hash
|
16
|
+
field :facet_default_attributes, Hash
|
17
|
+
field :facet_override_attributes, Hash
|
18
|
+
|
19
|
+
# cloud fields
|
20
|
+
field :cloud_name, String
|
21
|
+
field :availability_zones, Array, default: []
|
22
|
+
field :backing, String
|
23
|
+
field :ebs_optimized, :boolean
|
24
|
+
field :flavor, String
|
25
|
+
field :image_id, String
|
26
|
+
field :placement_group, String
|
27
|
+
field :elastic_ip, String
|
28
|
+
field :auto_elastic_ip, String
|
29
|
+
field :allocation_id, String
|
30
|
+
field :region, String
|
31
|
+
field :ssh_user, String
|
32
|
+
field :subnet, String
|
33
|
+
field :vpc, String
|
34
|
+
|
35
|
+
#-----------------------------------------------------------------------------------
|
36
|
+
# # FIXME: I haven't determined how to pull some of these fields
|
37
|
+
# # in from the remote machines. In fact, some of these
|
38
|
+
# # will have to be omitted when comparing. Since
|
39
|
+
# # they'll only be necessary when we refactor the
|
40
|
+
# # backend to accept manifests for launch, I'm going
|
41
|
+
# # to leave these commented out for now. --josh
|
42
|
+
#
|
43
|
+
# # base server fields
|
44
|
+
#
|
45
|
+
# field :volumes, Array, of: Volume
|
46
|
+
#
|
47
|
+
# # cloud fields
|
48
|
+
#
|
49
|
+
# field :bits, Integer
|
50
|
+
# field :bootstrap_distro, String
|
51
|
+
# field :chef_client_script, String
|
52
|
+
# field :default_availability_zone, String
|
53
|
+
# field :elastic_load_balancers, Array, of: Ironfan::Dsl::Ec2::ElasticLoadBalancer, default: []
|
54
|
+
# field :iam_server_certificates, Array, of: Ironfan::Dsl::Ec2::IamServerCertificate, default: []
|
55
|
+
# field :image_name, String
|
56
|
+
# field :keypair, String
|
57
|
+
# field :monitoring, String
|
58
|
+
# field :mount_ephemerals, Hash
|
59
|
+
# field :permanent, :boolean
|
60
|
+
# field :provider, Whatever
|
61
|
+
# field :security_groups, Array, of: Ironfan::Dsl::Ec2::SecurityGroup
|
62
|
+
# field :ssh_identity_dir, String
|
63
|
+
# field :validation_key, String
|
64
|
+
#-----------------------------------------------------------------------------------
|
65
|
+
|
66
|
+
# Reconstruct machine manifest from a computer, pulling
|
67
|
+
# information from remote sources as necessary.
|
68
|
+
def self.from_computer(computer)
|
69
|
+
node = get_node(computer.name)
|
70
|
+
cluster_name = node['cluster_name']
|
71
|
+
facet_name = node['facet_name']
|
72
|
+
instance = node['facet_index']
|
73
|
+
|
74
|
+
from_remote(
|
75
|
+
cluster_name,
|
76
|
+
facet_name,
|
77
|
+
instance,
|
78
|
+
node,
|
79
|
+
computer.machine,
|
80
|
+
computer.server.clouds.to_a.first,
|
81
|
+
get_role("#{cluster_name}-cluster"),
|
82
|
+
get_role("#{cluster_name}-#{facet_name}-facet")
|
83
|
+
)
|
84
|
+
end
|
85
|
+
|
86
|
+
def self.from_remote(cluster_name,
|
87
|
+
facet_name,
|
88
|
+
instance,
|
89
|
+
node,
|
90
|
+
machine,
|
91
|
+
cloud,
|
92
|
+
cluster_role,
|
93
|
+
facet_role)
|
94
|
+
machine = NilCheckDelegate.new(machine)
|
95
|
+
cloud = NilCheckDelegate.new(cloud)
|
96
|
+
cluster_role = NilCheckDelegate.new(cluster_role)
|
97
|
+
facet_role = NilCheckDelegate.new(facet_role)
|
98
|
+
|
99
|
+
result = Ironfan::Dsl::MachineManifest.
|
100
|
+
receive(
|
101
|
+
|
102
|
+
# base server fields
|
103
|
+
|
104
|
+
environment: node.chef_environment,
|
105
|
+
name: instance,
|
106
|
+
cluster_name: cluster_name,
|
107
|
+
facet_name: facet_name,
|
108
|
+
components: remote_components(node),
|
109
|
+
run_list: remote_run_list(node),
|
110
|
+
cluster_default_attributes: (cluster_role.default_attributes || {}),
|
111
|
+
cluster_override_attributes: (cluster_role.override_attributes || {}),
|
112
|
+
facet_default_attributes: (facet_role.default_attributes || {}),
|
113
|
+
facet_override_attributes: (facet_role.override_attributes || {}),
|
114
|
+
|
115
|
+
# cloud fields
|
116
|
+
|
117
|
+
backing: machine.root_device_type,
|
118
|
+
cloud_name: cloud.name,
|
119
|
+
availability_zones: [*machine.availability_zone],
|
120
|
+
ebs_optimized: machine.ebs_optimized,
|
121
|
+
flavor: machine.flavor_id,
|
122
|
+
image_id: machine.image_id,
|
123
|
+
keypair: machine.nilcheck_depth(1).key_pair.name,
|
124
|
+
monitoring: machine.monitoring,
|
125
|
+
placement_group: machine.placement_group,
|
126
|
+
region: machine.availability_zone.to_s[/.*-.*-\d+/],
|
127
|
+
security_groups: machine.nilcheck_depth(1).groups.map{|x| {name: x}},
|
128
|
+
subnet: machine.subnet_id,
|
129
|
+
vpc: machine.vpc_id
|
130
|
+
|
131
|
+
#-----------------------------------------------------------------------------------
|
132
|
+
# # FIXME: I haven't determined how to pull some of these fields
|
133
|
+
# # in from the remote machines. In fact, some of these
|
134
|
+
# # will have to be omitted when comparing. Since
|
135
|
+
# # they'll only be necessary when we refactor the
|
136
|
+
# # backend to accept manifests for launch, I'm going
|
137
|
+
# # to leave these commented out for now. --josh
|
138
|
+
#
|
139
|
+
# # base server fields
|
140
|
+
#
|
141
|
+
# volume: local_manifest.volume
|
142
|
+
#
|
143
|
+
# # cloud fields
|
144
|
+
#
|
145
|
+
# bits: local_manifest.bits,
|
146
|
+
# bootstrap_distro: local_manifest.bootstrap_distro,
|
147
|
+
# chef_client_script: local_manifest.chef_client_script,
|
148
|
+
# default_availability_zone: local_manifest.default_availability_zone,
|
149
|
+
# iam_server_certificates: launch_description.fetch(:iam_server_certificates),
|
150
|
+
# image_name: local_manifest.image_name,
|
151
|
+
# elastic_load_balancers: launch_description.fetch(:elastic_load_balancers),
|
152
|
+
# mount_ephemerals: local_manifest.mount_ephemerals,
|
153
|
+
# permanent: local_manifest.permanent,
|
154
|
+
# provider: local_manifest.provider,
|
155
|
+
# elastic_ip: local_manifest.elastic_ip,
|
156
|
+
# auto_elastic_ip: local_manifest.auto_elastic_ip,
|
157
|
+
# allocation_id: local_manifest.allocation_id,
|
158
|
+
# ssh_user: local_manifest.ssh_user,
|
159
|
+
# ssh_identity_dir: local_manifest.ssh_identity_dir,
|
160
|
+
# validation_key: local_manifest.validation_key,
|
161
|
+
#-----------------------------------------------------------------------------------
|
162
|
+
)
|
163
|
+
end
|
164
|
+
|
165
|
+
def to_comparable
|
166
|
+
deep_stringify(to_wire.tap do |hsh|
|
167
|
+
hsh.delete(:_type)
|
168
|
+
hsh.delete(:ssh_user)
|
169
|
+
#hsh[:security_groups] = Hash[hsh[:security_groups].map{|x| [x.fetch(:name), x]}]
|
170
|
+
hsh[:components] = Hash[hsh.fetch(:components).map do |component|
|
171
|
+
[component.fetch(:name), component]
|
172
|
+
end]
|
173
|
+
hsh[:run_list] = hsh.fetch(:run_list).map do |x|
|
174
|
+
x.end_with?(']') ? x : "recipe[#{x}]"
|
175
|
+
end
|
176
|
+
end)
|
177
|
+
end
|
178
|
+
|
179
|
+
private
|
180
|
+
|
181
|
+
def deep_stringify obj
|
182
|
+
case obj
|
183
|
+
when Hash then Hash[obj.map{|k,v| [k.to_s, deep_stringify(v)]}]
|
184
|
+
when Array then obj.map{|x| deep_stringify(x)}
|
185
|
+
when Symbol then obj.to_s
|
186
|
+
else obj
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
def self.get_node(node_name)
|
191
|
+
Chef::Node.load(node_name)
|
192
|
+
rescue Net::HTTPServerException => ex
|
193
|
+
Chef::Node.new
|
194
|
+
end
|
195
|
+
|
196
|
+
def self.get_role(role_name)
|
197
|
+
Chef::Role.load(role_name)
|
198
|
+
rescue Net::HTTPServerException => ex
|
199
|
+
Chef::Role.new
|
200
|
+
end
|
201
|
+
|
202
|
+
def self.remote_components(node)
|
203
|
+
announcements = node['components'] || {}
|
204
|
+
node['components'].to_a.map do |_, announce|
|
205
|
+
name = announce['name'].to_sym
|
206
|
+
plugin = Ironfan::Dsl::Compute.plugin_for(name)
|
207
|
+
plugin.from_node(node).tap{|x| x.name = name} if plugin
|
208
|
+
end.compact
|
209
|
+
end
|
210
|
+
|
211
|
+
def self.remote_run_list(node)
|
212
|
+
node.run_list.to_a.map(&:to_s)
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
4
216
|
class Server < Ironfan::Dsl::Compute
|
5
217
|
field :cluster_name, String
|
6
218
|
field :facet_name, String
|
@@ -38,6 +250,81 @@ module Ironfan
|
|
38
250
|
errors['missing cluster/facet/server'] = [cluster_name, facet_name, name] unless (cluster_name && facet_name && name)
|
39
251
|
errors
|
40
252
|
end
|
253
|
+
|
254
|
+
def to_machine_manifest
|
255
|
+
cloud = clouds.each.to_a.first
|
256
|
+
MachineManifest.receive(
|
257
|
+
|
258
|
+
# base server fields
|
259
|
+
|
260
|
+
environment: environment,
|
261
|
+
name: name,
|
262
|
+
cluster_name: cluster_name,
|
263
|
+
facet_name: facet_name,
|
264
|
+
run_list: run_list,
|
265
|
+
components: components,
|
266
|
+
cluster_default_attributes: cluster_role.default_attributes,
|
267
|
+
cluster_override_attributes: cluster_role.override_attributes,
|
268
|
+
facet_default_attributes: facet_role.default_attributes,
|
269
|
+
facet_override_attributes: facet_role.override_attributes,
|
270
|
+
volumes: volumes,
|
271
|
+
|
272
|
+
# cloud fields
|
273
|
+
|
274
|
+
cloud_name: cloud.name,
|
275
|
+
|
276
|
+
availability_zones: cloud.availability_zones,
|
277
|
+
backing: cloud.backing,
|
278
|
+
bits: cloud.bits,
|
279
|
+
bootstrap_distro: cloud.bootstrap_distro,
|
280
|
+
chef_client_script: cloud.chef_client_script,
|
281
|
+
default_availability_zone: cloud.default_availability_zone,
|
282
|
+
elastic_load_balancers: cloud.elastic_load_balancers,
|
283
|
+
ebs_optimized: cloud.ebs_optimized,
|
284
|
+
flavor: cloud.flavor,
|
285
|
+
iam_server_certificates: cloud.iam_server_certificates,
|
286
|
+
image_id: cloud.image_id,
|
287
|
+
image_name: cloud.image_name,
|
288
|
+
keypair: cloud.keypair,
|
289
|
+
monitoring: cloud.monitoring,
|
290
|
+
mount_ephemerals: cloud.mount_ephemerals,
|
291
|
+
permanent: cloud.permanent,
|
292
|
+
placement_group: cloud.placement_group,
|
293
|
+
provider: cloud.provider,
|
294
|
+
elastic_ip: cloud.elastic_ip,
|
295
|
+
auto_elastic_ip: cloud.auto_elastic_ip,
|
296
|
+
allocation_id: cloud.allocation_id,
|
297
|
+
region: cloud.region,
|
298
|
+
security_groups: cloud.security_groups,
|
299
|
+
ssh_user: cloud.ssh_user,
|
300
|
+
ssh_identity_dir: cloud.ssh_identity_dir,
|
301
|
+
subnet: cloud.subnet,
|
302
|
+
validation_key: cloud.validation_key,
|
303
|
+
vpc: cloud.vpc
|
304
|
+
|
305
|
+
)
|
306
|
+
end
|
307
|
+
|
308
|
+
def canonical_machine_manifest_hash
|
309
|
+
self.class.canonicalize(to_machine_manifest)
|
310
|
+
end
|
311
|
+
|
312
|
+
private
|
313
|
+
|
314
|
+
def self.canonicalize(item)
|
315
|
+
case item
|
316
|
+
when Array, Gorillib::ModelCollection
|
317
|
+
item.each.map{|i| canonicalize(i)}
|
318
|
+
when Ironfan::Dsl::Component
|
319
|
+
canonicalize(item.to_manifest)
|
320
|
+
when Gorillib::Builder, Gorillib::Model
|
321
|
+
canonicalize(item.to_wire.tap{|x| x.delete(:_type)})
|
322
|
+
when Hash then
|
323
|
+
Hash[item.sort.map{|k,v| [k, canonicalize(v)]}]
|
324
|
+
else
|
325
|
+
item
|
326
|
+
end
|
327
|
+
end
|
41
328
|
end
|
42
329
|
|
43
330
|
end
|
data/lib/ironfan/dsl/volume.rb
CHANGED
data/lib/ironfan/dsl.rb
CHANGED
@@ -2,6 +2,138 @@ module Ironfan
|
|
2
2
|
|
3
3
|
class Dsl < Builder
|
4
4
|
include Gorillib::Resolution
|
5
|
-
end
|
6
5
|
|
6
|
+
def self.default_cookbook_reqs
|
7
|
+
@default_cookbook_reqs ||= []
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.cookbook_req name, constraint
|
11
|
+
default_cookbook_reqs << new_req(name, constraint)
|
12
|
+
|
13
|
+
end
|
14
|
+
|
15
|
+
def join_req req1, req2
|
16
|
+
# order requirements by operation: =, >=, ~>
|
17
|
+
req1, req2 = (req1.constraint < req2.constraint) ? [req1, req2] : [req2, req1]
|
18
|
+
cn1, cn2 = [req1.constraint, req2.constraint]
|
19
|
+
vers1, vers2 = [cn1.split.last, cn2.split.last]
|
20
|
+
vers1_c, vers2_c = [vers1.split('.'), vers2.split('.')]
|
21
|
+
op1, op2 = [req1, req2].map{|x| x.constraint.split.first}
|
22
|
+
|
23
|
+
if op1 == '=' and op2 == '='
|
24
|
+
join_eq_eq(req1, req2)
|
25
|
+
elsif op1 == '>=' and op2 == '>='
|
26
|
+
join_geq_geq(req1, req2)
|
27
|
+
elsif op1 == '~>' and op2 == '~>'
|
28
|
+
join_agt_agt(req1, req2)
|
29
|
+
elsif op1 == '=' and op2 == '>='
|
30
|
+
join_eq_gte(req1, req2)
|
31
|
+
elsif op1 == '=' and op2 == '~>'
|
32
|
+
join_eq_agt(req1, req2)
|
33
|
+
elsif op1 == '>=' and op2 == '~>'
|
34
|
+
join_gte_agt(req1, req2)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def cookbook_req name, constraint
|
39
|
+
(@cookbook_reqs ||= []) << self.class.new_req(name, constraint)
|
40
|
+
end
|
41
|
+
|
42
|
+
def children() [] end
|
43
|
+
|
44
|
+
def cookbook_reqs
|
45
|
+
Hash[_cookbook_reqs.map{|x| [x.name, x.constraint]}]
|
46
|
+
end
|
47
|
+
|
48
|
+
def _cookbook_reqs
|
49
|
+
[
|
50
|
+
*shallow_cookbook_reqs,
|
51
|
+
*child_cookbook_reqs
|
52
|
+
].group_by(&:name).values.map do |group|
|
53
|
+
group.inject{|result, req| join_req(result, req)}
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
#-----------------------------------------------------------------------------------------------
|
60
|
+
|
61
|
+
def join_eq_eq(req1, req2)
|
62
|
+
(vers(req1) == vers(req2)) ? req1 : bad_reqs(req1, req2)
|
63
|
+
end
|
64
|
+
|
65
|
+
def join_geq_geq(req1, req2)
|
66
|
+
(vers(req1) >= vers(req2)) ? req1 : req2
|
67
|
+
end
|
68
|
+
|
69
|
+
def join_agt_agt(req1, req2)
|
70
|
+
if vers_a(req1).size == vers_a(req2).size and
|
71
|
+
vers_a_head(req1).zip(vers_a_head(req2)).all?{|v1,v2| v1 == v2}
|
72
|
+
(req1.constraint > req2.constraint) ? req1 : req2
|
73
|
+
else
|
74
|
+
bad_reqs(req1, req2)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def join_eq_gte(req1, req2)
|
79
|
+
vers(req1) >= vers(req2) ? req1 : bad_reqs(req1, req2)
|
80
|
+
end
|
81
|
+
|
82
|
+
def join_eq_agt(req1, req2)
|
83
|
+
if match_v_head(req1, req2) and
|
84
|
+
vers_a(req1)[vers_a(req2).size - 1] >= vers_a(req2).last
|
85
|
+
req1
|
86
|
+
else
|
87
|
+
bad_reqs(req1, req2)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def join_gte_agt(req1, req2)
|
92
|
+
if match_v_head(req1, req2) and vers(req1) <= vers(req2)
|
93
|
+
req2
|
94
|
+
else
|
95
|
+
bad_reqs(req1, req2)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def match_v_head(req1, req2)
|
100
|
+
vers_a_head(req1, vers_a(req2).size).zip(vers_a_head(req2)).all?{|v1,v2| v1 == v2}
|
101
|
+
end
|
102
|
+
|
103
|
+
def op(req)
|
104
|
+
req.constraint.split.first
|
105
|
+
end
|
106
|
+
|
107
|
+
def vers(req)
|
108
|
+
req.constraint.split.last
|
109
|
+
end
|
110
|
+
|
111
|
+
def vers_a(req)
|
112
|
+
vers(req).split('.')
|
113
|
+
end
|
114
|
+
|
115
|
+
def vers_a_head(req, last = 0)
|
116
|
+
vers_a(req)[0...last-1]
|
117
|
+
end
|
118
|
+
|
119
|
+
#-----------------------------------------------------------------------------------------------
|
120
|
+
|
121
|
+
def bad_reqs(req1, req2)
|
122
|
+
raise ArgumentError.new("#{req1.name}: cannot reconcile #{req1.constraint} with #{req2.constraint}")
|
123
|
+
end
|
124
|
+
|
125
|
+
def child_cookbook_reqs
|
126
|
+
children.map(&:_cookbook_reqs).flatten(1)
|
127
|
+
end
|
128
|
+
|
129
|
+
def self.new_req(name, constraint)
|
130
|
+
raise StandardError.new("Please don't use >= constraints. They're too vague!") if
|
131
|
+
constraint.start_with?('>=') and not (@@testing ||= false)
|
132
|
+
Ironfan::Plugin::CookbookRequirement.new(name: name, constraint: constraint)
|
133
|
+
end
|
134
|
+
|
135
|
+
def shallow_cookbook_reqs
|
136
|
+
@cookbook_reqs || self.class.default_cookbook_reqs
|
137
|
+
end
|
138
|
+
end
|
7
139
|
end
|
data/lib/ironfan/headers.rb
CHANGED
@@ -15,9 +15,11 @@ module Ironfan
|
|
15
15
|
end
|
16
16
|
|
17
17
|
class Dsl < Builder
|
18
|
+
class Component < Ironfan::Dsl; end
|
18
19
|
class Compute < Ironfan::Dsl; end
|
19
20
|
class Cluster < Ironfan::Dsl::Compute; end
|
20
21
|
class Facet < Ironfan::Dsl::Compute; end
|
22
|
+
class Realm < Ironfan::Dsl::Compute; end
|
21
23
|
class Server < Ironfan::Dsl::Compute; end
|
22
24
|
|
23
25
|
class Role < Ironfan::Dsl; end
|
@@ -36,6 +38,11 @@ module Ironfan
|
|
36
38
|
end
|
37
39
|
end
|
38
40
|
|
41
|
+
module Plugin
|
42
|
+
class CookbookRequirement; end
|
43
|
+
module Base; end
|
44
|
+
end
|
45
|
+
|
39
46
|
class Provider < Builder
|
40
47
|
class Resource < Builder; end
|
41
48
|
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'gorillib/model'
|
2
|
+
require 'gorillib/builder'
|
3
|
+
require 'gorillib/string/inflections'
|
4
|
+
require 'gorillib/metaprogramming/concern'
|
5
|
+
|
6
|
+
Gorillib::Model::Field.class_eval do
|
7
|
+
field :node_attr, String, default: nil
|
8
|
+
end
|
9
|
+
|
10
|
+
module Ironfan
|
11
|
+
|
12
|
+
module Pluggable
|
13
|
+
def add_plugin name, cls
|
14
|
+
registry[name] = cls
|
15
|
+
end
|
16
|
+
def plugin_for name
|
17
|
+
registry[name]
|
18
|
+
end
|
19
|
+
def registry() @registry ||= {}; end
|
20
|
+
end
|
21
|
+
|
22
|
+
module Plugin
|
23
|
+
class CookbookRequirement
|
24
|
+
include Gorillib::Builder
|
25
|
+
|
26
|
+
magic :name, String
|
27
|
+
magic :constraint, String
|
28
|
+
|
29
|
+
def <=>(other)
|
30
|
+
self.name <=> other.name
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
module Base
|
35
|
+
extend Gorillib::Concern
|
36
|
+
|
37
|
+
def to_node
|
38
|
+
Chef::Node.new.tap do |node|
|
39
|
+
self.class.fields.select{|_,x| x.node_attr}.each do |_,x|
|
40
|
+
val = send(x.name)
|
41
|
+
(keys = x.node_attr.split('.'))[0...-1].inject(node.set) do |hsh,key|
|
42
|
+
hsh[key]
|
43
|
+
end[keys.last] = val unless val.nil?
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
module ClassMethods
|
49
|
+
attr_reader :cookbook_reqs
|
50
|
+
attr_reader :plugin_name
|
51
|
+
|
52
|
+
def from_node(node = NilCheckDelegate.new(nil))
|
53
|
+
new(Hash[
|
54
|
+
fields.select{|_,x| x.node_attr}.map do |_,x|
|
55
|
+
[x.name, (x.node_attr.split('.').inject(node) do |hsh,attr|
|
56
|
+
if hsh
|
57
|
+
(val = hsh[attr]).is_a?(Mash) ? val.to_hash : val
|
58
|
+
end
|
59
|
+
end)]
|
60
|
+
end.reject{|_,v| v.nil?}
|
61
|
+
])
|
62
|
+
end
|
63
|
+
|
64
|
+
def register_with cls, &blk
|
65
|
+
(@dest_class = cls).class_eval{ extend Ironfan::Pluggable }
|
66
|
+
end
|
67
|
+
|
68
|
+
def template plugin_name_parts, base_class=self, &blk
|
69
|
+
plugin_name_parts = [*plugin_name_parts]
|
70
|
+
full_name = plugin_name_parts.map(&:to_s).join('_').to_sym
|
71
|
+
plugin_name = plugin_name_parts.first.to_sym
|
72
|
+
|
73
|
+
Class.new(base_class, &blk).tap do |plugin_class|
|
74
|
+
plugin_class.class_eval{ @plugin_name = plugin_name }
|
75
|
+
|
76
|
+
self.const_set(full_name.to_s.camelize.to_sym, plugin_class)
|
77
|
+
|
78
|
+
@dest_class.class_eval do
|
79
|
+
add_plugin(full_name, plugin_class)
|
80
|
+
define_method(full_name) do |*args, &blk|
|
81
|
+
plugin_class.plugin_hook self, (args.first || {}), plugin_name, full_name, &blk
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -69,6 +69,8 @@ module Ironfan
|
|
69
69
|
groups_to_create = [ ]
|
70
70
|
authorizations_to_ensure = [ ]
|
71
71
|
|
72
|
+
computers.each{|comp| ensure_groups(comp) if Ec2.applicable(comp) } # Add facet and cluster security groups for the computer
|
73
|
+
|
72
74
|
# First, deduce the list of all groups to which at least one instance belongs
|
73
75
|
# We'll use this later to decide whether to create groups, or authorize access,
|
74
76
|
# using a VPC security group or an EC2 security group.
|
@@ -76,7 +78,6 @@ module Ironfan
|
|
76
78
|
groups_to_create << groups_that_should_exist
|
77
79
|
|
78
80
|
computers.select { |computer| Ec2.applicable computer }.each do |computer|
|
79
|
-
ensure_groups(computer) # Add facet and cluster security groups for the computer
|
80
81
|
cloud = computer.server.cloud(:ec2)
|
81
82
|
cluster_name = computer.server.cluster_name
|
82
83
|
|
data/lib/ironfan/requirements.rb
CHANGED
@@ -2,13 +2,19 @@
|
|
2
2
|
require 'gorillib/builder'
|
3
3
|
require 'gorillib/resolution'
|
4
4
|
|
5
|
+
require 'gorillib/nil_check_delegate'
|
6
|
+
|
5
7
|
# Pre-declaration of class hierarchy
|
6
8
|
require 'ironfan/headers'
|
7
9
|
|
10
|
+
# Ironfan plugin mixin
|
11
|
+
require 'ironfan/plugin/base'
|
12
|
+
|
8
13
|
# DSL for cluster descriptions
|
9
14
|
require 'ironfan/dsl'
|
10
15
|
require 'ironfan/builder'
|
11
16
|
|
17
|
+
require 'ironfan/dsl/component'
|
12
18
|
require 'ironfan/dsl/compute'
|
13
19
|
require 'ironfan/dsl/server'
|
14
20
|
require 'ironfan/dsl/facet'
|
data/lib/ironfan.rb
CHANGED
@@ -91,6 +91,18 @@ module Ironfan
|
|
91
91
|
end
|
92
92
|
end
|
93
93
|
|
94
|
+
def self.load_realm(name)
|
95
|
+
name = name.to_sym
|
96
|
+
raise ArgumentError, "Please supply a realm name" if name.to_s.empty?
|
97
|
+
return @@realms[name] if @@realms[name]
|
98
|
+
|
99
|
+
load_cluster_files
|
100
|
+
|
101
|
+
unless @@realms[name] then die("Couldn't find a realm definition for #{name} in #{cluster_path}") end
|
102
|
+
|
103
|
+
@@realms[name]
|
104
|
+
end
|
105
|
+
|
94
106
|
#
|
95
107
|
# Return cluster if it's defined. Otherwise, search Ironfan.cluster_path
|
96
108
|
# for an eponymous file, load it, and return the cluster it defines.
|
@@ -104,16 +116,21 @@ module Ironfan
|
|
104
116
|
raise ArgumentError, "Please supply a cluster name" if name.to_s.empty?
|
105
117
|
return @@clusters[name] if @@clusters[name]
|
106
118
|
|
119
|
+
load_cluster_files
|
120
|
+
|
121
|
+
unless @@clusters[name] then die("Couldn't find a cluster definition for #{name} in #{cluster_path}") end
|
122
|
+
|
123
|
+
@@clusters[name]
|
124
|
+
end
|
125
|
+
|
126
|
+
def self.load_cluster_files
|
107
127
|
cluster_path.each do |cp_dir|
|
108
128
|
Dir[ File.join(cp_dir, '*.rb') ].each do |filename|
|
109
129
|
Chef::Log.info("Loading cluster file #{filename}")
|
110
130
|
require filename
|
131
|
+
clusters.values.each{|cluster| cluster.source_file ||= filename}
|
111
132
|
end
|
112
133
|
end
|
113
|
-
|
114
|
-
unless @@clusters[name] then die("Couldn't find a cluster definition for #{name} in #{cluster_path}") end
|
115
|
-
|
116
|
-
@@clusters[name]
|
117
134
|
end
|
118
135
|
|
119
136
|
#
|