syncwrap 1.5.2 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (107) hide show
  1. checksums.yaml +7 -0
  2. data/History.rdoc +19 -0
  3. data/Manifest.txt +82 -34
  4. data/README.rdoc +96 -48
  5. data/Rakefile +0 -65
  6. data/bin/syncwrap +27 -0
  7. data/examples/LAYOUT.rdoc +70 -0
  8. data/examples/Rakefile +16 -0
  9. data/examples/ec2.rb +44 -0
  10. data/examples/hello.rb +14 -0
  11. data/examples/hello_binding.rb +27 -0
  12. data/examples/jruby.rb +11 -0
  13. data/examples/private/aws.json +4 -0
  14. data/examples/rput.rb +24 -0
  15. data/examples/sync/home/bob/.ssh/authorized_keys +1 -0
  16. data/examples/sync/tmp/sample.erb +3 -0
  17. data/lib/syncwrap/amazon_ec2.rb +236 -0
  18. data/lib/syncwrap/amazon_ws.rb +308 -0
  19. data/lib/syncwrap/base.rb +4 -2
  20. data/lib/syncwrap/cli.rb +328 -0
  21. data/lib/syncwrap/component.rb +443 -0
  22. data/lib/syncwrap/components/commercial_jdk.rb +76 -0
  23. data/lib/syncwrap/components/cruby_vm.rb +144 -0
  24. data/lib/syncwrap/components/etc_hosts.rb +44 -0
  25. data/lib/syncwrap/{geminabox.rb → components/geminabox.rb} +12 -17
  26. data/lib/syncwrap/components/hashdot.rb +97 -0
  27. data/lib/syncwrap/components/iyyov.rb +144 -0
  28. data/lib/syncwrap/components/iyyov_daemon.rb +125 -0
  29. data/lib/syncwrap/components/jruby_vm.rb +122 -0
  30. data/lib/syncwrap/components/mdraid.rb +204 -0
  31. data/lib/syncwrap/components/network.rb +99 -0
  32. data/lib/syncwrap/components/open_jdk.rb +70 -0
  33. data/lib/syncwrap/components/postgresql.rb +159 -0
  34. data/lib/syncwrap/components/qpid.rb +303 -0
  35. data/lib/syncwrap/components/rhel.rb +71 -0
  36. data/lib/syncwrap/components/run_user.rb +99 -0
  37. data/lib/syncwrap/components/ubuntu.rb +85 -0
  38. data/lib/syncwrap/components/users.rb +200 -0
  39. data/lib/syncwrap/context.rb +260 -0
  40. data/lib/syncwrap/distro.rb +53 -60
  41. data/lib/syncwrap/formatter.rb +149 -0
  42. data/lib/syncwrap/host.rb +134 -0
  43. data/lib/syncwrap/main.rb +62 -0
  44. data/lib/syncwrap/path_util.rb +55 -0
  45. data/lib/syncwrap/rsync.rb +227 -0
  46. data/lib/syncwrap/ruby_support.rb +110 -0
  47. data/lib/syncwrap/shell.rb +207 -0
  48. data/lib/syncwrap.rb +367 -1
  49. data/{etc → sync/etc}/gemrc +1 -3
  50. data/sync/etc/hosts.erb +8 -0
  51. data/{etc/init.d/iyyov → sync/etc/init.d/iyyov.erb} +35 -7
  52. data/sync/etc/sysconfig/pgsql/postgresql.erb +2 -0
  53. data/sync/src/hashdot/Makefile.erb +98 -0
  54. data/sync/src/hashdot/profiles/default.hdp.erb +25 -0
  55. data/sync/src/hashdot/profiles/jruby-common.hdp +28 -0
  56. data/sync/src/hashdot/profiles/jruby-shortlived.hdp +9 -0
  57. data/sync/src/hashdot/profiles/jruby.hdp.erb +13 -0
  58. data/sync/src/hashdot/profiles/shortlived.hdp +6 -0
  59. data/sync/var/iyyov/default/config.rb +1 -0
  60. data/sync/var/iyyov/default/daemon.rb.erb +15 -0
  61. data/sync/var/iyyov/jobs.rb.erb +4 -0
  62. data/test/muddled_sync.rb +13 -0
  63. data/test/setup.rb +39 -0
  64. data/test/sync/d1/bar +1 -0
  65. data/test/sync/d1/foo.erb +1 -0
  66. data/test/sync/d3/d2/bar +1 -0
  67. data/test/sync/d3/d2/foo.erb +1 -0
  68. data/test/test_components.rb +108 -0
  69. data/test/test_context.rb +107 -0
  70. data/test/test_context_rput.rb +289 -0
  71. data/test/test_rsync.rb +138 -0
  72. data/test/test_shell.rb +233 -0
  73. data/test/test_space.rb +218 -0
  74. data/test/test_space_main.rb +40 -0
  75. data/test/zfile +1 -0
  76. metadata +204 -71
  77. data/etc/sysconfig/pgsql/postgresql +0 -2
  78. data/lib/syncwrap/aws.rb +0 -448
  79. data/lib/syncwrap/common.rb +0 -161
  80. data/lib/syncwrap/ec2.rb +0 -59
  81. data/lib/syncwrap/hashdot.rb +0 -70
  82. data/lib/syncwrap/iyyov.rb +0 -139
  83. data/lib/syncwrap/java.rb +0 -61
  84. data/lib/syncwrap/jruby.rb +0 -118
  85. data/lib/syncwrap/postgresql.rb +0 -135
  86. data/lib/syncwrap/qpid.rb +0 -251
  87. data/lib/syncwrap/remote_task.rb +0 -199
  88. data/lib/syncwrap/rhel.rb +0 -67
  89. data/lib/syncwrap/ubuntu.rb +0 -78
  90. data/lib/syncwrap/user_run.rb +0 -102
  91. data/test/test_syncwrap.rb +0 -202
  92. data/var/iyyov/jobs.rb +0 -11
  93. /data/{etc → sync/etc}/corosync/corosync.conf +0 -0
  94. /data/{etc → sync/etc}/corosync/uidgid.d/qpid +0 -0
  95. /data/{etc → sync/etc}/init.d/qpidd +0 -0
  96. /data/{etc → sync/etc}/sysctl.d/61-postgresql-shm.conf +0 -0
  97. /data/{usr/local → sync/jruby}/bin/jgem +0 -0
  98. /data/{postgresql → sync/postgresql}/rhel/pg_hba.conf +0 -0
  99. /data/{postgresql → sync/postgresql}/rhel/pg_ident.conf +0 -0
  100. /data/{postgresql → sync/postgresql}/rhel/postgresql.conf +0 -0
  101. /data/{postgresql → sync/postgresql}/ubuntu/environment +0 -0
  102. /data/{postgresql → sync/postgresql}/ubuntu/pg_ctl.conf +0 -0
  103. /data/{postgresql → sync/postgresql}/ubuntu/pg_hba.conf +0 -0
  104. /data/{postgresql → sync/postgresql}/ubuntu/pg_ident.conf +0 -0
  105. /data/{postgresql → sync/postgresql}/ubuntu/postgresql.conf +0 -0
  106. /data/{postgresql → sync/postgresql}/ubuntu/start.conf +0 -0
  107. /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-2013 David Kellum
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='1.5.2'
18
+ VERSION='2.0.0'
19
+
20
+ GEM_ROOT = File.dirname(File.dirname(File.dirname(__FILE__))) # :nodoc:
19
21
  end