rouster 0.7 → 0.41
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.
- checksums.yaml +4 -4
- data/.gitignore +0 -3
- data/README.md +7 -241
- data/Rakefile +18 -55
- data/Vagrantfile +8 -26
- data/lib/rouster.rb +183 -404
- data/lib/rouster/deltas.rb +118 -577
- data/lib/rouster/puppet.rb +34 -209
- data/lib/rouster/testing.rb +59 -366
- data/lib/rouster/tests.rb +19 -70
- data/path_helper.rb +7 -5
- data/rouster.gemspec +1 -3
- data/test/basic.rb +1 -4
- data/test/functional/deltas/test_get_groups.rb +2 -74
- data/test/functional/deltas/test_get_packages.rb +4 -86
- data/test/functional/deltas/test_get_ports.rb +1 -26
- data/test/functional/deltas/test_get_services.rb +4 -43
- data/test/functional/deltas/test_get_users.rb +2 -35
- data/test/functional/puppet/test_facter.rb +1 -41
- data/test/functional/puppet/test_get_puppet_star.rb +68 -0
- data/test/functional/test_caching.rb +1 -5
- data/test/functional/test_dirs.rb +0 -25
- data/test/functional/test_get.rb +6 -10
- data/test/functional/test_inspect.rb +1 -1
- data/test/functional/test_is_file.rb +1 -17
- data/test/functional/test_new.rb +22 -233
- data/test/functional/test_put.rb +11 -9
- data/test/functional/test_restart.rb +4 -1
- data/test/functional/test_run.rb +3 -2
- data/test/puppet/test_apply.rb +11 -13
- data/test/puppet/test_roles.rb +173 -0
- data/test/unit/test_new.rb +0 -88
- data/test/unit/test_parse_ls_string.rb +0 -67
- data/test/unit/testing/test_validate_file.rb +47 -39
- data/test/unit/testing/test_validate_package.rb +10 -36
- metadata +6 -46
- data/.reek +0 -63
- data/.travis.yml +0 -11
- data/Gemfile +0 -17
- data/Gemfile.lock +0 -102
- data/LICENSE +0 -9
- data/examples/aws.rb +0 -85
- data/examples/openstack.rb +0 -61
- data/examples/passthrough.rb +0 -71
- data/lib/rouster/vagrant.rb +0 -311
- data/plugins/aws.rb +0 -347
- data/plugins/openstack.rb +0 -136
- data/test/functional/deltas/test_get_crontab.rb +0 -161
- data/test/functional/deltas/test_get_os.rb +0 -68
- data/test/functional/test_is_in_file.rb +0 -40
- data/test/functional/test_passthroughs.rb +0 -94
- data/test/functional/test_validate_file.rb +0 -131
- data/test/unit/puppet/resources/puppet_run_with_failed_exec +0 -59
- data/test/unit/puppet/resources/puppet_run_with_successful_exec +0 -61
- data/test/unit/puppet/test_get_puppet_star.rb +0 -91
- data/test/unit/puppet/test_puppet_parsing.rb +0 -44
- data/test/unit/testing/resources/osx-launchd +0 -285
- data/test/unit/testing/resources/rhel-systemd +0 -46
- data/test/unit/testing/resources/rhel-systemv +0 -41
- data/test/unit/testing/resources/rhel-upstart +0 -20
- data/test/unit/testing/test_get_services.rb +0 -178
- data/test/unit/testing/test_validate_cron.rb +0 -78
- data/test/unit/testing/test_validate_port.rb +0 -103
data/examples/openstack.rb
DELETED
@@ -1,61 +0,0 @@
|
|
1
|
-
require sprintf('%s/../%s', File.dirname(File.expand_path(__FILE__)), 'path_helper')
|
2
|
-
|
3
|
-
require 'rouster'
|
4
|
-
require 'plugins/openstack' # brings in fog and some helpers
|
5
|
-
|
6
|
-
ostack = Rouster.new(
|
7
|
-
:name => 'ostack-testing',
|
8
|
-
:sudo => false,
|
9
|
-
:logfile => true,
|
10
|
-
:passthrough => {
|
11
|
-
:type => :openstack, # Indicate OpenStack provider
|
12
|
-
:openstack_auth_url => 'http://hostname.acme.com:5000/v2.0/tokens', # OpenStack API endpoint
|
13
|
-
:openstack_username => 'some_user', # OpenStack console username
|
14
|
-
:openstack_tenant => 'tenant_id', # Tenant ID
|
15
|
-
:user => 'ssh_user_id', # SSH login ID
|
16
|
-
:keypair => 'openstack_key_name', # Name of ssh keypair in OpenStack
|
17
|
-
:image_ref => 'c0340afb-577d-1234-87b2-aebdd6d1838f', # Image ID in OpenStack
|
18
|
-
:flavor_ref => '547d9af5-096c-1234-98df-7d23162556b8', # Flavor ID in OpenStack
|
19
|
-
:openstack_api_key => 'secret_openstack_key', # OpenStack console password
|
20
|
-
:key => '/path/to/ssh_keys.pem', # SSH key filename
|
21
|
-
},
|
22
|
-
:sshtunnel => false,
|
23
|
-
:verbosity => 1,
|
24
|
-
)
|
25
|
-
|
26
|
-
p "UP(): #{ostack.up}"
|
27
|
-
p "STATUS(): #{ostack.status}"
|
28
|
-
|
29
|
-
ostack_copy = Rouster.new(
|
30
|
-
:name => 'ostack-copy',
|
31
|
-
:sudo => false,
|
32
|
-
:logfile => true,
|
33
|
-
:passthrough => {
|
34
|
-
:type => :openstack, # Indicate OpenStack provider
|
35
|
-
:openstack_auth_url => 'http://hostname.acme.com:5000/v2.0/tokens', # OpenStack API endpoint
|
36
|
-
:openstack_username => 'some_user', # OpenStack console username
|
37
|
-
:openstack_tenant => 'tenant_id', # Tenant ID
|
38
|
-
:user => 'ssh_user_id', # SSH login ID
|
39
|
-
:keypair => 'openstack_key_name', # Name of ssh keypair in OpenStack
|
40
|
-
:openstack_api_key => 'secret_openstack_key', # OpenStack console password
|
41
|
-
:instance => ostack.ostack_get_instance_id, # ID of a running OpenStack instance.
|
42
|
-
},
|
43
|
-
:sshtunnel => false,
|
44
|
-
:verbosity => 1,
|
45
|
-
)
|
46
|
-
|
47
|
-
[ ostack, ostack_copy ].each do |o|
|
48
|
-
p "ostack_get_instance_id: #{o.ostack_get_instance_id}"
|
49
|
-
|
50
|
-
p "status: #{o.status}"
|
51
|
-
|
52
|
-
p "ostack_get_ip(): #{o.ostack_get_ip()}"
|
53
|
-
|
54
|
-
p "run(uptime): #{o.run('uptime')}"
|
55
|
-
p "get(/etc/hosts): #{o.get('/etc/hosts')}"
|
56
|
-
p "put(/etc/hosts, /tmp): #{o.put('/etc/hosts', '/tmp')}"
|
57
|
-
|
58
|
-
end
|
59
|
-
|
60
|
-
p "DESTROY(): #{ostack.destroy}"
|
61
|
-
exit
|
data/examples/passthrough.rb
DELETED
@@ -1,71 +0,0 @@
|
|
1
|
-
require sprintf('%s/../%s', File.dirname(File.expand_path(__FILE__)), 'path_helper')
|
2
|
-
|
3
|
-
require 'rouster'
|
4
|
-
require 'rouster/puppet'
|
5
|
-
require 'rouster/tests'
|
6
|
-
|
7
|
-
verbosity = ENV['VERBOSE'].nil? ? 4 : 0
|
8
|
-
|
9
|
-
# .inspect of this is blank for sshkey and status, looks ugly, but is ~accurate.. fix this?
|
10
|
-
local = Rouster.new(
|
11
|
-
:name => 'local',
|
12
|
-
:sudo => false,
|
13
|
-
:passthrough => { :type => :local },
|
14
|
-
:verbosity => verbosity,
|
15
|
-
)
|
16
|
-
|
17
|
-
remote = Rouster.new(
|
18
|
-
:name => 'remote',
|
19
|
-
:sudo => false,
|
20
|
-
:passthrough => {
|
21
|
-
:type => :remote,
|
22
|
-
:host => `hostname`.chomp, # yep, the remote is actually local.. perhaps the right :type would be 'ssh' vs 'shellout' instead..
|
23
|
-
:user => ENV['USER'],
|
24
|
-
:key => sprintf('%s/.ssh/id_dsa', ENV['HOME']),
|
25
|
-
},
|
26
|
-
:verbosity => verbosity,
|
27
|
-
)
|
28
|
-
|
29
|
-
sudo = Rouster.new(
|
30
|
-
:name => 'sudo',
|
31
|
-
:sudo => true,
|
32
|
-
:passthrough => { :type => :local },
|
33
|
-
:verbosity => verbosity,
|
34
|
-
)
|
35
|
-
|
36
|
-
vagrant = Rouster.new(
|
37
|
-
:name => 'ppm',
|
38
|
-
:sudo => true,
|
39
|
-
:verbosity => verbosity,
|
40
|
-
)
|
41
|
-
|
42
|
-
workers = [ local, remote, vagrant ]
|
43
|
-
|
44
|
-
workers = [vagrant]
|
45
|
-
|
46
|
-
workers.each do |r|
|
47
|
-
p r
|
48
|
-
|
49
|
-
## vagrant command testing
|
50
|
-
r.up()
|
51
|
-
r.suspend()
|
52
|
-
#r.destroy()
|
53
|
-
r.up()
|
54
|
-
|
55
|
-
p r.status() # why is this giving us nil after initial call? want to blame caching, but not sure
|
56
|
-
|
57
|
-
r.is_vagrant_running?()
|
58
|
-
r.sandbox_available?()
|
59
|
-
|
60
|
-
if r.sandbox_available?()
|
61
|
-
r.sandbox_on()
|
62
|
-
r.sandbox_off()
|
63
|
-
r.sandbox_rollback()
|
64
|
-
r.sandbox_commit()
|
65
|
-
end
|
66
|
-
|
67
|
-
p r.run('echo foo')
|
68
|
-
|
69
|
-
end
|
70
|
-
|
71
|
-
exit
|
data/lib/rouster/vagrant.rb
DELETED
@@ -1,311 +0,0 @@
|
|
1
|
-
require sprintf('%s/../../%s', File.dirname(File.expand_path(__FILE__)), 'path_helper')
|
2
|
-
|
3
|
-
## Vagrant specific (and related) methods
|
4
|
-
|
5
|
-
class Rouster
|
6
|
-
|
7
|
-
##
|
8
|
-
# vagrant
|
9
|
-
#
|
10
|
-
# abstraction layer to call vagrant faces
|
11
|
-
#
|
12
|
-
# parameters
|
13
|
-
# * <face> - vagrant face to call (include arguments)
|
14
|
-
def vagrant(face, sleep_time=10)
|
15
|
-
if self.is_passthrough?
|
16
|
-
@logger.warn(sprintf('calling [vagrant %s] on a passthrough host is a noop', face))
|
17
|
-
return nil
|
18
|
-
end
|
19
|
-
|
20
|
-
unless @vagrant_concurrency.eql?(true)
|
21
|
-
# TODO don't (ab|re)use variables
|
22
|
-
0.upto(@retries) do |try|
|
23
|
-
break if self.is_vagrant_running?().eql?(false)
|
24
|
-
|
25
|
-
sleep sleep_time # TODO log a message?
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
0.upto(@retries) do |try| # TODO should really be doing this with 'retry', but i think this code is actually cleaner
|
30
|
-
begin
|
31
|
-
return self._run(sprintf('cd %s; vagrant %s', File.dirname(@vagrantfile), face))
|
32
|
-
rescue
|
33
|
-
@logger.error(sprintf('failed vagrant command[%s], attempt[%s/%s]', face, try, retries)) if self.retries > 0
|
34
|
-
sleep sleep_time
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
raise InternalError.new(sprintf('failed to execute [%s], exitcode[%s], output[%s]', face, self.exitcode, self.get_output()))
|
39
|
-
|
40
|
-
|
41
|
-
end
|
42
|
-
|
43
|
-
##
|
44
|
-
# up
|
45
|
-
# runs `vagrant up <name>` from the Vagrantfile path
|
46
|
-
# if :sshtunnel is passed to the object during instantiation, the tunnel is created here as well
|
47
|
-
def up
|
48
|
-
@logger.info('up()')
|
49
|
-
|
50
|
-
# don't like putting this here, may be refactored
|
51
|
-
if self.is_passthrough?
|
52
|
-
if (self.passthrough[:type].equal?(:aws) or self.passthrough[:type].equal?(:raiden))
|
53
|
-
self.aws_up()
|
54
|
-
elsif (self.passthrough[:type].equal?(:openstack))
|
55
|
-
self.ostack_up()
|
56
|
-
else
|
57
|
-
self.vagrant(sprintf('up %s', @name))
|
58
|
-
end
|
59
|
-
else
|
60
|
-
self.vagrant(sprintf('up %s', @name))
|
61
|
-
end
|
62
|
-
|
63
|
-
@ssh_info = nil # in case the ssh-info has changed, a la destroy/rebuild
|
64
|
-
self.connect_ssh_tunnel() if @sshtunnel
|
65
|
-
end
|
66
|
-
|
67
|
-
##
|
68
|
-
# halt
|
69
|
-
# runs `vagrant halt <name>` from the Vagrantfile path
|
70
|
-
def halt
|
71
|
-
@logger.info('halt()')
|
72
|
-
self.vagrant(sprintf('halt %s', @name))
|
73
|
-
end
|
74
|
-
|
75
|
-
##
|
76
|
-
# package -- though vagrant docs still refer to 'repackage'
|
77
|
-
# runs `vagrant package <name> <provider>`
|
78
|
-
def package(provider='virtualbox') # TODO get the provider as a first class citizen on the rouster object
|
79
|
-
@logger.info(sprintf('package(%s)', provider))
|
80
|
-
self.vagrant(sprintf('package %s %s', @name, provider))
|
81
|
-
end
|
82
|
-
|
83
|
-
##
|
84
|
-
# destroy
|
85
|
-
# runs `vagrant destroy <name>` from the Vagrantfile path
|
86
|
-
def destroy
|
87
|
-
@logger.info('destroy()')
|
88
|
-
|
89
|
-
# don't like putting this here, may be refactored
|
90
|
-
if self.is_passthrough?
|
91
|
-
if (self.passthrough[:type].equal?(:aws) or self.passthrough[:type].equal?(:raiden))
|
92
|
-
self.aws_destroy()
|
93
|
-
elsif self.is_passthrough? and self.passthrough[:type].equal?(:openstack)
|
94
|
-
self.ostack_destroy()
|
95
|
-
else
|
96
|
-
raise InternalError.new(sprintf('failed to execute destroy(), unsupported passthrough type %s', self.passthrough[:type]))
|
97
|
-
end
|
98
|
-
else
|
99
|
-
self.vagrant(sprintf('destroy -f %s', @name))
|
100
|
-
end
|
101
|
-
|
102
|
-
disconnect_ssh_tunnel
|
103
|
-
end
|
104
|
-
|
105
|
-
##
|
106
|
-
# status
|
107
|
-
#
|
108
|
-
# runs `vagrant status <name>` from the Vagrantfile path
|
109
|
-
# parses the status and provider out of output, but only status is returned
|
110
|
-
def status
|
111
|
-
status = nil
|
112
|
-
|
113
|
-
if @cache_timeout
|
114
|
-
if @cache.has_key?(:status)
|
115
|
-
if (Time.now.to_i - @cache[:status][:time]) < @cache_timeout
|
116
|
-
@logger.debug(sprintf('using cached status[%s] from [%s]', @cache[:status][:status], @cache[:status][:time]))
|
117
|
-
return @cache[:status][:status]
|
118
|
-
end
|
119
|
-
end
|
120
|
-
end
|
121
|
-
|
122
|
-
# don't like putting this here, may be refactored
|
123
|
-
@logger.info('status()')
|
124
|
-
if self.is_passthrough?
|
125
|
-
if (self.passthrough[:type].equal?(:aws) or self.passthrough[:type].equal?(:raiden))
|
126
|
-
status = self.aws_status()
|
127
|
-
elsif self.passthrough[:type].equal?(:openstack)
|
128
|
-
status = self.ostack_status()
|
129
|
-
else
|
130
|
-
raise InternalError.new(sprintf('failed to execute status(), unsupported passthrough type %s', self.passthrough[:type]))
|
131
|
-
end
|
132
|
-
else
|
133
|
-
self.vagrant(sprintf('status %s', @name))
|
134
|
-
|
135
|
-
# else case here (both for nil/non-matching output) is handled by non-0 exit code
|
136
|
-
output = self.get_output()
|
137
|
-
if output.nil?
|
138
|
-
if self.is_passthrough?() and self.passthrough[:type].eql?(:local)
|
139
|
-
status = 'running'
|
140
|
-
else
|
141
|
-
status = 'not-created'
|
142
|
-
end
|
143
|
-
elsif output.match(/^#{@name}\s*(.*\s?\w+)\s\((.+)\)$/)
|
144
|
-
# vagrant 1.2+, $1 = status, $2 = provider
|
145
|
-
status = $1
|
146
|
-
elsif output.match(/^#{@name}\s+(.+)$/)
|
147
|
-
# vagrant 1.2-, $1 = status
|
148
|
-
status = $1
|
149
|
-
end
|
150
|
-
end
|
151
|
-
|
152
|
-
if @cache_timeout
|
153
|
-
@cache[:status] = Hash.new unless @cache[:status].class.eql?(Hash)
|
154
|
-
@cache[:status][:time] = Time.now.to_i
|
155
|
-
@cache[:status][:status] = status
|
156
|
-
@logger.debug(sprintf('caching status[%s] at [%s]', @cache[:status][:status], @cache[:status][:time]))
|
157
|
-
end
|
158
|
-
|
159
|
-
return status
|
160
|
-
end
|
161
|
-
|
162
|
-
##
|
163
|
-
# reload
|
164
|
-
#
|
165
|
-
# runs `vagrant reload <name> [--no-provision]` from the Vagrantfile path
|
166
|
-
# +no_provision+ Boolean whether or not to stop reprovisioning
|
167
|
-
def reload(no_provision = true)
|
168
|
-
|
169
|
-
if self.is_passthrough?
|
170
|
-
@logger.warn('calling [vagrant reload] on a passthrough host is a noop')
|
171
|
-
return nil
|
172
|
-
end
|
173
|
-
|
174
|
-
@logger.info('reload()')
|
175
|
-
self.vagrant(sprintf('reload %s %s', @name, no_provision ? '--no-provision' : ''))
|
176
|
-
end
|
177
|
-
|
178
|
-
##
|
179
|
-
# suspend
|
180
|
-
#
|
181
|
-
# runs `vagrant suspend <name>` from the Vagrantfile path
|
182
|
-
def suspend
|
183
|
-
@logger.info('suspend()')
|
184
|
-
self.vagrant(sprintf('suspend %s', @name))
|
185
|
-
disconnect_ssh_tunnel() unless self.is_passthrough?()
|
186
|
-
end
|
187
|
-
|
188
|
-
##
|
189
|
-
# is_vagrant_running?()
|
190
|
-
#
|
191
|
-
# returns true|false if a vagrant process is running on the host machine
|
192
|
-
#
|
193
|
-
# meant to be used to prevent race-y conditions when interacting with VirtualBox (potentially others, haven't tested)
|
194
|
-
def is_vagrant_running?
|
195
|
-
res = false
|
196
|
-
|
197
|
-
begin
|
198
|
-
# TODO would like to get the 2 -v greps into a single call..
|
199
|
-
raw = self._run("ps -ef | grep -v 'grep' | grep -v 'ssh' | grep '#{self.vagrantbinary}'")
|
200
|
-
res = true
|
201
|
-
rescue
|
202
|
-
end
|
203
|
-
|
204
|
-
@logger.debug(sprintf('is_vagrant_running?[%s]', res))
|
205
|
-
res
|
206
|
-
end
|
207
|
-
|
208
|
-
##
|
209
|
-
# sandbox_available?
|
210
|
-
#
|
211
|
-
# returns true or false after attempting to find out if the sandbox
|
212
|
-
# subcommand is available
|
213
|
-
def sandbox_available?
|
214
|
-
if self.is_passthrough?
|
215
|
-
@logger.warn('sandbox* methods on a passthrough host is a noop')
|
216
|
-
return nil
|
217
|
-
end
|
218
|
-
|
219
|
-
if @cache.has_key?(:sandbox_available?)
|
220
|
-
@logger.debug(sprintf('using cached sandbox_available?[%s]', @cache[:sandbox_available?]))
|
221
|
-
return @cache[:sandbox_available?]
|
222
|
-
end
|
223
|
-
|
224
|
-
@logger.info('sandbox_available()')
|
225
|
-
begin
|
226
|
-
# at some point, vagrant changed its behavior on exit code here, so rescuing
|
227
|
-
self._run(sprintf('cd %s; vagrant', File.dirname(@vagrantfile))) # calling 'vagrant' without parameters to determine available faces
|
228
|
-
rescue
|
229
|
-
end
|
230
|
-
|
231
|
-
sandbox_available = false
|
232
|
-
if self.get_output().match(/^\s+sandbox$/)
|
233
|
-
sandbox_available = true
|
234
|
-
end
|
235
|
-
|
236
|
-
@cache[:sandbox_available?] = sandbox_available
|
237
|
-
@logger.debug(sprintf('caching sandbox_available?[%s]', @cache[:sandbox_available?]))
|
238
|
-
@logger.error('sandbox support is not available, please install the "sahara" gem first, https://github.com/jedi4ever/sahara') unless sandbox_available
|
239
|
-
|
240
|
-
return sandbox_available
|
241
|
-
end
|
242
|
-
|
243
|
-
##
|
244
|
-
# sandbox_on
|
245
|
-
# runs `vagrant sandbox on` from the Vagrantfile path
|
246
|
-
def sandbox_on
|
247
|
-
if self.is_passthrough?
|
248
|
-
@logger.warn('sandbox* methods on a passthrough host is a noop')
|
249
|
-
return nil
|
250
|
-
end
|
251
|
-
|
252
|
-
if self.sandbox_available?
|
253
|
-
return self.vagrant(sprintf('sandbox on %s', @name))
|
254
|
-
else
|
255
|
-
raise ExternalError.new('sandbox plugin not installed')
|
256
|
-
end
|
257
|
-
end
|
258
|
-
|
259
|
-
##
|
260
|
-
# sandbox_off
|
261
|
-
# runs `vagrant sandbox off` from the Vagrantfile path
|
262
|
-
def sandbox_off
|
263
|
-
if self.is_passthrough?
|
264
|
-
@logger.warn('sandbox* methods on a passthrough host is a noop')
|
265
|
-
return nil
|
266
|
-
end
|
267
|
-
|
268
|
-
if self.sandbox_available?
|
269
|
-
return self.vagrant(sprintf('sandbox off %s', @name))
|
270
|
-
else
|
271
|
-
raise ExternalError.new('sandbox plugin not installed')
|
272
|
-
end
|
273
|
-
end
|
274
|
-
|
275
|
-
##
|
276
|
-
# sandbox_rollback
|
277
|
-
# runs `vagrant sandbox rollback` from the Vagrantfile path
|
278
|
-
def sandbox_rollback
|
279
|
-
if self.is_passthrough?
|
280
|
-
@logger.warn('sandbox* methods on a passthrough host is a noop')
|
281
|
-
return nil
|
282
|
-
end
|
283
|
-
|
284
|
-
if self.sandbox_available?
|
285
|
-
self.disconnect_ssh_tunnel
|
286
|
-
self.vagrant(sprintf('sandbox rollback %s', @name))
|
287
|
-
self.connect_ssh_tunnel
|
288
|
-
else
|
289
|
-
raise ExternalError.new('sandbox plugin not installed')
|
290
|
-
end
|
291
|
-
end
|
292
|
-
|
293
|
-
##
|
294
|
-
# sandbox_commit
|
295
|
-
# runs `vagrant sandbox commit` from the Vagrantfile path
|
296
|
-
def sandbox_commit
|
297
|
-
if self.is_passthrough?
|
298
|
-
@logger.warn('sandbox* methods on a passthrough host is a noop')
|
299
|
-
return nil
|
300
|
-
end
|
301
|
-
|
302
|
-
if self.sandbox_available?
|
303
|
-
self.disconnect_ssh_tunnel
|
304
|
-
self.vagrant(sprintf('sandbox commit %s', @name))
|
305
|
-
self.connect_ssh_tunnel
|
306
|
-
else
|
307
|
-
raise ExternalError.new('sandbox plugin not installed')
|
308
|
-
end
|
309
|
-
end
|
310
|
-
|
311
|
-
end
|
data/plugins/aws.rb
DELETED
@@ -1,347 +0,0 @@
|
|
1
|
-
#!/usr/bin/ruby
|
2
|
-
## plugins/aws.rb - provide helper functions for Rouster objects running on AWS/EC2
|
3
|
-
|
4
|
-
require sprintf('%s/../%s', File.dirname(File.expand_path(__FILE__)), 'path_helper')
|
5
|
-
|
6
|
-
require 'fog'
|
7
|
-
require 'uri'
|
8
|
-
|
9
|
-
class Rouster
|
10
|
-
|
11
|
-
attr_reader :ec2, :elb # expose AWS workers
|
12
|
-
attr_reader :instance_data # the result of the runInstances request
|
13
|
-
|
14
|
-
def aws_get_url(url)
|
15
|
-
# convenience method to run curls from inside the VM
|
16
|
-
self.run(sprintf('curl -s %s', url))
|
17
|
-
end
|
18
|
-
|
19
|
-
# TODO should this be 'aws_ip'?
|
20
|
-
def aws_get_ip (method = :internal, type = :public)
|
21
|
-
# allowed methods: :internal (check meta-data inside VM), :aws (ask API)
|
22
|
-
# allowed types: :public, :private
|
23
|
-
self.aws_describe_instance
|
24
|
-
|
25
|
-
if method.equal?(:internal)
|
26
|
-
key = type.equal?(:public) ? 'public-ipv4' : 'local-ipv4'
|
27
|
-
murl = sprintf('http://169.254.169.254/latest/meta-data/%s', key)
|
28
|
-
result = self.aws_get_url(murl)
|
29
|
-
else
|
30
|
-
key = type.equal?(:public) ? 'ipAddress' : 'privateIpAddress'
|
31
|
-
result = @instance_data[key]
|
32
|
-
end
|
33
|
-
|
34
|
-
result
|
35
|
-
end
|
36
|
-
|
37
|
-
def aws_get_userdata
|
38
|
-
murl = 'http://169.254.169.254/latest/user-data/'
|
39
|
-
result = self.aws_get_url(murl)
|
40
|
-
|
41
|
-
if result.match(/\S=\S/)
|
42
|
-
# TODO should we really be doing this?
|
43
|
-
userdata = Hash.new()
|
44
|
-
result.split("\n").each do |line|
|
45
|
-
if line.match(/^(.*?)=(.*)/)
|
46
|
-
userdata[$1] = $2
|
47
|
-
end
|
48
|
-
end
|
49
|
-
else
|
50
|
-
userdata = result
|
51
|
-
end
|
52
|
-
|
53
|
-
userdata
|
54
|
-
end
|
55
|
-
|
56
|
-
# return a hash containing meta-data items
|
57
|
-
def aws_get_metadata
|
58
|
-
murl = 'http://169.254.169.254/latest/meta-data/'
|
59
|
-
result = self.aws_get_url(murl)
|
60
|
-
metadata = Hash.new()
|
61
|
-
|
62
|
-
# TODO this isn't entirely right.. if the element ends in '/', it's actually another level of hash..
|
63
|
-
result.split("\n").each do |element|
|
64
|
-
metadata[element] = self.aws_get_url(sprintf('%s%s', murl, element))
|
65
|
-
end
|
66
|
-
|
67
|
-
metadata
|
68
|
-
end
|
69
|
-
|
70
|
-
def aws_get_hostname (method = :internal, type = :public)
|
71
|
-
# allowed methods: :internal (check meta-data inside VM), :aws (ask API)
|
72
|
-
# allowed types: :public, :private
|
73
|
-
self.aws_describe_instance
|
74
|
-
|
75
|
-
result = nil
|
76
|
-
|
77
|
-
if method.equal?(:internal)
|
78
|
-
key = type.equal?(:public) ? 'public-hostname' : 'local-hostname'
|
79
|
-
murl = sprintf('http://169.254.169.254/latest/meta-data/%s', key)
|
80
|
-
result = self.aws_get_url(murl)
|
81
|
-
else
|
82
|
-
key = type.equal?(:public) ? 'dnsName' : 'privateDnsName'
|
83
|
-
result = @instance_data[key]
|
84
|
-
end
|
85
|
-
|
86
|
-
result
|
87
|
-
end
|
88
|
-
|
89
|
-
def aws_get_instance ()
|
90
|
-
if ! @instance_data.nil? and @instance_data.has_key?('instanceId')
|
91
|
-
return @instance_data['instanceId'] # we already know the id
|
92
|
-
elsif @passthrough.has_key?(:instance)
|
93
|
-
return @passthrough[:instance] # we know the id we want
|
94
|
-
else
|
95
|
-
@logger.debug(sprintf('unable to determine ami-id from instance_data[%s] or passthrough specification[%s]', @instance_data, @passthrough))
|
96
|
-
return nil # we don't have an id yet, likely a up() call
|
97
|
-
end
|
98
|
-
end
|
99
|
-
|
100
|
-
def aws_get_ami ()
|
101
|
-
if ! @instance_data.nil? and @instance_data.has_key?('ami')
|
102
|
-
return @instance_data['ami']
|
103
|
-
else
|
104
|
-
return @passthrough[:ami]
|
105
|
-
end
|
106
|
-
end
|
107
|
-
|
108
|
-
def aws_up
|
109
|
-
# wait for machine to transition to running state and become sshable (TODO maybe make the second half optional)
|
110
|
-
self.aws_connect
|
111
|
-
|
112
|
-
status = self.status()
|
113
|
-
|
114
|
-
if status.eql?('running')
|
115
|
-
self.connect_ssh_tunnel
|
116
|
-
self.passthrough[:instance] = self.aws_get_instance
|
117
|
-
return self.aws_get_instance
|
118
|
-
end
|
119
|
-
|
120
|
-
# TODO provide more context
|
121
|
-
@logger.info(sprintf('calling RunInstances ami[%s], size[%s], keypair[%s]',
|
122
|
-
self.passthrough[:ami],
|
123
|
-
self.passthrough[:size],
|
124
|
-
self.passthrough[:keypair]
|
125
|
-
))
|
126
|
-
|
127
|
-
server = @ec2.run_instances(
|
128
|
-
self.passthrough[:ami],
|
129
|
-
self.passthrough[:min_count],
|
130
|
-
self.passthrough[:max_count],
|
131
|
-
{
|
132
|
-
'InstanceType' => self.passthrough[:size],
|
133
|
-
'KeyName' => self.passthrough[:keypair],
|
134
|
-
'SecurityGroup' => self.passthrough[:security_groups],
|
135
|
-
'UserData' => self.passthrough[:userdata],
|
136
|
-
},
|
137
|
-
)
|
138
|
-
|
139
|
-
@instance_data = server.data[:body]['instancesSet'][0]
|
140
|
-
|
141
|
-
# wait until the machine starts
|
142
|
-
ceiling = 9
|
143
|
-
sleep_time = 20
|
144
|
-
status = nil
|
145
|
-
0.upto(ceiling) do |try|
|
146
|
-
status = self.aws_status
|
147
|
-
|
148
|
-
@logger.debug(sprintf('describeInstances[%s]: [%s] [#%s]', self.aws_get_instance, status, try))
|
149
|
-
|
150
|
-
if status.eql?('running') or status.eql?('16')
|
151
|
-
@logger.info(sprintf('[%s] transitioned to state[%s]', self.aws_get_instance, self.aws_status))
|
152
|
-
break
|
153
|
-
end
|
154
|
-
|
155
|
-
sleep sleep_time
|
156
|
-
end
|
157
|
-
|
158
|
-
raise sprintf('instance[%s] did not transition to running state, stopped trying at[%s]', self.aws_get_instance, status) unless status.eql?('running') or status.eql?('16')
|
159
|
-
|
160
|
-
# TODO don't be this hacky
|
161
|
-
self.aws_describe_instance # the server.data response doesn't include public hostname/ip
|
162
|
-
if @passthrough[:type].eql?(:aws)
|
163
|
-
@passthrough[:host] = @instance_data['dnsName']
|
164
|
-
else
|
165
|
-
@passthrough[:host] = self.find_ssh_elb(true)
|
166
|
-
end
|
167
|
-
|
168
|
-
self.connect_ssh_tunnel
|
169
|
-
|
170
|
-
self.passthrough[:instance] = self.aws_get_instance
|
171
|
-
self.passthrough[:instance]
|
172
|
-
end
|
173
|
-
|
174
|
-
def aws_destroy
|
175
|
-
self.aws_connect
|
176
|
-
|
177
|
-
server = @ec2.terminate_instances(self.aws_get_instance)
|
178
|
-
|
179
|
-
if self.passthrough[:created_elb] && self.passthrough[:elb_cleanup]
|
180
|
-
@logger.info(sprintf('deleting ELB[%s]', self.passthrough[:created_elb]))
|
181
|
-
@elb.delete_load_balancer(self.passthrough[:created_elb])
|
182
|
-
end
|
183
|
-
|
184
|
-
self.aws_status
|
185
|
-
end
|
186
|
-
|
187
|
-
def aws_describe_instance(instance = aws_get_instance)
|
188
|
-
|
189
|
-
if @cache_timeout
|
190
|
-
if @cache.has_key?(:aws_describe_instance)
|
191
|
-
if (Time.now.to_i - @cache[:aws_describe_instance][:time]) < @cache_timeout
|
192
|
-
@logger.debug(sprintf('using cached aws_describe_instance?[%s] from [%s]', @cache[:aws_describe_instance][:instance], @cache[:aws_describe_instance][:time]))
|
193
|
-
return @cache[:aws_describe_instance][:instance]
|
194
|
-
end
|
195
|
-
end
|
196
|
-
end
|
197
|
-
|
198
|
-
return nil if instance.nil?
|
199
|
-
|
200
|
-
self.aws_connect
|
201
|
-
server = @ec2.describe_instances('instance-id' => [ instance ])
|
202
|
-
response = server.data[:body]['reservationSet'][0]['instancesSet'][0]
|
203
|
-
|
204
|
-
if instance.eql?(self.aws_get_instance)
|
205
|
-
@instance_data = response
|
206
|
-
end
|
207
|
-
|
208
|
-
if @cache_timeout
|
209
|
-
@cache[:aws_describe_instance] = Hash.new unless @cache[:aws_describe_instance].class.eql?(Hash)
|
210
|
-
@cache[:aws_describe_instance][:time] = Time.now.to_i
|
211
|
-
@cache[:aws_describe_instance][:instance] = response
|
212
|
-
@logger.debug(sprintf('caching is_available_via_ssh?[%s] at [%s]', @cache[:aws_describe_instance][:instance], @cache[:aws_decribe_instance][:time]))
|
213
|
-
end
|
214
|
-
|
215
|
-
response
|
216
|
-
end
|
217
|
-
|
218
|
-
def aws_status
|
219
|
-
self.aws_describe_instance
|
220
|
-
return 'not-created' if @instance_data.nil?
|
221
|
-
@instance_data['instanceState']['name'].nil? ? @instance_data['instanceState']['code'] : @instance_data['instanceState']['name']
|
222
|
-
end
|
223
|
-
|
224
|
-
def aws_connect_to_elb (id, elbname, listeners = [{ 'InstancePort' => 22, 'LoadBalancerPort' => 22, 'Protocol' => 'TCP' }])
|
225
|
-
self.elb_connect
|
226
|
-
|
227
|
-
# allow either hash or array of hash specification for listeners
|
228
|
-
listeners = [ listeners ] unless listeners.is_a?(Array)
|
229
|
-
required_params = [ 'InstancePort', 'LoadBalancerPort', 'Protocol' ] # figure out plan re: InstanceProtocol/LoadbalancerProtocol vs. Protocol
|
230
|
-
|
231
|
-
listeners.each do |l|
|
232
|
-
required_params.each do |r|
|
233
|
-
raise sprintf('listener[%s] does not include required parameter[%s]', l, r) unless l[r]
|
234
|
-
end
|
235
|
-
|
236
|
-
end
|
237
|
-
|
238
|
-
@logger.debug(sprintf('confirming ELB name uniqueness[%s]', elbname))
|
239
|
-
response = @elb.describe_load_balancers()
|
240
|
-
response.body['DescribeLoadBalancersResult']['LoadBalancerDescriptions'].each do |elb|
|
241
|
-
if elb['LoadBalancerName'].eql?(elbname)
|
242
|
-
# terminate
|
243
|
-
@logger.debug(sprintf('terminating ELB[%s]', elbname))
|
244
|
-
@elb.delete_load_balancer(elbname)
|
245
|
-
end
|
246
|
-
end
|
247
|
-
|
248
|
-
# create the ELB/VIP
|
249
|
-
@logger.debug(sprintf('creating a load balancer[%s] with listeners[%s]', elbname, listeners))
|
250
|
-
response = @elb.create_load_balancer(
|
251
|
-
[], # availability zones not needed on raiden
|
252
|
-
elbname,
|
253
|
-
listeners
|
254
|
-
)
|
255
|
-
|
256
|
-
dnsname = response.body['CreateLoadBalancerResult']['DNSName']
|
257
|
-
|
258
|
-
# string it up to the id passed
|
259
|
-
response = @elb.register_instances_with_load_balancer(id, elbname)
|
260
|
-
|
261
|
-
# i hate this so much.
|
262
|
-
@logger.debug(sprintf('sleeping[%s] to allow DNS propagation', self.passthrough[:dns_propagation_sleep]))
|
263
|
-
sleep self.passthrough[:dns_propagation_sleep]
|
264
|
-
|
265
|
-
self.passthrough[:created_elb] = elbname
|
266
|
-
|
267
|
-
return dnsname
|
268
|
-
end
|
269
|
-
|
270
|
-
# TODO this will throw at the first error - should we catch?
|
271
|
-
# run some commands, return an array of the output
|
272
|
-
def aws_bootstrap (commands)
|
273
|
-
self.aws_connect
|
274
|
-
commands = (commands.is_a?(Array)) ? commands : [ commands ]
|
275
|
-
output = Array.new
|
276
|
-
|
277
|
-
commands.each do |command|
|
278
|
-
output << self.run(command)
|
279
|
-
end
|
280
|
-
|
281
|
-
return output
|
282
|
-
end
|
283
|
-
|
284
|
-
def aws_connect
|
285
|
-
return @ec2 unless @ec2.nil?
|
286
|
-
|
287
|
-
config = {
|
288
|
-
:provider => 'AWS',
|
289
|
-
:region => self.passthrough[:region],
|
290
|
-
:aws_access_key_id => self.passthrough[:key_id],
|
291
|
-
:aws_secret_access_key => self.passthrough[:secret_key],
|
292
|
-
}
|
293
|
-
|
294
|
-
config[:endpoint] = self.passthrough[:ec2_endpoint] unless self.passthrough[:ec2_endpoint].nil?
|
295
|
-
@ec2 = Fog::Compute.new(config)
|
296
|
-
end
|
297
|
-
|
298
|
-
def elb_connect
|
299
|
-
return @elb unless @elb.nil?
|
300
|
-
|
301
|
-
if self.passthrough[:elb_endpoint]
|
302
|
-
endpoint = URI.parse(self.passthrough[:elb_endpoint])
|
303
|
-
elsif self.passthrough[:ec2_endpoint]
|
304
|
-
endpoint = URI.parse(self.passthrough[:ec2_endpoint])
|
305
|
-
end
|
306
|
-
|
307
|
-
config = {
|
308
|
-
:region => self.passthrough[:region],
|
309
|
-
:aws_access_key_id => self.passthrough[:key_id],
|
310
|
-
:aws_secret_access_key => self.passthrough[:secret_key],
|
311
|
-
}
|
312
|
-
|
313
|
-
unless endpoint.nil?
|
314
|
-
# if not specifying an endpoint, don't add to the config
|
315
|
-
config[:host] = endpoint.host
|
316
|
-
config[:path] = endpoint.path
|
317
|
-
config[:port] = endpoint.port
|
318
|
-
config[:scheme] = endpoint.scheme
|
319
|
-
end
|
320
|
-
|
321
|
-
@elb = Fog::AWS::ELB.new(config)
|
322
|
-
end
|
323
|
-
|
324
|
-
def find_ssh_elb (create_if_not_found = false, instance = aws_get_instance)
|
325
|
-
# given an instance, see if there is already an ELB that it is connected to - and potentially create one
|
326
|
-
self.elb_connect
|
327
|
-
result = nil
|
328
|
-
|
329
|
-
response = @elb.describe_load_balancers
|
330
|
-
elbs = response.body['DescribeLoadBalancersResult']['LoadBalancerDescriptions']
|
331
|
-
|
332
|
-
elbs.each do |elb|
|
333
|
-
if elb['Instances'].member?(instance)
|
334
|
-
result = elb['DNSName']
|
335
|
-
break
|
336
|
-
end
|
337
|
-
end
|
338
|
-
|
339
|
-
if create_if_not_found and result.nil?
|
340
|
-
result = self.aws_connect_to_elb(instance, sprintf('%s-ssh', self.name))
|
341
|
-
end
|
342
|
-
|
343
|
-
result
|
344
|
-
|
345
|
-
end
|
346
|
-
|
347
|
-
end
|