hula 0.7.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LEGAL +13 -0
- data/lib/hula/bosh_director.rb +246 -0
- data/lib/hula/bosh_manifest/job.rb +35 -0
- data/lib/hula/bosh_manifest.rb +102 -0
- data/lib/hula/cloud_foundry/service_broker.rb +36 -0
- data/lib/hula/cloud_foundry.rb +308 -0
- data/lib/hula/command_runner.rb +38 -0
- data/lib/hula/helpers/socket_tools.rb +44 -0
- data/lib/hula/helpers/timeout_tools.rb +27 -0
- data/lib/hula/http_proxy_upstream_socks.rb +72 -0
- data/lib/hula/service_broker/api.rb +123 -0
- data/lib/hula/service_broker/catalog.rb +43 -0
- data/lib/hula/service_broker/client.rb +69 -0
- data/lib/hula/service_broker/errors.rb +24 -0
- data/lib/hula/service_broker/http_json_client.rb +90 -0
- data/lib/hula/service_broker/instance_binding.rb +30 -0
- data/lib/hula/service_broker/plan.rb +32 -0
- data/lib/hula/service_broker/service.rb +47 -0
- data/lib/hula/service_broker/service_instance.rb +24 -0
- data/lib/hula/socks4_proxy_ssh.rb +118 -0
- data/lib/hula/version.rb +13 -0
- data/lib/hula.rb +16 -0
- metadata +235 -0
@@ -0,0 +1,308 @@
|
|
1
|
+
# Copyright (c) 2014-2015 Pivotal Software, Inc.
|
2
|
+
# All rights reserved.
|
3
|
+
# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
4
|
+
# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
5
|
+
# PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
6
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
7
|
+
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
8
|
+
# USE OR OTHER DEALINGS IN THE SOFTWARE.
|
9
|
+
#
|
10
|
+
|
11
|
+
require 'tmpdir'
|
12
|
+
require 'json'
|
13
|
+
require 'open3'
|
14
|
+
require 'tempfile'
|
15
|
+
|
16
|
+
require 'hula/command_runner'
|
17
|
+
require 'hula/cloud_foundry/service_broker'
|
18
|
+
|
19
|
+
module Hula
|
20
|
+
class CloudFoundry
|
21
|
+
attr_reader :current_organization, :current_space, :domain, :api_url
|
22
|
+
|
23
|
+
def initialize(args)
|
24
|
+
@domain = args.fetch(:domain)
|
25
|
+
@api_url = args.fetch(:api_url)
|
26
|
+
@logger = args.fetch(:logger, default_logger)
|
27
|
+
@command_runner = args.fetch(:command_runner, default_command_runner)
|
28
|
+
|
29
|
+
target_and_login = args.fetch(:target_and_login, true)
|
30
|
+
if target_and_login
|
31
|
+
target(api_url)
|
32
|
+
login(args.fetch(:username), args.fetch(:password))
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def url_for_app(app_name)
|
37
|
+
"https://#{app_name}.#{domain}"
|
38
|
+
end
|
39
|
+
|
40
|
+
def target(cloud_controller_url)
|
41
|
+
cf("api #{cloud_controller_url} --skip-ssl-validation")
|
42
|
+
end
|
43
|
+
|
44
|
+
def app_vcap_services(app_name)
|
45
|
+
app_environment(app_name)["VCAP_SERVICES"]
|
46
|
+
end
|
47
|
+
|
48
|
+
def login(username, password, allow_failure = true)
|
49
|
+
cf("auth #{username} #{password}", allow_failure: allow_failure)
|
50
|
+
end
|
51
|
+
|
52
|
+
def service_brokers
|
53
|
+
output = cf('service-brokers')
|
54
|
+
|
55
|
+
if output.include?('No service brokers found')
|
56
|
+
[]
|
57
|
+
else
|
58
|
+
output.split("\n").drop(3).map do |row|
|
59
|
+
name, url = row.split(/\s+/)
|
60
|
+
ServiceBroker.new(name: name, url: url)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def create_and_target_org(name)
|
66
|
+
create_org(name)
|
67
|
+
sleep 1
|
68
|
+
target_org(name)
|
69
|
+
end
|
70
|
+
|
71
|
+
def create_org(name)
|
72
|
+
cf("create-org #{name}")
|
73
|
+
end
|
74
|
+
|
75
|
+
def target_org(name)
|
76
|
+
cf("target -o #{name}")
|
77
|
+
@current_organization = name
|
78
|
+
end
|
79
|
+
|
80
|
+
def create_and_target_space(name)
|
81
|
+
create_space(name)
|
82
|
+
target_space(name)
|
83
|
+
end
|
84
|
+
|
85
|
+
def create_space(name)
|
86
|
+
cf("create-space #{name}")
|
87
|
+
end
|
88
|
+
|
89
|
+
def target_space(name)
|
90
|
+
cf("target -s #{name}")
|
91
|
+
@current_space = name
|
92
|
+
end
|
93
|
+
|
94
|
+
def space_exists?(name)
|
95
|
+
spaces = cf('spaces').lines[3..-1]
|
96
|
+
spaces.map(&:strip).include?(name)
|
97
|
+
end
|
98
|
+
|
99
|
+
def org_exists?(name)
|
100
|
+
orgs = cf('orgs').lines[3..-1]
|
101
|
+
orgs.map(&:strip).include?(name)
|
102
|
+
end
|
103
|
+
|
104
|
+
def setup_permissive_security_group(org, space)
|
105
|
+
rules = [{
|
106
|
+
'destination' => '0.0.0.0-255.255.255.255',
|
107
|
+
'protocol' => 'all'
|
108
|
+
}]
|
109
|
+
|
110
|
+
rule_file = Tempfile.new('default_security_group.json')
|
111
|
+
rule_file.write(rules.to_json)
|
112
|
+
rule_file.close
|
113
|
+
|
114
|
+
cf("create-security-group prof-test #{rule_file.path}")
|
115
|
+
cf("bind-security-group prof-test #{org} #{space}")
|
116
|
+
cf('bind-staging-security-group prof-test')
|
117
|
+
cf('bind-running-security-group prof-test')
|
118
|
+
|
119
|
+
rule_file.unlink
|
120
|
+
end
|
121
|
+
|
122
|
+
def delete_space(name, options = {})
|
123
|
+
allow_failure = options.fetch(:allow_failure, true)
|
124
|
+
cf("delete-space #{name} -f", allow_failure: allow_failure)
|
125
|
+
end
|
126
|
+
|
127
|
+
def delete_org(name, options = {})
|
128
|
+
allow_failure = options.fetch(:allow_failure, true)
|
129
|
+
cf("delete-org #{name} -f", allow_failure: allow_failure)
|
130
|
+
end
|
131
|
+
|
132
|
+
alias_method :reset!, :delete_org
|
133
|
+
|
134
|
+
def add_public_service_broker(service_name, _service_label, url, username, password)
|
135
|
+
cf("create-service-broker #{service_name} #{username} #{password} #{url}")
|
136
|
+
|
137
|
+
service_plans = JSON.parse(cf('curl /v2/service_plans -X GET'))
|
138
|
+
guids = service_plans['resources'].map do |resource|
|
139
|
+
resource['metadata']['guid']
|
140
|
+
end
|
141
|
+
|
142
|
+
guids.each do |guid|
|
143
|
+
cf(%(curl /v2/service_plans/#{guid} -X PUT -d '{"public":true}'))
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def remove_service_broker(service_name, options = {})
|
148
|
+
allow_failure = options.fetch(:allow_failure, true)
|
149
|
+
cf("delete-service-broker #{service_name} -f", allow_failure: allow_failure)
|
150
|
+
end
|
151
|
+
|
152
|
+
def assert_broker_is_in_marketplace(type)
|
153
|
+
output = marketplace
|
154
|
+
unless output.include?(type)
|
155
|
+
fail "Broker #{type} not found in marketplace"
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def marketplace
|
160
|
+
cf('marketplace')
|
161
|
+
end
|
162
|
+
|
163
|
+
def create_service_instance(type, name, plan)
|
164
|
+
cf("create-service #{type} #{plan} #{name}")
|
165
|
+
end
|
166
|
+
|
167
|
+
def delete_service_instance_and_unbind(name, options = {})
|
168
|
+
allow_failure = options.fetch(:allow_failure, true)
|
169
|
+
cf("delete-service -f #{name}", allow_failure: allow_failure)
|
170
|
+
end
|
171
|
+
|
172
|
+
def assert_instance_is_in_services_list(service_name)
|
173
|
+
output = cf('services')
|
174
|
+
unless output.include?(service_name)
|
175
|
+
fail "Instance #{service_name} not found in services list"
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
def push_app_and_start(app_path, name)
|
180
|
+
push_app(app_path, name)
|
181
|
+
start_app(name)
|
182
|
+
end
|
183
|
+
|
184
|
+
def push_app(app_path, name)
|
185
|
+
cf("push #{name} -p #{app_path} -n #{name} -d #{domain} --no-start")
|
186
|
+
end
|
187
|
+
|
188
|
+
def enable_diego_for_app(name)
|
189
|
+
cf("enable-diego #{name}")
|
190
|
+
end
|
191
|
+
|
192
|
+
def delete_app(name, options = {})
|
193
|
+
allow_failure = options.fetch(:allow_failure, true)
|
194
|
+
cf("delete #{name} -f", allow_failure: allow_failure)
|
195
|
+
end
|
196
|
+
|
197
|
+
def bind_app_to_service(app_name, service_name)
|
198
|
+
cf("bind-service #{app_name} #{service_name}")
|
199
|
+
end
|
200
|
+
|
201
|
+
def unbind_app_from_service(app_name, service_name)
|
202
|
+
cf("unbind-service #{app_name} #{service_name}")
|
203
|
+
end
|
204
|
+
|
205
|
+
def list_service_keys(service_instance_name)
|
206
|
+
cf("service-keys #{service_instance_name}")
|
207
|
+
end
|
208
|
+
|
209
|
+
def create_service_key(service_instance_name, key_name)
|
210
|
+
cf("create-service-key #{service_instance_name} #{key_name}")
|
211
|
+
end
|
212
|
+
|
213
|
+
def delete_service_key(service_instance_name, key_name)
|
214
|
+
cf("delete-service-key #{service_instance_name} #{key_name} -f")
|
215
|
+
end
|
216
|
+
|
217
|
+
def service_key(service_instance_name, key_name)
|
218
|
+
cf("service-key #{service_instance_name} #{key_name}")
|
219
|
+
end
|
220
|
+
|
221
|
+
def restart_app(name)
|
222
|
+
stop_app(name)
|
223
|
+
start_app(name)
|
224
|
+
end
|
225
|
+
|
226
|
+
def start_app(name)
|
227
|
+
cf("start #{name}")
|
228
|
+
rescue => start_exception
|
229
|
+
begin
|
230
|
+
cf("logs --recent #{name}")
|
231
|
+
ensure
|
232
|
+
raise start_exception
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
def stop_app(name)
|
237
|
+
cf("stop #{name}")
|
238
|
+
end
|
239
|
+
|
240
|
+
def app_env(app_name)
|
241
|
+
cf("env #{app_name}")
|
242
|
+
end
|
243
|
+
|
244
|
+
def create_user(username, password)
|
245
|
+
cf("create-user #{username} #{password}")
|
246
|
+
end
|
247
|
+
|
248
|
+
def delete_user(username)
|
249
|
+
cf("delete-user -f #{username}")
|
250
|
+
end
|
251
|
+
|
252
|
+
def user_exists?(username, org)
|
253
|
+
output = cf("org-users #{org}")
|
254
|
+
output.lines.select { |l| l.start_with? ' ' }.map(&:strip).uniq.include?(username)
|
255
|
+
end
|
256
|
+
|
257
|
+
def set_org_role(username, org, role)
|
258
|
+
cf("set-org-role #{username} #{org} #{role}")
|
259
|
+
end
|
260
|
+
|
261
|
+
def version
|
262
|
+
cf('-v')
|
263
|
+
end
|
264
|
+
|
265
|
+
private
|
266
|
+
|
267
|
+
attr_reader :logger, :command_runner
|
268
|
+
|
269
|
+
def app_environment(app_name)
|
270
|
+
env_output = cf("env #{app_name}")
|
271
|
+
response = env_output[/^\{.*}$/m].split(/^\n/)
|
272
|
+
response = response.map { |json| JSON.parse(json) }
|
273
|
+
response.inject({}) { |result, current| result.merge(current) }
|
274
|
+
end
|
275
|
+
|
276
|
+
def default_logger
|
277
|
+
@default_logger ||= begin
|
278
|
+
STDOUT.sync = true
|
279
|
+
require 'logger'
|
280
|
+
Logger.new(STDOUT)
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
def default_command_runner
|
285
|
+
@default_command_runner ||= CommandRunner.new(environment: env)
|
286
|
+
end
|
287
|
+
|
288
|
+
def cf(command, options = {})
|
289
|
+
allow_failure = options.fetch(:allow_failure, false)
|
290
|
+
cf_command = "cf #{command}"
|
291
|
+
|
292
|
+
logger.info(cf_command)
|
293
|
+
|
294
|
+
command_runner.run(cf_command, allow_failure: allow_failure)
|
295
|
+
end
|
296
|
+
|
297
|
+
def env
|
298
|
+
@env ||= ENV.to_hash.merge(
|
299
|
+
'PATH' => clean_path,
|
300
|
+
'CF_HOME' => Dir.mktmpdir('cf-home')
|
301
|
+
)
|
302
|
+
end
|
303
|
+
|
304
|
+
def clean_path
|
305
|
+
'/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin/:sbin'
|
306
|
+
end
|
307
|
+
end
|
308
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# Copyright (c) 2014-2015 Pivotal Software, Inc.
|
2
|
+
# All rights reserved.
|
3
|
+
# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
4
|
+
# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
5
|
+
# PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
6
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
7
|
+
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
8
|
+
# USE OR OTHER DEALINGS IN THE SOFTWARE.
|
9
|
+
#
|
10
|
+
|
11
|
+
require 'open3'
|
12
|
+
|
13
|
+
module Hula
|
14
|
+
class CommandFailedError < StandardError; end
|
15
|
+
|
16
|
+
class CommandRunner
|
17
|
+
def initialize(environment: ENV)
|
18
|
+
@environment = environment
|
19
|
+
end
|
20
|
+
|
21
|
+
def run(command, allow_failure: false)
|
22
|
+
stdout_and_stderr, status = Open3.capture2e(environment, command)
|
23
|
+
|
24
|
+
if !allow_failure && !status.success?
|
25
|
+
message = "Command failed! - #{command}\n\n#{stdout_and_stderr}\n\nexit status: #{status.exitstatus}"
|
26
|
+
fail CommandFailedError, message
|
27
|
+
end
|
28
|
+
|
29
|
+
stdout_and_stderr
|
30
|
+
rescue => e
|
31
|
+
raise CommandFailedError, e
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
attr_reader :environment
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# Copyright (c) 2014-2015 Pivotal Software, Inc.
|
2
|
+
# All rights reserved.
|
3
|
+
# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
4
|
+
# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
5
|
+
# PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
6
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
7
|
+
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
8
|
+
# USE OR OTHER DEALINGS IN THE SOFTWARE.
|
9
|
+
#
|
10
|
+
|
11
|
+
require 'hula/helpers/timeout_tools'
|
12
|
+
|
13
|
+
require 'socket'
|
14
|
+
|
15
|
+
module Hula
|
16
|
+
module Helpers
|
17
|
+
module SocketTools
|
18
|
+
module_function def wait_for_port(host:, port:, timeout_seconds: 20)
|
19
|
+
error = "Failed to connect to #{host}:#{port} within #{timeout_seconds} seconds"
|
20
|
+
TimeoutTools.wait_for(error: error, timeout_seconds: timeout_seconds) do
|
21
|
+
port_open?(host: host, port: port)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
module_function def port_open?(host:, port:)
|
26
|
+
socket = TCPSocket.new(host, port)
|
27
|
+
socket.close unless socket.nil?
|
28
|
+
true
|
29
|
+
rescue Errno::ECONNREFUSED
|
30
|
+
false
|
31
|
+
end
|
32
|
+
|
33
|
+
module_function def free_port
|
34
|
+
socket = Socket.new(:INET, :STREAM, 0)
|
35
|
+
socket.bind(Addrinfo.tcp('127.0.0.1', 0))
|
36
|
+
socket.local_address.ip_port
|
37
|
+
ensure
|
38
|
+
socket.close
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# Copyright (c) 2014-2015 Pivotal Software, Inc.
|
2
|
+
# All rights reserved.
|
3
|
+
# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
4
|
+
# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
5
|
+
# PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
6
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
7
|
+
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
8
|
+
# USE OR OTHER DEALINGS IN THE SOFTWARE.
|
9
|
+
#
|
10
|
+
|
11
|
+
require 'timeout'
|
12
|
+
|
13
|
+
module Hula
|
14
|
+
module Helpers
|
15
|
+
module TimeoutTools
|
16
|
+
module_function def wait_for(error: nil, timeout_seconds:, &condition_block)
|
17
|
+
Timeout::timeout(timeout_seconds) do
|
18
|
+
until condition_block.call do
|
19
|
+
sleep 0.1
|
20
|
+
end
|
21
|
+
end
|
22
|
+
rescue Timeout::Error => e
|
23
|
+
error ? raise(error) : raise(e)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# Copyright (c) 2014-2015 Pivotal Software, Inc.
|
2
|
+
# All rights reserved.
|
3
|
+
# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
4
|
+
# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
5
|
+
# PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
6
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
7
|
+
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
8
|
+
# USE OR OTHER DEALINGS IN THE SOFTWARE.
|
9
|
+
#
|
10
|
+
|
11
|
+
require 'hula/helpers/socket_tools'
|
12
|
+
|
13
|
+
module Hula
|
14
|
+
class HttpProxyUpstreamSocks
|
15
|
+
include Helpers::SocketTools
|
16
|
+
|
17
|
+
def initialize(
|
18
|
+
polipo_bin: 'polipo',
|
19
|
+
socks_proxy:,
|
20
|
+
http_host: 'localhost',
|
21
|
+
http_port: free_port
|
22
|
+
)
|
23
|
+
@socks_proxy_host = socks_proxy.socks_host
|
24
|
+
@socks_proxy_port = socks_proxy.socks_port
|
25
|
+
@http_host = http_host
|
26
|
+
@http_port = http_port
|
27
|
+
@polipo_bin = polipo_bin
|
28
|
+
|
29
|
+
check_polipo_bin!
|
30
|
+
end
|
31
|
+
|
32
|
+
attr_reader :http_host, :http_port
|
33
|
+
|
34
|
+
def start
|
35
|
+
@process ||= start_polipo_process
|
36
|
+
end
|
37
|
+
|
38
|
+
def stop
|
39
|
+
return unless @process
|
40
|
+
|
41
|
+
Process.kill('TERM', @process) rescue Errno::ESRCH
|
42
|
+
Process.wait(@process) rescue Errno::ECHILD
|
43
|
+
@process = nil
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
attr_reader :socks_proxy_host, :socks_proxy_port, :polipo_bin
|
49
|
+
|
50
|
+
|
51
|
+
def start_polipo_process
|
52
|
+
pid = Process.spawn(polipo_command)
|
53
|
+
at_exit { stop }
|
54
|
+
wait_for_port(host: http_host, port: http_port)
|
55
|
+
Process.detach(pid)
|
56
|
+
pid
|
57
|
+
end
|
58
|
+
|
59
|
+
def polipo_command
|
60
|
+
"#{polipo_bin} diskCacheRoot='' \
|
61
|
+
proxyPort=#{http_port} \
|
62
|
+
socksParentProxy=#{socks_proxy_host}:#{socks_proxy_port} \
|
63
|
+
socksProxyType=socks4a"
|
64
|
+
end
|
65
|
+
|
66
|
+
def check_polipo_bin!
|
67
|
+
unless system("which #{polipo_bin} > /dev/null 2>&1")
|
68
|
+
raise "Could not run polipo (#{polipo_bin}). Please install, or put in PATH"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
# Copyright (c) 2014-2015 Pivotal Software, Inc.
|
2
|
+
# All rights reserved.
|
3
|
+
# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
4
|
+
# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
5
|
+
# PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
6
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
7
|
+
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
8
|
+
# USE OR OTHER DEALINGS IN THE SOFTWARE.
|
9
|
+
#
|
10
|
+
|
11
|
+
require 'forwardable'
|
12
|
+
require 'hula/service_broker/catalog'
|
13
|
+
require 'hula/service_broker/instance_binding'
|
14
|
+
require 'hula/service_broker/http_json_client'
|
15
|
+
require 'hula/service_broker/service_instance'
|
16
|
+
|
17
|
+
require 'securerandom'
|
18
|
+
|
19
|
+
module Hula
|
20
|
+
module ServiceBroker
|
21
|
+
class Api
|
22
|
+
extend Forwardable
|
23
|
+
def_delegators :catalog, :service_plan
|
24
|
+
|
25
|
+
def initialize(url:, username:, password:, http_client: HttpJsonClient.new)
|
26
|
+
@http_client = http_client
|
27
|
+
|
28
|
+
@url = URI(url)
|
29
|
+
@username = username
|
30
|
+
@password = password
|
31
|
+
end
|
32
|
+
|
33
|
+
attr_reader :url
|
34
|
+
|
35
|
+
def catalog
|
36
|
+
json = http_client.get(url_for('/v2/catalog'), auth: { username: username, password: password })
|
37
|
+
Catalog.new(json)
|
38
|
+
end
|
39
|
+
|
40
|
+
def provision_instance(plan, service_instance_id: SecureRandom.uuid)
|
41
|
+
http_provision_instance(
|
42
|
+
service_id: plan.service_id,
|
43
|
+
plan_id: plan.id,
|
44
|
+
service_instance_id: service_instance_id
|
45
|
+
)
|
46
|
+
|
47
|
+
ServiceInstance.new(id: service_instance_id)
|
48
|
+
end
|
49
|
+
|
50
|
+
def deprovision_instance(service_instance)
|
51
|
+
http_deprovision_service(service_instance_id: service_instance.id)
|
52
|
+
end
|
53
|
+
|
54
|
+
def bind_instance(service_instance, binding_id: SecureRandom.uuid)
|
55
|
+
result = http_bind_instance(
|
56
|
+
service_instance_id: service_instance.id,
|
57
|
+
binding_id: binding_id
|
58
|
+
)
|
59
|
+
|
60
|
+
InstanceBinding.new(
|
61
|
+
id: binding_id,
|
62
|
+
credentials: result.fetch(:credentials),
|
63
|
+
service_instance: service_instance
|
64
|
+
)
|
65
|
+
end
|
66
|
+
|
67
|
+
def unbind_instance(instance_binding)
|
68
|
+
http_unbind_instance(
|
69
|
+
service_instance_id: instance_binding.service_instance.id,
|
70
|
+
binding_id: instance_binding.id
|
71
|
+
)
|
72
|
+
end
|
73
|
+
|
74
|
+
def debug
|
75
|
+
http_client.get(url_for('/debug'), auth: { username: username, password: password })
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
def http_provision_instance(service_instance_id:, service_id:, plan_id:)
|
81
|
+
http_client.put(
|
82
|
+
url_for("/v2/service_instances/#{service_instance_id}"),
|
83
|
+
body: {
|
84
|
+
service_id: service_id,
|
85
|
+
plan_id: plan_id,
|
86
|
+
},
|
87
|
+
auth: { username: username, password: password }
|
88
|
+
)
|
89
|
+
end
|
90
|
+
|
91
|
+
def http_deprovision_service(service_instance_id:)
|
92
|
+
http_client.delete(
|
93
|
+
url_for("/v2/service_instances/#{service_instance_id}"),
|
94
|
+
auth: {
|
95
|
+
username: username,
|
96
|
+
password: password
|
97
|
+
}
|
98
|
+
)
|
99
|
+
end
|
100
|
+
|
101
|
+
def http_bind_instance(service_instance_id:, binding_id:)
|
102
|
+
http_client.put(
|
103
|
+
url_for("/v2/service_instances/#{service_instance_id}/service_bindings/#{binding_id}"),
|
104
|
+
body: {},
|
105
|
+
auth: { username: username, password: password }
|
106
|
+
)
|
107
|
+
end
|
108
|
+
|
109
|
+
def http_unbind_instance(service_instance_id:, binding_id:)
|
110
|
+
http_client.delete(
|
111
|
+
url_for("/v2/service_instances/#{service_instance_id}/service_bindings/#{binding_id}"),
|
112
|
+
auth: { username: username, password: password }
|
113
|
+
)
|
114
|
+
end
|
115
|
+
|
116
|
+
def url_for(path)
|
117
|
+
url.dup.tap { |uri| uri.path += path }
|
118
|
+
end
|
119
|
+
|
120
|
+
attr_reader :http_client, :username, :password
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# Copyright (c) 2014-2015 Pivotal Software, Inc.
|
2
|
+
# All rights reserved.
|
3
|
+
# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
4
|
+
# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
5
|
+
# PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
6
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
7
|
+
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
8
|
+
# USE OR OTHER DEALINGS IN THE SOFTWARE.
|
9
|
+
#
|
10
|
+
|
11
|
+
require 'hula/service_broker/errors'
|
12
|
+
require 'hula/service_broker/service'
|
13
|
+
|
14
|
+
module Hula
|
15
|
+
module ServiceBroker
|
16
|
+
|
17
|
+
class Catalog
|
18
|
+
attr_reader :services
|
19
|
+
|
20
|
+
def initialize(args = {})
|
21
|
+
@services = args.fetch(:services).map { |s| Service.new(s) }
|
22
|
+
end
|
23
|
+
|
24
|
+
def ==(other)
|
25
|
+
is_a?(other.class) &&
|
26
|
+
services == other.services
|
27
|
+
end
|
28
|
+
|
29
|
+
def service(service_name)
|
30
|
+
services.find { |s| s.name == service_name } or
|
31
|
+
fail(ServiceNotFoundError, [
|
32
|
+
%{Unknown service with name: #{service_name.inspect}},
|
33
|
+
" Known service names: #{services.map(&:name).inspect}"
|
34
|
+
].join("\n")
|
35
|
+
)
|
36
|
+
end
|
37
|
+
|
38
|
+
def service_plan(service_name, plan_name)
|
39
|
+
service(service_name).plan(plan_name)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|