syncwrap 1.5.2 → 2.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.
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