syncwrap 1.5.2 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/History.rdoc +19 -0
- data/Manifest.txt +82 -34
- data/README.rdoc +96 -48
- data/Rakefile +0 -65
- data/bin/syncwrap +27 -0
- data/examples/LAYOUT.rdoc +70 -0
- data/examples/Rakefile +16 -0
- data/examples/ec2.rb +44 -0
- data/examples/hello.rb +14 -0
- data/examples/hello_binding.rb +27 -0
- data/examples/jruby.rb +11 -0
- data/examples/private/aws.json +4 -0
- data/examples/rput.rb +24 -0
- data/examples/sync/home/bob/.ssh/authorized_keys +1 -0
- data/examples/sync/tmp/sample.erb +3 -0
- data/lib/syncwrap/amazon_ec2.rb +236 -0
- data/lib/syncwrap/amazon_ws.rb +308 -0
- data/lib/syncwrap/base.rb +4 -2
- data/lib/syncwrap/cli.rb +328 -0
- data/lib/syncwrap/component.rb +443 -0
- data/lib/syncwrap/components/commercial_jdk.rb +76 -0
- data/lib/syncwrap/components/cruby_vm.rb +144 -0
- data/lib/syncwrap/components/etc_hosts.rb +44 -0
- data/lib/syncwrap/{geminabox.rb → components/geminabox.rb} +12 -17
- data/lib/syncwrap/components/hashdot.rb +97 -0
- data/lib/syncwrap/components/iyyov.rb +144 -0
- data/lib/syncwrap/components/iyyov_daemon.rb +125 -0
- data/lib/syncwrap/components/jruby_vm.rb +122 -0
- data/lib/syncwrap/components/mdraid.rb +204 -0
- data/lib/syncwrap/components/network.rb +99 -0
- data/lib/syncwrap/components/open_jdk.rb +70 -0
- data/lib/syncwrap/components/postgresql.rb +159 -0
- data/lib/syncwrap/components/qpid.rb +303 -0
- data/lib/syncwrap/components/rhel.rb +71 -0
- data/lib/syncwrap/components/run_user.rb +99 -0
- data/lib/syncwrap/components/ubuntu.rb +85 -0
- data/lib/syncwrap/components/users.rb +200 -0
- data/lib/syncwrap/context.rb +260 -0
- data/lib/syncwrap/distro.rb +53 -60
- data/lib/syncwrap/formatter.rb +149 -0
- data/lib/syncwrap/host.rb +134 -0
- data/lib/syncwrap/main.rb +62 -0
- data/lib/syncwrap/path_util.rb +55 -0
- data/lib/syncwrap/rsync.rb +227 -0
- data/lib/syncwrap/ruby_support.rb +110 -0
- data/lib/syncwrap/shell.rb +207 -0
- data/lib/syncwrap.rb +367 -1
- data/{etc → sync/etc}/gemrc +1 -3
- data/sync/etc/hosts.erb +8 -0
- data/{etc/init.d/iyyov → sync/etc/init.d/iyyov.erb} +35 -7
- data/sync/etc/sysconfig/pgsql/postgresql.erb +2 -0
- data/sync/src/hashdot/Makefile.erb +98 -0
- data/sync/src/hashdot/profiles/default.hdp.erb +25 -0
- data/sync/src/hashdot/profiles/jruby-common.hdp +28 -0
- data/sync/src/hashdot/profiles/jruby-shortlived.hdp +9 -0
- data/sync/src/hashdot/profiles/jruby.hdp.erb +13 -0
- data/sync/src/hashdot/profiles/shortlived.hdp +6 -0
- data/sync/var/iyyov/default/config.rb +1 -0
- data/sync/var/iyyov/default/daemon.rb.erb +15 -0
- data/sync/var/iyyov/jobs.rb.erb +4 -0
- data/test/muddled_sync.rb +13 -0
- data/test/setup.rb +39 -0
- data/test/sync/d1/bar +1 -0
- data/test/sync/d1/foo.erb +1 -0
- data/test/sync/d3/d2/bar +1 -0
- data/test/sync/d3/d2/foo.erb +1 -0
- data/test/test_components.rb +108 -0
- data/test/test_context.rb +107 -0
- data/test/test_context_rput.rb +289 -0
- data/test/test_rsync.rb +138 -0
- data/test/test_shell.rb +233 -0
- data/test/test_space.rb +218 -0
- data/test/test_space_main.rb +40 -0
- data/test/zfile +1 -0
- metadata +204 -71
- data/etc/sysconfig/pgsql/postgresql +0 -2
- data/lib/syncwrap/aws.rb +0 -448
- data/lib/syncwrap/common.rb +0 -161
- data/lib/syncwrap/ec2.rb +0 -59
- data/lib/syncwrap/hashdot.rb +0 -70
- data/lib/syncwrap/iyyov.rb +0 -139
- data/lib/syncwrap/java.rb +0 -61
- data/lib/syncwrap/jruby.rb +0 -118
- data/lib/syncwrap/postgresql.rb +0 -135
- data/lib/syncwrap/qpid.rb +0 -251
- data/lib/syncwrap/remote_task.rb +0 -199
- data/lib/syncwrap/rhel.rb +0 -67
- data/lib/syncwrap/ubuntu.rb +0 -78
- data/lib/syncwrap/user_run.rb +0 -102
- data/test/test_syncwrap.rb +0 -202
- data/var/iyyov/jobs.rb +0 -11
- /data/{etc → sync/etc}/corosync/corosync.conf +0 -0
- /data/{etc → sync/etc}/corosync/uidgid.d/qpid +0 -0
- /data/{etc → sync/etc}/init.d/qpidd +0 -0
- /data/{etc → sync/etc}/sysctl.d/61-postgresql-shm.conf +0 -0
- /data/{usr/local → sync/jruby}/bin/jgem +0 -0
- /data/{postgresql → sync/postgresql}/rhel/pg_hba.conf +0 -0
- /data/{postgresql → sync/postgresql}/rhel/pg_ident.conf +0 -0
- /data/{postgresql → sync/postgresql}/rhel/postgresql.conf +0 -0
- /data/{postgresql → sync/postgresql}/ubuntu/environment +0 -0
- /data/{postgresql → sync/postgresql}/ubuntu/pg_ctl.conf +0 -0
- /data/{postgresql → sync/postgresql}/ubuntu/pg_hba.conf +0 -0
- /data/{postgresql → sync/postgresql}/ubuntu/pg_ident.conf +0 -0
- /data/{postgresql → sync/postgresql}/ubuntu/postgresql.conf +0 -0
- /data/{postgresql → sync/postgresql}/ubuntu/start.conf +0 -0
- /data/{usr → sync/usr}/local/etc/qpidd.conf +0 -0
data/lib/syncwrap/aws.rb
DELETED
@@ -1,448 +0,0 @@
|
|
1
|
-
#--
|
2
|
-
# Copyright (c) 2011-2013 David Kellum
|
3
|
-
#
|
4
|
-
# Licensed under the Apache License, Version 2.0 (the "License"); you
|
5
|
-
# may not use this file except in compliance with the License. You may
|
6
|
-
# obtain a copy of the License at
|
7
|
-
#
|
8
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
-
#
|
10
|
-
# Unless required by applicable law or agreed to in writing, software
|
11
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
13
|
-
# implied. See the License for the specific language governing
|
14
|
-
# permissions and limitations under the License.
|
15
|
-
#++
|
16
|
-
|
17
|
-
require 'json'
|
18
|
-
require 'aws-sdk'
|
19
|
-
require 'resolv'
|
20
|
-
|
21
|
-
require 'syncwrap/common'
|
22
|
-
|
23
|
-
# Supports host provisioning in EC2 via AWS APIs, creating and
|
24
|
-
# attaching EBS volumes, creating Route53 record sets, and as a remote
|
25
|
-
# task: building mdraid volumes.
|
26
|
-
#
|
27
|
-
# This module also includes a disk based cache of meta-data on created
|
28
|
-
# instances which allows automated role assignment (i.e. create an
|
29
|
-
# instance and run deploy tasks on it in a single pass.)
|
30
|
-
module SyncWrap::AWS
|
31
|
-
include SyncWrap::Common
|
32
|
-
|
33
|
-
# The json configuration file, parsed and passed directly to
|
34
|
-
# AWS::config method. This file should contain a json object with
|
35
|
-
# the minimal required keys: access_key_id, secret_access_key
|
36
|
-
# (default: ./private/aws.json)
|
37
|
-
attr_accessor :aws_config_json
|
38
|
-
|
39
|
-
# The cached aws instance json file (default: ./aws_instances.json)
|
40
|
-
attr_accessor :aws_instances_json
|
41
|
-
|
42
|
-
# Array of region strings to check for aws_import_instances
|
43
|
-
attr_accessor :aws_regions
|
44
|
-
|
45
|
-
# Array of imported or read instances represented as hashes.
|
46
|
-
attr_accessor :aws_instances
|
47
|
-
|
48
|
-
# Default options (which may be overridden) in call to
|
49
|
-
# aws_create_instance.
|
50
|
-
attr_accessor :aws_default_instance_options
|
51
|
-
|
52
|
-
# Default options (which may be overridden) in call to
|
53
|
-
# aws_create_security_group.
|
54
|
-
attr_accessor :aws_default_sg_options
|
55
|
-
|
56
|
-
# Default options for Route53 record set creation
|
57
|
-
attr_accessor :route53_default_rs_options
|
58
|
-
|
59
|
-
# DNS Resolver options for testing Route53 (default: Use public
|
60
|
-
# google name servers to avoid local negative caching)
|
61
|
-
attr_accessor :resolver_options
|
62
|
-
|
63
|
-
def initialize
|
64
|
-
@aws_config_json = 'private/aws.json'
|
65
|
-
@aws_regions = %w[ us-east-1 ]
|
66
|
-
@aws_instances_json = 'aws_instances.json'
|
67
|
-
@aws_instances = []
|
68
|
-
@aws_default_instance_options = {
|
69
|
-
:ebs_volumes => 0,
|
70
|
-
:ebs_volume_options => { :size => 16 }, #gb
|
71
|
-
:lvm_volumes => [ [ 1.00, '/data' ] ],
|
72
|
-
:security_groups => :default,
|
73
|
-
:instance_type => 'm1.medium',
|
74
|
-
:region => 'us-east-1'
|
75
|
-
}
|
76
|
-
@aws_default_sg_options = {
|
77
|
-
:region => 'us-east-1'
|
78
|
-
}
|
79
|
-
|
80
|
-
@route53_default_rs_options = {
|
81
|
-
:ttl => 300,
|
82
|
-
:wait => true
|
83
|
-
}
|
84
|
-
|
85
|
-
@resolver_options = {
|
86
|
-
:nameserver => [ '8.8.8.8', '8.8.4.4' ]
|
87
|
-
}
|
88
|
-
|
89
|
-
super
|
90
|
-
|
91
|
-
aws_configure if File.exist?( @aws_config_json )
|
92
|
-
aws_read_instances if File.exist?( @aws_instances_json )
|
93
|
-
end
|
94
|
-
|
95
|
-
def aws_configure
|
96
|
-
AWS.config( JSON.parse( IO.read( @aws_config_json ),
|
97
|
-
:symbolize_names => true ) )
|
98
|
-
end
|
99
|
-
|
100
|
-
# Create a security_group given name and options. :region is the
|
101
|
-
# only required option, :description is a good to have. Currently
|
102
|
-
# this is a no-op if the security group already exists.
|
103
|
-
def aws_create_security_group( name, opts = {} )
|
104
|
-
opts = deep_merge_hashes( @aws_default_sg_options, opts )
|
105
|
-
region = opts.delete( :region )
|
106
|
-
ec2 = AWS::EC2.new.regions[ region ]
|
107
|
-
unless ec2.security_groups.find { |sg| sg.name == name }
|
108
|
-
ec2.security_groups.create( name, opts )
|
109
|
-
end
|
110
|
-
end
|
111
|
-
|
112
|
-
# Create an instance, using name as the Name tag and assumed
|
113
|
-
# host name. For options see
|
114
|
-
# {AWS::EC2::InstanceCollection.create}[http://docs.aws.amazon.com/AWSRubySDK/latest/AWS/EC2/InstanceCollection.html#create-instance_method]
|
115
|
-
# with the following additions/differences:
|
116
|
-
#
|
117
|
-
# :count:: must be 1 or unspecified.
|
118
|
-
# :region:: Default 'us-east-1'
|
119
|
-
# :security_groups:: As per aws-sdk, but the special :default value
|
120
|
-
# is replaced with a single security group with
|
121
|
-
# same name as the :region option.
|
122
|
-
# :ebs_volumes:: The number of EBS volumes to create an attach to this instance.
|
123
|
-
# :ebs_volume_options:: A nested Hash of options, as per
|
124
|
-
# {AWS::EC2::VolumeCollection.create}[http://docs.aws.amazon.com/AWSRubySDK/latest/AWS/EC2/VolumeCollection.html#create-instance_method]
|
125
|
-
# with custom default :size 16 GB, and the same
|
126
|
-
# :availibility_zone as the instance.
|
127
|
-
# :lvm_volumes:: Ignored here.
|
128
|
-
# :roles:: Array of role Strings or Symbols (applied as Roles tag)
|
129
|
-
def aws_create_instance( name, opts = {} )
|
130
|
-
opts = deep_merge_hashes( @aws_default_instance_options, opts )
|
131
|
-
region = opts.delete( :region )
|
132
|
-
opts.delete( :lvm_volumes ) #unused here
|
133
|
-
|
134
|
-
ec2 = AWS::EC2.new.regions[ region ]
|
135
|
-
|
136
|
-
iopts = opts.dup
|
137
|
-
iopts.delete( :ebs_volumes )
|
138
|
-
iopts.delete( :ebs_volume_options )
|
139
|
-
iopts.delete( :roles )
|
140
|
-
|
141
|
-
if iopts[ :count ] && iopts[ :count ] != 1
|
142
|
-
raise ":count #{iopts[ :count ]} != 1 is not supported"
|
143
|
-
end
|
144
|
-
|
145
|
-
if iopts[ :security_groups ] == :default
|
146
|
-
iopts[ :security_groups ] = [ region ]
|
147
|
-
end
|
148
|
-
|
149
|
-
inst = ec2.instances.create( iopts )
|
150
|
-
|
151
|
-
inst.add_tag( 'Name', :value => name )
|
152
|
-
|
153
|
-
if opts[ :roles ]
|
154
|
-
inst.add_tag( 'Roles', :value => opts[ :roles ].join(' ') )
|
155
|
-
end
|
156
|
-
|
157
|
-
wait_for_running( inst )
|
158
|
-
|
159
|
-
if opts[ :ebs_volumes ] > 0
|
160
|
-
vopts = { :availability_zone => inst.availability_zone }.
|
161
|
-
merge( opts[ :ebs_volume_options ] )
|
162
|
-
|
163
|
-
attachments = opts[ :ebs_volumes ].times.map do |i|
|
164
|
-
vol = ec2.volumes.create( vopts )
|
165
|
-
wait_until( vol.id, 0.5 ) { vol.status == :available }
|
166
|
-
vol.attach_to( inst, "/dev/sdh#{i+1}" ) #=> Attachment
|
167
|
-
end
|
168
|
-
|
169
|
-
wait_until( "volumes to attach" ) do
|
170
|
-
!( attachments.any? { |a| a.status == :attaching } )
|
171
|
-
end
|
172
|
-
end
|
173
|
-
|
174
|
-
iprops = aws_instance_to_props( region, inst )
|
175
|
-
aws_instance_added( iprops )
|
176
|
-
aws_write_instances
|
177
|
-
iprops
|
178
|
-
end
|
179
|
-
|
180
|
-
# Create a Route53 DNS CNAME from iprops :name to :internet_name.
|
181
|
-
# Options are per {AWS::Route53::ResourceRecordSetCollection.create}[http://docs.aws.amazon.com/AWSRubySDK/latest/AWS/Route53/ResourceRecordSetCollection.html#create-instance_method]
|
182
|
-
# (currently undocumented) with the following addition:
|
183
|
-
#
|
184
|
-
# :domain_name:: name of the hosted zone and suffix for
|
185
|
-
# CNAME. Should terminate in a DOT '.'
|
186
|
-
# :wait:: If true, wait for CNAME to resolve
|
187
|
-
def route53_create_host_cname( iprops, opts = {} )
|
188
|
-
opts = deep_merge_hashes( @route53_default_rs_options, opts )
|
189
|
-
dname = dot_terminate( opts.delete( :domain_name ) )
|
190
|
-
do_wait = opts.delete( :wait )
|
191
|
-
rs_opts = opts.
|
192
|
-
merge( :resource_records => [ {:value => iprops[:internet_name]} ] )
|
193
|
-
|
194
|
-
r53 = AWS::Route53.new
|
195
|
-
zone = r53.hosted_zones.find { |hz| hz.name == dname } or
|
196
|
-
raise "Route53 Hosted Zone name #{dname} not found"
|
197
|
-
long_name = [ iprops[:name], dname ].join('.')
|
198
|
-
zone.rrsets.create( long_name, 'CNAME', rs_opts )
|
199
|
-
wait_for_dns_resolve( long_name, dname ) if do_wait
|
200
|
-
end
|
201
|
-
|
202
|
-
def wait_for_dns_resolve( long_name,
|
203
|
-
domain,
|
204
|
-
rtype = Resolv::DNS::Resource::IN::CNAME )
|
205
|
-
|
206
|
-
ns_addr = Resolv::DNS.open( @resolver_options ) do |rvr|
|
207
|
-
ns_n = rvr.getresource( domain, Resolv::DNS::Resource::IN::SOA ).mname
|
208
|
-
rvr.getaddress( ns_n ).to_s
|
209
|
-
end
|
210
|
-
|
211
|
-
sleep 3 # Initial wait
|
212
|
-
|
213
|
-
wait_until( "#{long_name} to resolve", 3.0 ) do
|
214
|
-
Resolv::DNS.open( :nameserver => ns_addr ) do |rvr|
|
215
|
-
rvr.getresources( long_name, rtype ).first
|
216
|
-
end
|
217
|
-
end
|
218
|
-
end
|
219
|
-
|
220
|
-
# Terminates an instance and permanently deletes any EBS volumes
|
221
|
-
# attached to /dev/sdh like via create. WARNING: potential for data
|
222
|
-
# loss! The minimum required propererties in iprops hash are :region
|
223
|
-
# and :id.
|
224
|
-
def aws_terminate_instance_and_ebs_volumes( iprops )
|
225
|
-
ec2 = AWS::EC2.new.regions[ iprops[ :region ] ]
|
226
|
-
inst = ec2.instances[ iprops[ :id ] ]
|
227
|
-
unless inst.exists?
|
228
|
-
raise "Instance #{iprops[:id]} does not exist in #{iprops[:region]}"
|
229
|
-
end
|
230
|
-
|
231
|
-
ebs_volumes = inst.block_devices.map do |dev|
|
232
|
-
ebs = dev[ :ebs ]
|
233
|
-
if ebs && dev[:device_name] =~ /dh\d+$/ && !ebs[:delete_on_termination]
|
234
|
-
ebs[ :volume_id ]
|
235
|
-
end
|
236
|
-
end.compact
|
237
|
-
|
238
|
-
inst.terminate
|
239
|
-
wait_until( "termination of #{inst.id}", 2.0 ) { inst.status == :terminated }
|
240
|
-
|
241
|
-
ebs_volumes = ebs_volumes.map do |vid|
|
242
|
-
volume = ec2.volumes[ vid ]
|
243
|
-
if volume.exists?
|
244
|
-
volume
|
245
|
-
else
|
246
|
-
puts "WARN: #{volume} doesn't exist"
|
247
|
-
nil
|
248
|
-
end
|
249
|
-
end.compact
|
250
|
-
|
251
|
-
ebs_volumes.each do |vol|
|
252
|
-
wait_until( "deletion of vol #{vol.id}" ) do
|
253
|
-
vol.status == :available || vol.status == :deleted
|
254
|
-
end
|
255
|
-
vol.delete if vol.status == :available
|
256
|
-
end
|
257
|
-
|
258
|
-
found = aws_find_instance( iprops )
|
259
|
-
if found
|
260
|
-
aws_instance_removed( found )
|
261
|
-
aws_write_instances
|
262
|
-
end
|
263
|
-
end
|
264
|
-
|
265
|
-
# Return instance properties, by example via iprops, either by [
|
266
|
-
# :id, :region ], :internet_name, :internet_ip, or :name, attempted
|
267
|
-
# in that order.
|
268
|
-
def aws_find_instance( iprops )
|
269
|
-
if iprops[:id]
|
270
|
-
@aws_instances.find do |r|
|
271
|
-
r[:id] == iprops[:id] && r[:region] == iprops[:region]
|
272
|
-
end
|
273
|
-
else
|
274
|
-
[ :internet_name, :internet_ip, :name ].inject( nil ) do |found, key|
|
275
|
-
found || @aws_instances.find { |r| r[key] == iprops[key] }
|
276
|
-
end
|
277
|
-
end
|
278
|
-
end
|
279
|
-
|
280
|
-
def aws_instance_added( inst )
|
281
|
-
@aws_instances << inst
|
282
|
-
@aws_instances.sort! { |p,n| p[:name] <=> n[:name] }
|
283
|
-
end
|
284
|
-
|
285
|
-
def aws_instance_removed( iprops )
|
286
|
-
@aws_instances.reject! do |row|
|
287
|
-
row[:id] == iprops[:id] && row[:region] == iprops[:region]
|
288
|
-
end
|
289
|
-
end
|
290
|
-
|
291
|
-
def wait_for_running( inst )
|
292
|
-
wait_until( "instance #{inst.id} to run", 2.0 ) { inst.status != :pending }
|
293
|
-
stat = inst.status
|
294
|
-
raise "Instance #{inst.id} has status #{stat}" unless stat == :running
|
295
|
-
nil
|
296
|
-
end
|
297
|
-
|
298
|
-
# Find running/pending instances in each of aws_regions, convert to
|
299
|
-
# props, and save in aws_instances list.
|
300
|
-
def aws_import_instances
|
301
|
-
|
302
|
-
instances = []
|
303
|
-
|
304
|
-
aws_regions.each do |region|
|
305
|
-
ec2 = AWS::EC2.new.regions[ region ]
|
306
|
-
|
307
|
-
found = ec2.instances.map do |inst|
|
308
|
-
next unless [ :running, :pending ].include?( inst.status )
|
309
|
-
aws_instance_to_props( region, inst )
|
310
|
-
end
|
311
|
-
|
312
|
-
instances += found.compact
|
313
|
-
end
|
314
|
-
|
315
|
-
@aws_instances = instances
|
316
|
-
end
|
317
|
-
|
318
|
-
def aws_instance_to_props( region, inst )
|
319
|
-
tags = inst.tags.to_h
|
320
|
-
name = tags[ 'Name' ]
|
321
|
-
roles = decode_roles( tags[ 'Roles' ] )
|
322
|
-
|
323
|
-
{ :id => inst.id,
|
324
|
-
:region => region,
|
325
|
-
:ami => inst.image_id,
|
326
|
-
:name => name,
|
327
|
-
:internet_name => inst.dns_name,
|
328
|
-
:internet_ip => inst.ip_address,
|
329
|
-
:internal_ip => inst.private_ip_address,
|
330
|
-
:instance_type => inst.instance_type,
|
331
|
-
:roles => roles }
|
332
|
-
end
|
333
|
-
|
334
|
-
# Read the aws_instances_json file, replacing in RAM AWS instances
|
335
|
-
def aws_read_instances
|
336
|
-
list = JSON.parse( IO.read( aws_instances_json ), :symbolize_names => true )
|
337
|
-
list.each do |inst|
|
338
|
-
inst[:roles] = ( inst[:roles] || [] ).map { |r| r.to_sym }
|
339
|
-
end
|
340
|
-
@aws_instances = list.sort { |p,n| p[:name] <=> n[:name] }
|
341
|
-
end
|
342
|
-
|
343
|
-
# Overwrite aws_instances_json file with the current AWS instances.
|
344
|
-
def aws_write_instances
|
345
|
-
File.open( aws_instances_json, 'w' ) do |fout|
|
346
|
-
aws_dump_instances( fout )
|
347
|
-
end
|
348
|
-
end
|
349
|
-
|
350
|
-
# Dump AWS instances as JSON but with custom layout of host per line.
|
351
|
-
def aws_dump_instances( fout = $stdout )
|
352
|
-
fout.puts '['
|
353
|
-
rows = @aws_instances.sort { |p,n| p[:name] <=> n[:name] }
|
354
|
-
rows.each_with_index do |row, i|
|
355
|
-
fout.puts( " " + JSON.generate( row, :space => ' ', :object_nl => ' ' ) +
|
356
|
-
( ( i == ( rows.length - 1 ) ) ? '' : ',' ) )
|
357
|
-
end
|
358
|
-
fout.puts ']'
|
359
|
-
end
|
360
|
-
|
361
|
-
def decode_roles( roles )
|
362
|
-
( roles || "" ).split( /\s+/ ).map { |r| r.to_sym }
|
363
|
-
end
|
364
|
-
|
365
|
-
# Runs create_lvm_volumes! if the first :lvm_volumes mount path does
|
366
|
-
# not yet exist.
|
367
|
-
def create_lvm_volumes( opts = {} )
|
368
|
-
opts = deep_merge_hashes( @aws_default_instance_options, opts )
|
369
|
-
unless exist?( opts[ :lvm_volumes ].first[1] )
|
370
|
-
create_lvm_volumes!( opts )
|
371
|
-
end
|
372
|
-
end
|
373
|
-
|
374
|
-
# Create an mdraid array from previously attached EBS volumes, then
|
375
|
-
# divvy up across N lvm mount points by ratio, creating filesystems and
|
376
|
-
# mounting. This mdadm and lvm recipe was originally derived from
|
377
|
-
# {Install MongoDB on Amazon EC2}[http://docs.mongodb.org/ecosystem/tutorial/install-mongodb-on-amazon-ec2/]
|
378
|
-
#
|
379
|
-
# === Options
|
380
|
-
# :ebs_volumes:: The number of EBS volumes previously created and
|
381
|
-
# attached to this instance.
|
382
|
-
# :lvm_volumes:: A table of [ slice, path (,name) ] rows where;
|
383
|
-
# slice is a floating point ratio in range (0.0,1.0],
|
384
|
-
# path is the mount point, and name is the lvm name,
|
385
|
-
# defaulting if omitted to the last path element. The
|
386
|
-
# sum of all slice values in the table should be 1.0.
|
387
|
-
def create_lvm_volumes!( opts = {} )
|
388
|
-
opts = deep_merge_hashes( @aws_default_instance_options, opts )
|
389
|
-
|
390
|
-
vol_count = opts[ :ebs_volumes ]
|
391
|
-
devices = vol_count.times.map { |i| "/dev/xvdh#{i+1}" }
|
392
|
-
|
393
|
-
cmd = <<-SH
|
394
|
-
#{dist_install_s( "mdadm", "lvm2", :minimal => true )}
|
395
|
-
mdadm --create /dev/md0 --level=10 --chunk=256 --raid-devices=#{vol_count} \
|
396
|
-
#{devices.join ' '}
|
397
|
-
echo "DEVICE #{devices.join ' '}" > /etc/mdadm.conf
|
398
|
-
mdadm --detail --scan >> /etc/mdadm.conf
|
399
|
-
SH
|
400
|
-
|
401
|
-
devices.each do |d|
|
402
|
-
cmd << <<-SH
|
403
|
-
blockdev --setra 128 #{d}
|
404
|
-
SH
|
405
|
-
end
|
406
|
-
|
407
|
-
cmd << <<-SH
|
408
|
-
blockdev --setra 128 /dev/md0
|
409
|
-
dd if=/dev/zero of=/dev/md0 bs=512 count=1
|
410
|
-
pvcreate /dev/md0
|
411
|
-
vgcreate vg0 /dev/md0
|
412
|
-
SH
|
413
|
-
|
414
|
-
opts[ :lvm_volumes ].each do | slice, path, name |
|
415
|
-
name ||= ( path =~ /\/(\w+)$/ ) && $1
|
416
|
-
cmd << <<-SH
|
417
|
-
lvcreate -l #{(slice * 100).round}%vg -n #{name} vg0
|
418
|
-
mke2fs -t ext4 -F /dev/vg0/#{name}
|
419
|
-
if [ -d #{path} ]; then
|
420
|
-
rm -rf #{path}
|
421
|
-
fi
|
422
|
-
mkdir -p #{path}
|
423
|
-
echo '/dev/vg0/#{name} #{path} ext4 defaults,auto,noatime,noexec 0 0' >> /etc/fstab
|
424
|
-
mount #{path}
|
425
|
-
SH
|
426
|
-
end
|
427
|
-
|
428
|
-
sudo cmd
|
429
|
-
end
|
430
|
-
|
431
|
-
# Wait until block returns truthy, sleeping for freq seconds between
|
432
|
-
# attempts. Writes desc and a sequence of DOTs on a single line
|
433
|
-
# until complete.
|
434
|
-
def wait_until( desc, freq = 1.0 )
|
435
|
-
$stdout.write( "Waiting for " + desc )
|
436
|
-
until (ret = yield) do
|
437
|
-
$stdout.write '.'
|
438
|
-
sleep freq
|
439
|
-
end
|
440
|
-
ret
|
441
|
-
ensure
|
442
|
-
puts
|
443
|
-
end
|
444
|
-
|
445
|
-
def dot_terminate( name )
|
446
|
-
( name =~ /\.$/ ) ? name : ( name + '.' )
|
447
|
-
end
|
448
|
-
end
|
data/lib/syncwrap/common.rb
DELETED
@@ -1,161 +0,0 @@
|
|
1
|
-
#--
|
2
|
-
# Copyright (c) 2011-2013 David Kellum
|
3
|
-
#
|
4
|
-
# Licensed under the Apache License, Version 2.0 (the "License"); you
|
5
|
-
# may not use this file except in compliance with the License. You may
|
6
|
-
# obtain a copy of the License at
|
7
|
-
#
|
8
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
-
#
|
10
|
-
# Unless required by applicable law or agreed to in writing, software
|
11
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
13
|
-
# implied. See the License for the specific language governing
|
14
|
-
# permissions and limitations under the License.
|
15
|
-
#++
|
16
|
-
|
17
|
-
require 'syncwrap/base'
|
18
|
-
|
19
|
-
# Common utility methods and variables.
|
20
|
-
module SyncWrap::Common
|
21
|
-
|
22
|
-
# The prefix for local non-distro installs (default: /usr/local)
|
23
|
-
attr_accessor :common_prefix
|
24
|
-
|
25
|
-
def initialize
|
26
|
-
super
|
27
|
-
@common_prefix = '/usr/local'
|
28
|
-
end
|
29
|
-
|
30
|
-
# Return true if remote file exists, as per "test -e"
|
31
|
-
def exist?( file )
|
32
|
-
exec_conditional { run "test -e #{file}" } == 0
|
33
|
-
end
|
34
|
-
|
35
|
-
# Put files or entire directories to remote.
|
36
|
-
#
|
37
|
-
# rput( src..., dest, {options} )
|
38
|
-
# rput( src, {options} )
|
39
|
-
#
|
40
|
-
# A trailing hash is interpreted as options, see below.
|
41
|
-
#
|
42
|
-
# If there is two or more remaining arguments, the last is
|
43
|
-
# interpreted as the remote destination. If there is a single
|
44
|
-
# remaining argument, the destination is implied by finding its base
|
45
|
-
# directory and prepending '/'. Thus for example:
|
46
|
-
#
|
47
|
-
# rput( 'etc/gemrc', :user => 'root' )
|
48
|
-
#
|
49
|
-
# has an implied destination of: `/etc/`. The src and destination
|
50
|
-
# are intrepreted as by `rsync`: glob patterns are expanded and
|
51
|
-
# trailing '/' is significant.
|
52
|
-
#
|
53
|
-
# ==== Options
|
54
|
-
# :user:: Files should be owned on destination by a user other than
|
55
|
-
# installer (ex: 'root') (implies sudo)
|
56
|
-
# :perms:: Permissions to set for install files.
|
57
|
-
# :excludes:: One or more rsync compatible --filter excludes, or
|
58
|
-
# :dev which excludes common developmoent tree droppings
|
59
|
-
# like '*~'
|
60
|
-
def rput( *args )
|
61
|
-
opts = args.last.is_a?( Hash ) && args.pop || {}
|
62
|
-
|
63
|
-
if args.length == 1
|
64
|
-
abspath = "/" + args.first
|
65
|
-
abspath = File.dirname( abspath ) + '/' unless abspath =~ %r{/$}
|
66
|
-
else
|
67
|
-
abspath = args.pop
|
68
|
-
end
|
69
|
-
|
70
|
-
flags = []
|
71
|
-
|
72
|
-
excludes = Array( opts[ :excludes ] )
|
73
|
-
flags += excludes.map do |e|
|
74
|
-
if e == :dev
|
75
|
-
'--cvs-exclude'
|
76
|
-
else
|
77
|
-
"--filter=#{e}"
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
if opts[ :perms ]
|
82
|
-
flags << '-p'
|
83
|
-
flags << "--chmod=#{opts[ :perms ]}"
|
84
|
-
end
|
85
|
-
|
86
|
-
if opts[ :user ]
|
87
|
-
# Use sudo to place files at remote.
|
88
|
-
user = opts[ :user ] || 'root'
|
89
|
-
flags << ( if user != 'root'
|
90
|
-
"--rsync-path=sudo -u #{user} rsync"
|
91
|
-
else
|
92
|
-
"--rsync-path=sudo rsync"
|
93
|
-
end )
|
94
|
-
end
|
95
|
-
|
96
|
-
cmd = [ flags, args, [ target_host, abspath ].join(':') ].flatten.compact
|
97
|
-
rsync( *cmd )
|
98
|
-
|
99
|
-
end
|
100
|
-
|
101
|
-
# Run args as shell command on the remote host. A line delimited
|
102
|
-
# argument is interpreted as multiple commands, otherwise arguments
|
103
|
-
# are joined as a single command.
|
104
|
-
#
|
105
|
-
# A trailing Hash is interpreted as options, see below.
|
106
|
-
#
|
107
|
-
# ==== options
|
108
|
-
# :error:: Set the error handling mode: If `:exit`, causes "set -e" to
|
109
|
-
# be passed as the first line of a multi-line
|
110
|
-
# command. (Default: :exit)
|
111
|
-
def run( *args )
|
112
|
-
raise "Include a remoting-specific module, e.g. RemoteTask"
|
113
|
-
end
|
114
|
-
|
115
|
-
# Run args under sudo on remote host. A line delimited argument is
|
116
|
-
# interpreted as multiple commands, otherwise arguments are joined
|
117
|
-
# as a single command.
|
118
|
-
#
|
119
|
-
# A trailing Hash is interpreted as options, see below.
|
120
|
-
#
|
121
|
-
# ==== options
|
122
|
-
# :user:: Run as specified user (default: root)
|
123
|
-
# :flags:: Additional sudo flags
|
124
|
-
# :shell:: Run command in a shell by wrapping it in sh -c "", and
|
125
|
-
# escaping quotes in the original joined args command.
|
126
|
-
# (default: true)
|
127
|
-
# :error:: Set the error handling mode: If `:exit`, causes "set -e" to
|
128
|
-
# be passed as the first line of a multi-line
|
129
|
-
# command. (Default: :exit, only applies if :shell)
|
130
|
-
def sudo( *args )
|
131
|
-
raise "Include a remoting-specific module, e.g. RemoteTask"
|
132
|
-
end
|
133
|
-
|
134
|
-
# Return the exit status of the first non-zero command result, or 0
|
135
|
-
# if all commands succeeded.
|
136
|
-
def exec_conditional
|
137
|
-
raise "Include a remoting-specific module, e.g. RemoteTask"
|
138
|
-
end
|
139
|
-
|
140
|
-
# Implement rsync as used by rput. Note that args should not be
|
141
|
-
# passed through shell interpretation, eg run via system( Array )
|
142
|
-
def rsync( *args )
|
143
|
-
raise "Include a remoting-specific module, e.g. RemoteTask"
|
144
|
-
end
|
145
|
-
|
146
|
-
# Returns the current target host when called from a running task.
|
147
|
-
def target_host
|
148
|
-
raise "Include a remoting-specific module, e.g. RemoteTask"
|
149
|
-
end
|
150
|
-
|
151
|
-
def deep_merge_hashes( h1, h2 )
|
152
|
-
h1.merge( h2 ) do |key, v1, v2|
|
153
|
-
if v1.is_a?( Hash ) && v2.is_a?( Hash )
|
154
|
-
deep_merge_hashes( v1, v2 )
|
155
|
-
else
|
156
|
-
v2
|
157
|
-
end
|
158
|
-
end
|
159
|
-
end
|
160
|
-
|
161
|
-
end
|
data/lib/syncwrap/ec2.rb
DELETED
@@ -1,59 +0,0 @@
|
|
1
|
-
#--
|
2
|
-
# Copyright (c) 2011-2013 David Kellum
|
3
|
-
#
|
4
|
-
# Licensed under the Apache License, Version 2.0 (the "License"); you
|
5
|
-
# may not use this file except in compliance with the License. You may
|
6
|
-
# obtain a copy of the License at
|
7
|
-
#
|
8
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
-
#
|
10
|
-
# Unless required by applicable law or agreed to in writing, software
|
11
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
13
|
-
# implied. See the License for the specific language governing
|
14
|
-
# permissions and limitations under the License.
|
15
|
-
#++
|
16
|
-
|
17
|
-
require 'syncwrap/common'
|
18
|
-
|
19
|
-
# Provisions for common Amazon EC2 image tasks.
|
20
|
-
module SyncWrap::EC2
|
21
|
-
include SyncWrap::Common
|
22
|
-
|
23
|
-
# The device name of the ephemeral storage device
|
24
|
-
# (default: 'xvdb')
|
25
|
-
attr_accessor :ec2_es_device
|
26
|
-
|
27
|
-
def initialize
|
28
|
-
super
|
29
|
-
|
30
|
-
@ec2_es_device = 'xvdb'
|
31
|
-
end
|
32
|
-
|
33
|
-
# WARNING: Destructive if run!
|
34
|
-
# Re-mkfs /mnt partition as ext4 if its ec2_es_device and is
|
35
|
-
# currently ext3
|
36
|
-
def ec2_reformat_mnt_as_ext4
|
37
|
-
rc = exec_conditional do
|
38
|
-
run "mount | grep '/dev/#{ec2_es_device} on /mnt'"
|
39
|
-
end
|
40
|
-
raise "Device /dev/#{ec2_es_device} not mounted on /mnt" unless rc == 0
|
41
|
-
|
42
|
-
rc = exec_conditional do
|
43
|
-
run "mount | grep '/dev/#{ec2_es_device} on /mnt type ext3'"
|
44
|
-
end
|
45
|
-
ec2_reformat_mnt_as_ext4! if rc == 0
|
46
|
-
end
|
47
|
-
|
48
|
-
# WARNING: Destructive!
|
49
|
-
# Re-mkfs /mnt partition as ext4
|
50
|
-
# See: http://serverfault.com/questions/317009
|
51
|
-
def ec2_reformat_mnt_as_ext4!
|
52
|
-
sudo <<-SH
|
53
|
-
umount /mnt
|
54
|
-
mkfs -t ext4 /dev/#{ec2_es_device}
|
55
|
-
mount /mnt
|
56
|
-
SH
|
57
|
-
end
|
58
|
-
|
59
|
-
end
|