hula 0.7.1

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.
@@ -0,0 +1,69 @@
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/api'
12
+
13
+ require 'forwardable'
14
+
15
+ module Hula
16
+ module ServiceBroker
17
+ class Client
18
+
19
+ extend Forwardable
20
+ def_delegators :api,
21
+ :catalog,
22
+ :debug,
23
+ :deprovision_instance,
24
+ :bind_instance,
25
+ :unbind_instance,
26
+ :url
27
+
28
+ def initialize(args = {})
29
+ api_args = args.reject { |k, _v| k == :api }
30
+ @api = args.fetch(:api) { Api.new(api_args) }
31
+ end
32
+
33
+ def provision_and_bind(service_name, plan_name, &block)
34
+ raise Error, 'no block given' unless block_given?
35
+ provision_instance(service_name, plan_name) do |service_instance|
36
+ bind_instance(service_instance, &block)
37
+ end
38
+ end
39
+
40
+ def provision_instance(service_name, plan_name, &block)
41
+ plan = catalog.service_plan(service_name, plan_name)
42
+ service_instance = api.provision_instance(plan)
43
+ return service_instance unless block
44
+
45
+ begin
46
+ block.call(service_instance)
47
+ ensure
48
+ api.deprovision_instance(service_instance)
49
+ end
50
+ end
51
+
52
+ def bind_instance(service_instance, &block)
53
+ binding = api.bind_instance(service_instance)
54
+ return binding unless block
55
+
56
+ begin
57
+ block.call(binding, service_instance)
58
+ ensure
59
+ api.unbind_instance(binding)
60
+ sleep 1
61
+ end
62
+ end
63
+
64
+ private
65
+
66
+ attr_reader :api
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,24 @@
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
+ module Hula
12
+ module ServiceBroker
13
+ class Error < StandardError; end
14
+
15
+ class NotInCatalog < Error; end
16
+ class PlanNotFoundError < NotInCatalog; end
17
+ class ServiceNotFoundError < NotInCatalog; end
18
+
19
+ class JsonParseError < Error; end
20
+ class TimeoutError < Error; end
21
+ class HTTPError < Error; end
22
+
23
+ end
24
+ end
@@ -0,0 +1,90 @@
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 'net/http'
12
+
13
+ require 'hula/service_broker/errors'
14
+
15
+ module Hula
16
+ module ServiceBroker
17
+
18
+ class HttpProxyNull
19
+ def http_host
20
+ nil
21
+ end
22
+ def http_port
23
+ nil
24
+ end
25
+ end
26
+
27
+ class HttpJsonClient
28
+ def initialize(http_proxy: HttpProxyNull.new)
29
+ @http_proxy = http_proxy
30
+ end
31
+
32
+ def get(uri, auth: nil)
33
+ request = Net::HTTP::Get.new(uri)
34
+ request.basic_auth auth.fetch(:username), auth.fetch(:password) unless auth.nil?
35
+ send_request(request)
36
+ end
37
+
38
+ def put(uri, body: nil, auth: nil)
39
+ request = Net::HTTP::Put.new(uri)
40
+ request.body = JSON.generate(body) if body
41
+ request.basic_auth auth.fetch(:username), auth.fetch(:password) unless auth.nil?
42
+ send_request(request)
43
+ end
44
+
45
+ def delete(uri, auth: nil)
46
+ request = Net::HTTP::Delete.new(uri)
47
+ request.basic_auth auth.fetch(:username), auth.fetch(:password) unless auth.nil?
48
+ send_request(request)
49
+ end
50
+
51
+ private
52
+
53
+ attr_reader :http_proxy
54
+
55
+ def send_request(request)
56
+ uri = request.uri
57
+ make_request(uri.hostname, uri.port, uri.scheme, request)
58
+ end
59
+
60
+ def make_request(host, port, scheme, request)
61
+ response = Net::HTTP.start(
62
+ host,
63
+ port,
64
+ http_proxy.http_host,
65
+ http_proxy.http_port,
66
+ use_ssl: scheme == 'https',
67
+ verify_mode: OpenSSL::SSL::VERIFY_NONE
68
+ ) { |http| http.request(request) }
69
+
70
+ handle(response)
71
+ rescue Timeout::Error => e
72
+ raise TimeoutError, e
73
+ end
74
+
75
+ def handle(response)
76
+ unless response.is_a?(Net::HTTPSuccess)
77
+ fail HTTPError, [
78
+ response.uri.to_s,
79
+ response.code,
80
+ response.body
81
+ ].join("\n\n")
82
+ end
83
+
84
+ JSON.parse(response.body, symbolize_names: true)
85
+ rescue JSON::ParserError => e
86
+ raise JsonParseError, e
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,30 @@
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
+ module Hula
12
+ module ServiceBroker
13
+ class InstanceBinding
14
+ attr_reader :id, :credentials, :service_instance
15
+
16
+ def initialize(id:, credentials:, service_instance:)
17
+ @id = id
18
+ @credentials = credentials
19
+ @service_instance = service_instance
20
+ end
21
+
22
+ def ==(other)
23
+ is_a?(other.class) &&
24
+ id == other.id &&
25
+ credentials == other.credentials &&
26
+ service_instance == other.service_instance
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,32 @@
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
+ module Hula
12
+ module ServiceBroker
13
+ class Plan
14
+ def initialize(args = {})
15
+ @id = args.fetch(:id)
16
+ @name = args.fetch(:name)
17
+ @description = args.fetch(:description)
18
+ @service_id = args.fetch(:service_id)
19
+ end
20
+
21
+ attr_reader :id, :name, :description, :service_id
22
+
23
+ def ==(other)
24
+ is_a?(other.class) &&
25
+ id == other.id &&
26
+ name == other.name &&
27
+ description == other.description &&
28
+ service_id == other.service_id
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,47 @@
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/plan'
13
+
14
+ module Hula
15
+ module ServiceBroker
16
+ class Service
17
+ def initialize(args = {})
18
+ @id = args.fetch(:id)
19
+ @name = args.fetch(:name)
20
+ @description = args.fetch(:description)
21
+ @bindable = !!args.fetch(:bindable)
22
+ @plans = args.fetch(:plans).map { |p| Plan.new(p.merge(service_id: self.id)) }
23
+ end
24
+
25
+ attr_reader :id, :name, :description, :bindable, :plans
26
+
27
+ def ==(other)
28
+ is_a?(other.class) &&
29
+ id == other.id &&
30
+ name == other.name &&
31
+ description == other.description &&
32
+ bindable == other.bindable &&
33
+ plans == other.plans
34
+ end
35
+
36
+ def plan(plan_name)
37
+ plans.find { |p| p.name == plan_name } or
38
+ fail(PlanNotFoundError, [
39
+ %{Unknown plan with name: #{plan_name.inspect}},
40
+ " Known plan names are: #{plans.map(&:name).inspect}"
41
+ ].join("\n")
42
+ )
43
+ end
44
+
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,24 @@
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
+ module Hula
12
+ module ServiceBroker
13
+ class ServiceInstance
14
+ def initialize(id:)
15
+ @id = id
16
+ end
17
+ attr_reader :id
18
+
19
+ def ==(other)
20
+ is_a?(other.class) && id == other.id
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,118 @@
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
+ require 'pty'
13
+ require 'expect'
14
+
15
+ module Hula
16
+ class Socks4ProxySsh
17
+ class FailedToStart < StandardError; end
18
+ class DieSignal < StandardError; end
19
+
20
+ include Helpers::SocketTools
21
+
22
+ def initialize(
23
+ ssh_host:,
24
+ ssh_username:,
25
+ ssh_password:,
26
+ socks_host: 'localhost',
27
+ socks_port: free_port,
28
+ ssh_bin: 'ssh',
29
+ retry_count: 10
30
+ )
31
+ @ssh_bin = String(ssh_bin)
32
+ @ssh_host = String(ssh_host)
33
+ @ssh_username = String(ssh_username)
34
+ @ssh_password = String(ssh_password)
35
+ @socks_port = String(socks_port)
36
+ @socks_host = String(socks_host)
37
+ @retry_count = retry_count
38
+ end
39
+
40
+ attr_reader :socks_port, :socks_host
41
+
42
+ def stop
43
+ return unless @thread
44
+ @thread.raise(DieSignal)
45
+ @thread.join
46
+ @thread = nil
47
+ end
48
+
49
+ def start
50
+ @thread ||= begin
51
+ thread = start_ssh_socks_thread
52
+ wait_for_port(:host => socks_host, :port => socks_port, timeout_seconds: 30)
53
+ thread
54
+ end
55
+ end
56
+
57
+ private
58
+
59
+ attr_reader :ssh_host, :ssh_username, :ssh_password, :ssh_bin
60
+
61
+ def start_ssh_socks_thread
62
+ # We need to shell out to ssh as this has a SOCKS proxy facility
63
+ # However, there's a problem. SSH does not provide an argument for passing in a password
64
+ # We don't have keys available so we need to use expect to feed the password into the prompt
65
+ Thread.new(
66
+ ssh_command,
67
+ ssh_password
68
+ ) do |ssh_command, ssh_password|
69
+ tries_remaining = @retry_count
70
+
71
+ while true
72
+ puts "--- SSH Gateway attempt #{@retry_count + 1 - tries_remaining}"
73
+ sleep 1
74
+
75
+ begin
76
+ ssh_out, ssh_in, pid = PTY.spawn(*ssh_command)
77
+ # On BOSH lite a password is used, however on aws an identity is used
78
+ # The below statment will wait on a aws run and the pid is never returned
79
+ ssh_out.expect(/[Pp]assword\:/) { |r| ssh_in.print("#{ssh_password}\n") }
80
+ Process.wait(pid)
81
+
82
+ tries_remaining -= 1
83
+ if tries_remaining < 0
84
+ raise FailedToStart, "SSH finished early - SSH Socks Proxy could not be setup or failed"
85
+ end
86
+ rescue Errno::EIO
87
+ tries_remaining -= 1
88
+ # Can't read or write to dead process
89
+
90
+ if tries_remaining < 0
91
+ raise FailedToStart, "SSH finished early EIO - SSH Socks Proxy could not be setup or failed"
92
+ end
93
+ rescue DieSignal
94
+ Process.kill('KILL', pid) rescue Errno::ESRCH
95
+ Process.wait(pid) rescue Errno::ECHILD
96
+ break
97
+ end
98
+ end
99
+ end.tap do |thread|
100
+ thread.abort_on_exception = true
101
+ end
102
+ end
103
+
104
+ def ssh_command
105
+ [
106
+ ssh_bin,
107
+ '-D', "#{socks_host}:#{socks_port}",
108
+ '-N',
109
+ "#{ssh_username}@#{ssh_host}",
110
+ '-o', 'UserKnownHostsFile=/dev/null',
111
+ '-o', 'StrictHostKeyChecking=no',
112
+ '-o', 'NumberOfPasswordPrompts=1',
113
+ '-o', 'StrictHostKeyChecking=no'
114
+ ]
115
+ end
116
+
117
+ end
118
+ end
@@ -0,0 +1,13 @@
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
+ module Hula
12
+ VERSION = '0.7.1'
13
+ end
data/lib/hula.rb ADDED
@@ -0,0 +1,16 @@
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/version'
12
+ require 'hula/bosh_director'
13
+ require 'hula/cloud_foundry'
14
+
15
+ module Hula
16
+ end