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
@@ -0,0 +1,236 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2011-2014 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 'time'
|
18
|
+
|
19
|
+
require 'syncwrap/amazon_ws'
|
20
|
+
require 'syncwrap/host'
|
21
|
+
|
22
|
+
module SyncWrap
|
23
|
+
|
24
|
+
# Amazon EC2 host provider. Supports importing, creating, and
|
25
|
+
# terminating hosts.
|
26
|
+
#
|
27
|
+
# == Synopsis
|
28
|
+
#
|
29
|
+
# Add the following to your sync.rb
|
30
|
+
#
|
31
|
+
# ec2 = AmazonEC2.new( space )
|
32
|
+
#
|
33
|
+
# Then add profiles via #profile, as needed.
|
34
|
+
#
|
35
|
+
class AmazonEC2
|
36
|
+
include AmazonWS
|
37
|
+
|
38
|
+
# FIXME: Interim strategy: use AmazonWS and defer deciding final
|
39
|
+
# organization.
|
40
|
+
|
41
|
+
# The json configuration file path, parsed and passed to
|
42
|
+
# AWS::config. This file should contain a json object with the
|
43
|
+
# minimal required keys: access_key_id, secret_access_key. A
|
44
|
+
# relative path is interpreted relative to the :sync_file_path
|
45
|
+
# option if provided on construction. (default: private/aws.json)
|
46
|
+
attr_accessor :aws_config
|
47
|
+
|
48
|
+
def initialize( space, opts = {} )
|
49
|
+
super()
|
50
|
+
@profiles = {}
|
51
|
+
@space = space
|
52
|
+
@aws_config = 'private/aws.json'
|
53
|
+
opts = opts.dup
|
54
|
+
sync_file_path = opts.delete( :sync_file_path )
|
55
|
+
opts.each { |name, val| send( name.to_s + '=', val ) }
|
56
|
+
|
57
|
+
# Look up aws_config (i.e. default private/aws.json) relative to
|
58
|
+
# the sync file path.
|
59
|
+
if @aws_config
|
60
|
+
if sync_file_path
|
61
|
+
aws_configure( File.expand_path( @aws_config, sync_file_path ) )
|
62
|
+
else
|
63
|
+
aws_configure( @aws_config )
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Define a host profile by Symbol key and Hash value.
|
69
|
+
#
|
70
|
+
# Profiles may inherit properties from a :base_profile, either
|
71
|
+
# specified by that key, or the :default key profile. The
|
72
|
+
# base_profile must be defined in advance (above in the sync
|
73
|
+
# file). When merging profile to any base_profile, the :roles
|
74
|
+
# property is concatenated via set union. All other properties are
|
75
|
+
# overwritten.
|
76
|
+
#
|
77
|
+
# FIXME: All other profile properties are as currently defined by
|
78
|
+
# #aws_create_instance.
|
79
|
+
def profile( key, profile )
|
80
|
+
profile = profile.dup
|
81
|
+
base = profile.delete( :base_profile ) || :default
|
82
|
+
base_profile = @profiles[ base ]
|
83
|
+
if base_profile
|
84
|
+
profile = base_profile.merge( profile )
|
85
|
+
if base_profile[ :roles ] && profile[ :roles ]
|
86
|
+
profile[ :roles ] = ( base_profile[ :roles ] | profile[ :roles ] )
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
@profiles[ key ] = profile
|
91
|
+
end
|
92
|
+
|
93
|
+
def import_hosts( regions, output_file )
|
94
|
+
hlist = import_host_props( regions )
|
95
|
+
unless hlist.empty?
|
96
|
+
|
97
|
+
hlist.map! do |props|
|
98
|
+
props[:name] ||= props[:id].to_s
|
99
|
+
Host.new( space, props )
|
100
|
+
end
|
101
|
+
|
102
|
+
time = Time.now.utc
|
103
|
+
cmt = "\n# Import of AWS #{regions.join ','} on #{time.iso8601}"
|
104
|
+
append_host_definitions( hlist, cmt, output_file )
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def create_hosts( count, profile_key, name, output_file )
|
109
|
+
profile = @profiles[ profile_key ].dup or
|
110
|
+
raise "Profile #{profile_key} not registered"
|
111
|
+
|
112
|
+
# FIXME: Support profiles overrides.
|
113
|
+
# Also add some targeted CLI overrides (like for :availability_zones)
|
114
|
+
|
115
|
+
if profile[ :user_data ] == :ec2_user_sudo
|
116
|
+
profile[ :user_data ] = ec2_user_data
|
117
|
+
end
|
118
|
+
|
119
|
+
dname = profile.delete( :default_name )
|
120
|
+
name ||= dname
|
121
|
+
|
122
|
+
count.times do
|
123
|
+
hname = if count == 1
|
124
|
+
if space.host_names.include?( name )
|
125
|
+
raise "Host #{name} already exists!"
|
126
|
+
end
|
127
|
+
name
|
128
|
+
else
|
129
|
+
find_name( name )
|
130
|
+
end
|
131
|
+
props = aws_create_instance( hname, profile )
|
132
|
+
host = space.host( props )
|
133
|
+
append_host_definitions( [ host ], nil, output_file )
|
134
|
+
host[ :just_created ] = true #after so this isn't written
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def terminate_hosts( names, delete_attached_storage, sync_file )
|
139
|
+
names.each do |name|
|
140
|
+
if space.host_names.include?( name )
|
141
|
+
host = space.host( name )
|
142
|
+
raise "Host #{name} missing :id" unless host[:id]
|
143
|
+
raise "Host #{name} missing :region" unless host[:region]
|
144
|
+
aws_terminate_instance( host, delete_attached_storage )
|
145
|
+
delete_host_definition( host, sync_file )
|
146
|
+
else
|
147
|
+
raise "Host #{name} not found in Space, sync file."
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
private
|
153
|
+
|
154
|
+
attr_reader :space
|
155
|
+
|
156
|
+
def find_name( prefix )
|
157
|
+
host_names = space.host_names
|
158
|
+
i = 1
|
159
|
+
name = nil
|
160
|
+
loop do
|
161
|
+
name = "%s-%2d" % [ prefix, i ]
|
162
|
+
break if ! host_names.include?( name )
|
163
|
+
i += 1
|
164
|
+
end
|
165
|
+
name
|
166
|
+
end
|
167
|
+
|
168
|
+
def append_host_definitions( hosts, comment, output_file )
|
169
|
+
File.open( output_file, "a" ) do |out|
|
170
|
+
out.puts comment if comment
|
171
|
+
|
172
|
+
hosts.each do |host|
|
173
|
+
props = host.props.dup
|
174
|
+
props.delete( :name )
|
175
|
+
|
176
|
+
roles = ( host.roles - [:all] ).map { |s| ':' + s.to_s }.join ', '
|
177
|
+
roles << ',' unless roles.empty?
|
178
|
+
props = host.props.map do |key,val|
|
179
|
+
"#{key}: #{val.inspect}" unless key == :name
|
180
|
+
end.compact.join ",\n "
|
181
|
+
|
182
|
+
out.puts "host( '#{host.name}', #{roles}"
|
183
|
+
out.puts " #{props} ) # :auto-generated"
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
def delete_host_definition( host, sync_file )
|
189
|
+
lines = IO.readlines( sync_file )
|
190
|
+
out_lines = []
|
191
|
+
state = :find
|
192
|
+
lines.each do |line|
|
193
|
+
if state == :find
|
194
|
+
if line =~ /^host\( '#{host.name}',/
|
195
|
+
state = :just_found
|
196
|
+
else
|
197
|
+
out_lines << line
|
198
|
+
end
|
199
|
+
end
|
200
|
+
if state == :just_found || state == :found
|
201
|
+
if state == :found && line =~ /^host\(/
|
202
|
+
state = :no_end
|
203
|
+
break
|
204
|
+
end
|
205
|
+
state = :found
|
206
|
+
if line =~ /^\s*[^#].*\) # :auto-generated$/
|
207
|
+
state = :deleted
|
208
|
+
end
|
209
|
+
elsif state == :deleted
|
210
|
+
out_lines << line
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
if state == :deleted
|
215
|
+
File.open( sync_file, "w" ) do |out|
|
216
|
+
out.puts out_lines
|
217
|
+
end
|
218
|
+
else
|
219
|
+
$stderr.puts( "WARNING: #{sync_file} entry not deleted (#{state})" )
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
def ec2_user_data( user = 'ec2-user' )
|
224
|
+
#FIXME: Utility module for this (+ Users sudoers)?
|
225
|
+
script = <<-SH
|
226
|
+
#!/bin/sh -e
|
227
|
+
echo '#{user} ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers.d/#{user}
|
228
|
+
echo 'Defaults:#{user} !requiretty' >> /etc/sudoers.d/#{user}
|
229
|
+
chmod 440 /etc/sudoers.d/#{user}
|
230
|
+
SH
|
231
|
+
script.split( "\n" ).map { |l| l.strip }.join( "\n" )
|
232
|
+
end
|
233
|
+
|
234
|
+
end
|
235
|
+
|
236
|
+
end
|
@@ -0,0 +1,308 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2011-2014 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 'aws-sdk'
|
18
|
+
require 'resolv'
|
19
|
+
require 'json'
|
20
|
+
|
21
|
+
module SyncWrap
|
22
|
+
|
23
|
+
# Supports host provisioning in EC2 via AWS APIs, creating and
|
24
|
+
# attaching EBS volumes, and creating Route53 record sets.
|
25
|
+
module AmazonWS
|
26
|
+
|
27
|
+
# Default options for Route53 record set creation
|
28
|
+
attr_accessor :route53_default_rs_options
|
29
|
+
|
30
|
+
# DNS Resolver options for testing Route53 (default: Use public
|
31
|
+
# google name servers to avoid local negative caching)
|
32
|
+
attr_accessor :resolver_options
|
33
|
+
|
34
|
+
def initialize
|
35
|
+
@default_instance_options = {
|
36
|
+
ebs_volumes: 0,
|
37
|
+
ebs_volume_options: { size: 16 }, #gb
|
38
|
+
lvm_volumes: [ [ 1.00, '/data' ] ],
|
39
|
+
security_groups: [ :default ],
|
40
|
+
instance_type: 'm1.medium',
|
41
|
+
region: 'us-east-1'
|
42
|
+
}
|
43
|
+
@route53_default_rs_options = {
|
44
|
+
ttl: 300,
|
45
|
+
wait: true
|
46
|
+
}
|
47
|
+
|
48
|
+
@resolver_options = {
|
49
|
+
nameserver: [ '8.8.8.8', '8.8.4.4' ]
|
50
|
+
}
|
51
|
+
|
52
|
+
super
|
53
|
+
end
|
54
|
+
|
55
|
+
protected
|
56
|
+
|
57
|
+
def aws_configure( json_file )
|
58
|
+
AWS.config( JSON.parse( IO.read( json_file ),
|
59
|
+
symbolize_names: true ) )
|
60
|
+
end
|
61
|
+
|
62
|
+
# Create a security_group given name and options. :region is the
|
63
|
+
# only required option, :description is good to have. Currently
|
64
|
+
# this is a no-op if the security group already exists.
|
65
|
+
def aws_create_security_group( name, opts = {} )
|
66
|
+
opts = opts.dup
|
67
|
+
region = opts.delete( :region )
|
68
|
+
ec2 = AWS::EC2.new.regions[ region ]
|
69
|
+
unless ec2.security_groups.find { |sg| sg.name == name }
|
70
|
+
sg = ec2.security_groups.create( name, opts )
|
71
|
+
|
72
|
+
# FIXME: Allow ssh on the "default" region named group
|
73
|
+
if name == region
|
74
|
+
sg.authorize_ingress(:tcp, 22)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# Create an instance, using name as the Name tag and assumed
|
80
|
+
# host name. For options see
|
81
|
+
# {AWS::EC2::InstanceCollection.create}[http://docs.aws.amazon.com/AWSRubySDK/latest/AWS/EC2/InstanceCollection.html#create-instance_method]
|
82
|
+
# with the following additions/differences:
|
83
|
+
#
|
84
|
+
# :count:: must be 1 or unspecified.
|
85
|
+
# :region:: Default 'us-east-1'
|
86
|
+
# :security_groups:: As per aws-sdk, but the special :default value
|
87
|
+
# is replaced with a single security group with
|
88
|
+
# same name as the :region.
|
89
|
+
# :ebs_volumes:: The number of EBS volumes to create an attach to this instance.
|
90
|
+
# :ebs_volume_options:: A nested Hash of options, as per
|
91
|
+
# {AWS::EC2::VolumeCollection.create}[http://docs.aws.amazon.com/AWSRubySDK/latest/AWS/EC2/VolumeCollection.html#create-instance_method]
|
92
|
+
# with custom default :size 16 GB, and the same
|
93
|
+
# :availibility_zone as the instance.
|
94
|
+
# :lvm_volumes:: Ignored here.
|
95
|
+
# :roles:: Array of role Strings or Symbols (applied as Roles tag)
|
96
|
+
def aws_create_instance( name, opts = {} )
|
97
|
+
opts = deep_merge_hashes( @default_instance_options, opts )
|
98
|
+
region = opts.delete( :region )
|
99
|
+
opts.delete( :lvm_volumes ) #unused here
|
100
|
+
|
101
|
+
ec2 = AWS::EC2.new.regions[ region ]
|
102
|
+
|
103
|
+
iopts = opts.dup
|
104
|
+
iopts.delete( :ebs_volumes )
|
105
|
+
iopts.delete( :ebs_volume_options )
|
106
|
+
iopts.delete( :roles )
|
107
|
+
|
108
|
+
if iopts[ :count ] && iopts[ :count ] != 1
|
109
|
+
raise ":count #{iopts[ :count ]} != 1 is not supported"
|
110
|
+
end
|
111
|
+
|
112
|
+
iopts[ :security_groups ].map! do |sg|
|
113
|
+
sg == :default ? region : sg
|
114
|
+
end
|
115
|
+
|
116
|
+
iopts[ :security_groups ].each do |sg|
|
117
|
+
aws_create_security_group( sg, region: region )
|
118
|
+
end
|
119
|
+
|
120
|
+
inst = ec2.instances.create( iopts )
|
121
|
+
|
122
|
+
inst.add_tag( 'Name', value: name )
|
123
|
+
|
124
|
+
if opts[ :roles ]
|
125
|
+
inst.add_tag( 'Roles', value: opts[ :roles ].join(' ') )
|
126
|
+
end
|
127
|
+
|
128
|
+
wait_for_running( inst )
|
129
|
+
|
130
|
+
# FIXME: Split method
|
131
|
+
# FIXME: Support alternative syntax, i.e
|
132
|
+
# { ebs_volumes: [ [4, size: 48], [2, size: 8] ] }
|
133
|
+
|
134
|
+
if opts[ :ebs_volumes ] > 0
|
135
|
+
vopts = { availability_zone: inst.availability_zone }.
|
136
|
+
merge( opts[ :ebs_volume_options ] )
|
137
|
+
|
138
|
+
attachments = opts[ :ebs_volumes ].times.map do |i|
|
139
|
+
vol = ec2.volumes.create( vopts )
|
140
|
+
wait_until( vol.id, 0.5 ) { vol.status == :available }
|
141
|
+
vol.attach_to( inst, "/dev/sdh#{i+1}" ) #=> Attachment
|
142
|
+
end
|
143
|
+
|
144
|
+
wait_until( "volumes to attach" ) do
|
145
|
+
!( attachments.any? { |a| a.status == :attaching } )
|
146
|
+
end
|
147
|
+
end
|
148
|
+
#FIXME: end
|
149
|
+
|
150
|
+
instance_to_props( region, inst )
|
151
|
+
end
|
152
|
+
|
153
|
+
# Create a Route53 DNS CNAME from iprops :name to :internet_name.
|
154
|
+
# Options are per {AWS::Route53::ResourceRecordSetCollection.create}[http://docs.aws.amazon.com/AWSRubySDK/latest/AWS/Route53/ResourceRecordSetCollection.html#create-instance_method]
|
155
|
+
# (currently undocumented) with the following additions:
|
156
|
+
#
|
157
|
+
# :domain_name:: name of the hosted zone and suffix for
|
158
|
+
# CNAME. Should terminate in a DOT '.'
|
159
|
+
# :wait:: If true, wait for CNAME to resolve
|
160
|
+
def route53_create_host_cname( iprops, opts = {} )
|
161
|
+
opts = deep_merge_hashes( @route53_default_rs_options, opts )
|
162
|
+
dname = dot_terminate( opts.delete( :domain_name ) )
|
163
|
+
do_wait = opts.delete( :wait )
|
164
|
+
rs_opts = opts.
|
165
|
+
merge( resource_records: [ {value: iprops[:internet_name]} ] )
|
166
|
+
|
167
|
+
r53 = AWS::Route53.new
|
168
|
+
zone = r53.hosted_zones.find { |hz| hz.name == dname } or
|
169
|
+
raise "Route53 Hosted Zone name #{dname} not found"
|
170
|
+
long_name = [ iprops[:name], dname ].join('.')
|
171
|
+
zone.rrsets.create( long_name, 'CNAME', rs_opts )
|
172
|
+
wait_for_dns_resolve( long_name, dname ) if do_wait
|
173
|
+
end
|
174
|
+
|
175
|
+
def wait_for_dns_resolve( long_name,
|
176
|
+
domain,
|
177
|
+
rtype = Resolv::DNS::Resource::IN::CNAME )
|
178
|
+
|
179
|
+
ns_addr = Resolv::DNS.open( @resolver_options ) do |rvr|
|
180
|
+
ns_n = rvr.getresource( domain, Resolv::DNS::Resource::IN::SOA ).mname
|
181
|
+
rvr.getaddress( ns_n ).to_s
|
182
|
+
end
|
183
|
+
|
184
|
+
sleep 3 # Initial wait
|
185
|
+
|
186
|
+
wait_until( "#{long_name} to resolve", 3.0 ) do
|
187
|
+
Resolv::DNS.open( nameserver: ns_addr ) do |rvr|
|
188
|
+
rvr.getresources( long_name, rtype ).first
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
# Terminate an instance and wait for it to be terminated. If
|
194
|
+
# requested, /dev/sdh# attached EBS volumes which are not
|
195
|
+
# otherwise marked for :delete_on_termination will _also_ be
|
196
|
+
# terminated. The minimum required properties in iprops are
|
197
|
+
# :region and :id.
|
198
|
+
#
|
199
|
+
# _WARNING_: data _will_ be lost!
|
200
|
+
def aws_terminate_instance( iprops, delete_attached_storage = false )
|
201
|
+
ec2 = AWS::EC2.new.regions[ iprops[ :region ] ]
|
202
|
+
inst = ec2.instances[ iprops[ :id ] ]
|
203
|
+
unless inst.exists?
|
204
|
+
raise "Instance #{iprops[:id]} does not exist in #{iprops[:region]}"
|
205
|
+
end
|
206
|
+
|
207
|
+
ebs_volumes = []
|
208
|
+
if delete_attached_storage
|
209
|
+
ebs_volumes = inst.block_devices.map do |dev|
|
210
|
+
ebs = dev[ :ebs ]
|
211
|
+
if ebs && dev[:device_name] =~ /dh\d+$/ && !ebs[:delete_on_termination]
|
212
|
+
ebs[ :volume_id ]
|
213
|
+
end
|
214
|
+
end.compact
|
215
|
+
end
|
216
|
+
|
217
|
+
inst.terminate
|
218
|
+
wait_until( "termination of #{inst.id}", 2.0 ) { inst.status == :terminated }
|
219
|
+
|
220
|
+
ebs_volumes = ebs_volumes.map do |vid|
|
221
|
+
volume = ec2.volumes[ vid ]
|
222
|
+
if volume.exists?
|
223
|
+
volume
|
224
|
+
else
|
225
|
+
puts "WARN: #{volume} doesn't exist"
|
226
|
+
nil
|
227
|
+
end
|
228
|
+
end.compact
|
229
|
+
|
230
|
+
ebs_volumes.each do |vol|
|
231
|
+
wait_until( "deletion of vol #{vol.id}" ) do
|
232
|
+
vol.status == :available || vol.status == :deleted
|
233
|
+
end
|
234
|
+
vol.delete if vol.status == :available
|
235
|
+
end
|
236
|
+
|
237
|
+
end
|
238
|
+
|
239
|
+
def wait_for_running( inst )
|
240
|
+
wait_until( "instance #{inst.id} to run", 2.0 ) { inst.status != :pending }
|
241
|
+
stat = inst.status
|
242
|
+
raise "Instance #{inst.id} has status #{stat}" unless stat == :running
|
243
|
+
nil
|
244
|
+
end
|
245
|
+
|
246
|
+
# Find running or pending instances in each region String and
|
247
|
+
# convert to a HostList.
|
248
|
+
def import_host_props( regions )
|
249
|
+
regions.inject([]) do |insts, region|
|
250
|
+
ec2 = AWS::EC2.new.regions[ region ]
|
251
|
+
|
252
|
+
found = ec2.instances.map do |inst|
|
253
|
+
next unless [ :running, :pending ].include?( inst.status )
|
254
|
+
instance_to_props( region, inst )
|
255
|
+
end
|
256
|
+
|
257
|
+
insts + found.compact
|
258
|
+
end
|
259
|
+
|
260
|
+
end
|
261
|
+
|
262
|
+
def instance_to_props( region, inst )
|
263
|
+
tags = inst.tags.to_h
|
264
|
+
|
265
|
+
{ id: inst.id,
|
266
|
+
region: region,
|
267
|
+
ami: inst.image_id,
|
268
|
+
name: tags[ 'Name' ],
|
269
|
+
internet_name: inst.dns_name,
|
270
|
+
internet_ip: inst.ip_address,
|
271
|
+
internal_ip: inst.private_ip_address,
|
272
|
+
instance_type: inst.instance_type,
|
273
|
+
roles: decode_roles( tags[ 'Roles' ] ) }
|
274
|
+
end
|
275
|
+
|
276
|
+
def decode_roles( roles )
|
277
|
+
( roles || "" ).split( /\s+/ ).map { |r| r.to_sym }
|
278
|
+
end
|
279
|
+
|
280
|
+
# Wait until block returns truthy, sleeping for freq seconds
|
281
|
+
# between attempts. Writes desc and a sequence of DOTs on a single
|
282
|
+
# line until complete.
|
283
|
+
def wait_until( desc, freq = 1.0 )
|
284
|
+
$stdout.write( "Waiting for " + desc )
|
285
|
+
until (ret = yield) do
|
286
|
+
$stdout.write '.'
|
287
|
+
sleep freq
|
288
|
+
end
|
289
|
+
ret
|
290
|
+
ensure
|
291
|
+
puts
|
292
|
+
end
|
293
|
+
|
294
|
+
def dot_terminate( name )
|
295
|
+
( name =~ /\.$/ ) ? name : ( name + '.' )
|
296
|
+
end
|
297
|
+
|
298
|
+
def deep_merge_hashes( h1, h2 )
|
299
|
+
h1.merge( h2 ) do |key, v1, v2|
|
300
|
+
if v1.is_a?( Hash ) && v2.is_a?( Hash )
|
301
|
+
deep_merge_hashes( v1, v2 )
|
302
|
+
else
|
303
|
+
v2
|
304
|
+
end
|
305
|
+
end
|
306
|
+
end
|
307
|
+
end
|
308
|
+
end
|
data/lib/syncwrap/base.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
#--
|
2
|
-
# Copyright (c) 2011-
|
2
|
+
# Copyright (c) 2011-2014 David Kellum
|
3
3
|
#
|
4
4
|
# Licensed under the Apache License, Version 2.0 (the "License"); you
|
5
5
|
# may not use this file except in compliance with the License. You may
|
@@ -15,5 +15,7 @@
|
|
15
15
|
#++
|
16
16
|
|
17
17
|
module SyncWrap
|
18
|
-
VERSION='
|
18
|
+
VERSION='2.0.0'
|
19
|
+
|
20
|
+
GEM_ROOT = File.dirname(File.dirname(File.dirname(__FILE__))) # :nodoc:
|
19
21
|
end
|