ironfan 4.12.3 → 5.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.
- 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
|
#
|