jfoundry 0.1.0.pre
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +746 -0
- data/Rakefile +10 -0
- data/lib/cc_api_stub/applications.rb +53 -0
- data/lib/cc_api_stub/domains.rb +32 -0
- data/lib/cc_api_stub/frameworks.rb +22 -0
- data/lib/cc_api_stub/helper.rb +139 -0
- data/lib/cc_api_stub/login.rb +21 -0
- data/lib/cc_api_stub/organization_users.rb +21 -0
- data/lib/cc_api_stub/organizations.rb +70 -0
- data/lib/cc_api_stub/routes.rb +26 -0
- data/lib/cc_api_stub/runtimes.rb +22 -0
- data/lib/cc_api_stub/service_bindings.rb +22 -0
- data/lib/cc_api_stub/service_instances.rb +22 -0
- data/lib/cc_api_stub/services.rb +21 -0
- data/lib/cc_api_stub/spaces.rb +49 -0
- data/lib/cc_api_stub/users.rb +85 -0
- data/lib/cc_api_stub.rb +16 -0
- data/lib/jfoundry/auth_token.rb +63 -0
- data/lib/jfoundry/baseclient.rb +177 -0
- data/lib/jfoundry/chatty_hash.rb +46 -0
- data/lib/jfoundry/client.rb +39 -0
- data/lib/jfoundry/concerns/proxy_options.rb +17 -0
- data/lib/jfoundry/errors.rb +163 -0
- data/lib/jfoundry/rest_client.rb +331 -0
- data/lib/jfoundry/signature/version.rb +27 -0
- data/lib/jfoundry/signer.rb +13 -0
- data/lib/jfoundry/test_support.rb +3 -0
- data/lib/jfoundry/timer.rb +13 -0
- data/lib/jfoundry/trace_helpers.rb +64 -0
- data/lib/jfoundry/upload_helpers.rb +222 -0
- data/lib/jfoundry/v2/app.rb +357 -0
- data/lib/jfoundry/v2/app_event.rb +13 -0
- data/lib/jfoundry/v2/base.rb +92 -0
- data/lib/jfoundry/v2/client.rb +78 -0
- data/lib/jfoundry/v2/domain.rb +20 -0
- data/lib/jfoundry/v2/managed_service_instance.rb +13 -0
- data/lib/jfoundry/v2/model.rb +209 -0
- data/lib/jfoundry/v2/model_magic/attribute.rb +49 -0
- data/lib/jfoundry/v2/model_magic/client_extensions.rb +170 -0
- data/lib/jfoundry/v2/model_magic/has_summary.rb +49 -0
- data/lib/jfoundry/v2/model_magic/queryable_by.rb +39 -0
- data/lib/jfoundry/v2/model_magic/to_many.rb +138 -0
- data/lib/jfoundry/v2/model_magic/to_one.rb +81 -0
- data/lib/jfoundry/v2/model_magic.rb +93 -0
- data/lib/jfoundry/v2/organization.rb +22 -0
- data/lib/jfoundry/v2/quota_definition.rb +12 -0
- data/lib/jfoundry/v2/route.rb +25 -0
- data/lib/jfoundry/v2/service.rb +20 -0
- data/lib/jfoundry/v2/service_auth_token.rb +10 -0
- data/lib/jfoundry/v2/service_binding.rb +10 -0
- data/lib/jfoundry/v2/service_broker.rb +11 -0
- data/lib/jfoundry/v2/service_instance.rb +13 -0
- data/lib/jfoundry/v2/service_plan.rb +13 -0
- data/lib/jfoundry/v2/space.rb +18 -0
- data/lib/jfoundry/v2/stack.rb +10 -0
- data/lib/jfoundry/v2/user.rb +104 -0
- data/lib/jfoundry/v2/user_provided_service_instance.rb +7 -0
- data/lib/jfoundry/validator.rb +41 -0
- data/lib/jfoundry/version.rb +4 -0
- data/lib/jfoundry/zip.rb +56 -0
- data/lib/jfoundry.rb +5 -0
- data/lib/tasks/gem_release.rake +42 -0
- data/vendor/errors/README.md +3 -0
- data/vendor/errors/v1.yml +189 -0
- data/vendor/errors/v2.yml +470 -0
- metadata +269 -0
@@ -0,0 +1,63 @@
|
|
1
|
+
module JFoundry
|
2
|
+
class AuthToken
|
3
|
+
class << self
|
4
|
+
def from_uaa_token_info(token_info)
|
5
|
+
new(
|
6
|
+
token_info.auth_header,
|
7
|
+
token_info.info[:refresh_token]
|
8
|
+
)
|
9
|
+
end
|
10
|
+
|
11
|
+
def from_hash(hash)
|
12
|
+
new(
|
13
|
+
hash[:token],
|
14
|
+
hash[:refresh_token]
|
15
|
+
)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize(auth_header, refresh_token = nil)
|
20
|
+
@auth_header = auth_header
|
21
|
+
@refresh_token = refresh_token
|
22
|
+
end
|
23
|
+
|
24
|
+
attr_accessor :auth_header
|
25
|
+
attr_reader :refresh_token
|
26
|
+
|
27
|
+
def to_hash
|
28
|
+
{
|
29
|
+
:token => auth_header,
|
30
|
+
:refresh_token => @refresh_token
|
31
|
+
}
|
32
|
+
end
|
33
|
+
|
34
|
+
JSON_HASH = /\{.*?\}/.freeze
|
35
|
+
|
36
|
+
# TODO: rename to #data
|
37
|
+
def token_data
|
38
|
+
return @token_data if @token_data
|
39
|
+
return {} unless @auth_header
|
40
|
+
|
41
|
+
json_hashes = Base64.decode64(@auth_header.split(" ", 2).last)
|
42
|
+
data_json = json_hashes.sub(JSON_HASH, "")[JSON_HASH]
|
43
|
+
return {} unless data_json
|
44
|
+
|
45
|
+
@token_data = MultiJson.load data_json, :symbolize_keys => true
|
46
|
+
rescue MultiJson::DecodeError
|
47
|
+
{}
|
48
|
+
end
|
49
|
+
|
50
|
+
def auth_header=(auth_header)
|
51
|
+
@token_data = nil
|
52
|
+
@auth_header = auth_header
|
53
|
+
end
|
54
|
+
|
55
|
+
def expiration
|
56
|
+
Time.at(token_data[:exp])
|
57
|
+
end
|
58
|
+
|
59
|
+
def expires_soon?
|
60
|
+
(expiration.to_i - Time.now.to_i) < 60
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,177 @@
|
|
1
|
+
require "jfoundry/trace_helpers"
|
2
|
+
require "net/https"
|
3
|
+
require "net/http/post/multipart"
|
4
|
+
require "multi_json"
|
5
|
+
require "fileutils"
|
6
|
+
require "forwardable"
|
7
|
+
|
8
|
+
module JFoundry
|
9
|
+
class BaseClient # :nodoc:
|
10
|
+
include JFoundry::ProxyOptions
|
11
|
+
|
12
|
+
extend Forwardable
|
13
|
+
|
14
|
+
attr_reader :rest_client
|
15
|
+
|
16
|
+
def_delegators :rest_client, :target, :target=, :access_key, :secret_key, :version, #:token,
|
17
|
+
:trace, :backtrace, :backtrace=, :log, :log=,
|
18
|
+
:http_proxy, :http_proxy=, :https_proxy, :https_proxy=
|
19
|
+
|
20
|
+
def initialize(target, access_key, secret_key, version)
|
21
|
+
@rest_client = JFoundry::RestClient.new(target, access_key, secret_key, version)
|
22
|
+
self.trace = false
|
23
|
+
self.backtrace = false
|
24
|
+
self.log = false
|
25
|
+
end
|
26
|
+
|
27
|
+
def trace=(trace)
|
28
|
+
@rest_client.trace = trace
|
29
|
+
#@uaa.trace = trace if @uaa
|
30
|
+
end
|
31
|
+
|
32
|
+
# Cloud metadata
|
33
|
+
def info
|
34
|
+
get("info", :accept => :json)
|
35
|
+
end
|
36
|
+
|
37
|
+
def get(*args)
|
38
|
+
request("GET", *args)
|
39
|
+
end
|
40
|
+
|
41
|
+
def delete(*args)
|
42
|
+
request("DELETE", *args)
|
43
|
+
end
|
44
|
+
|
45
|
+
def post(*args)
|
46
|
+
request("POST", *args)
|
47
|
+
end
|
48
|
+
|
49
|
+
def put(*args)
|
50
|
+
request("PUT", *args)
|
51
|
+
end
|
52
|
+
|
53
|
+
def request(method, *args)
|
54
|
+
#puts "args: ", args
|
55
|
+
path, options = normalize_arguments(args)
|
56
|
+
#puts "path: ", path
|
57
|
+
#puts "options: ", options
|
58
|
+
request, response = request_raw(method, path, options)
|
59
|
+
handle_response(response, options, request)
|
60
|
+
end
|
61
|
+
|
62
|
+
def request_raw(method, path, options)
|
63
|
+
@rest_client.request(method, path, options)
|
64
|
+
end
|
65
|
+
|
66
|
+
def stream_url(url, &blk)
|
67
|
+
uri = URI.parse(url)
|
68
|
+
|
69
|
+
opts = {}
|
70
|
+
|
71
|
+
if uri.scheme == "https"
|
72
|
+
opts[:use_ssl] = true
|
73
|
+
opts[:verify_mode] = OpenSSL::SSL::VERIFY_NONE
|
74
|
+
end
|
75
|
+
|
76
|
+
Net::HTTP.start(uri.host, uri.port, *proxy_options_for(uri), opts) do |http|
|
77
|
+
http.read_timeout = 5
|
78
|
+
|
79
|
+
req = Net::HTTP::Get.new(uri.request_uri)
|
80
|
+
#req["Authorization"] = token.auth_header if token
|
81
|
+
|
82
|
+
http.request(req) do |response|
|
83
|
+
case response
|
84
|
+
when Net::HTTPOK
|
85
|
+
response.read_body(&blk)
|
86
|
+
when Net::HTTPNotFound
|
87
|
+
raise JFoundry::NotFound.new(response.body, 404)
|
88
|
+
when Net::HTTPForbidden
|
89
|
+
raise JFoundry::Denied.new(response.body, 403)
|
90
|
+
when Net::HTTPUnauthorized
|
91
|
+
raise JFoundry::Unauthorized.new(response.body, 401)
|
92
|
+
else
|
93
|
+
raise JFoundry::BadResponse.new(response.body, response.code)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
|
101
|
+
def status_is_successful?(code)
|
102
|
+
(code >= 200) && (code < 400)
|
103
|
+
end
|
104
|
+
|
105
|
+
def handle_response(response, options, request)
|
106
|
+
if status_is_successful?(response[:status].to_i)
|
107
|
+
handle_successful_response(response, options)
|
108
|
+
else
|
109
|
+
handle_error_response(response, request)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def handle_successful_response(response, options)
|
114
|
+
if options[:return_response]
|
115
|
+
response
|
116
|
+
elsif options[:accept] == :json
|
117
|
+
parse_json(response[:body])
|
118
|
+
else
|
119
|
+
response[:body]
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def handle_error_response(response, request)
|
124
|
+
body_json = parse_json(response[:body])
|
125
|
+
body_code = body_json && body_json[:code]
|
126
|
+
code = body_code || response[:status].to_i
|
127
|
+
|
128
|
+
if body_code
|
129
|
+
error_class = JFoundry::APIError.error_classes[body_code] || JFoundry::APIError
|
130
|
+
raise error_class.new(body_json[:description], body_code, request, response)
|
131
|
+
end
|
132
|
+
|
133
|
+
case code
|
134
|
+
when 404
|
135
|
+
raise JFoundry::NotFound.new(nil, code, request, response)
|
136
|
+
when 403
|
137
|
+
raise JFoundry::Denied.new(nil, code, request, response)
|
138
|
+
when 401
|
139
|
+
raise JFoundry::Unauthorized.new(nil, code, request, response)
|
140
|
+
else
|
141
|
+
raise JFoundry::BadResponse.new(nil, code, request, response)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def normalize_arguments(args)
|
146
|
+
if args.last.is_a?(Hash)
|
147
|
+
options = args.pop
|
148
|
+
else
|
149
|
+
options = {}
|
150
|
+
end
|
151
|
+
|
152
|
+
[normalize_path(args), options]
|
153
|
+
end
|
154
|
+
|
155
|
+
URI_ENCODING_PATTERN = Regexp.new("[^#{URI::PATTERN::UNRESERVED}]")
|
156
|
+
|
157
|
+
def normalize_path(segments)
|
158
|
+
if segments.size == 1 && segments.first =~ /^\//
|
159
|
+
segments.first
|
160
|
+
else
|
161
|
+
segments.flatten.collect { |x|
|
162
|
+
URI.encode(x.to_s, URI_ENCODING_PATTERN)
|
163
|
+
}.join("/")
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
def parse_json(x)
|
168
|
+
if x.empty?
|
169
|
+
raise MultiJson::DecodeError.new("Empty JSON string", [], "")
|
170
|
+
else
|
171
|
+
MultiJson.load(x, :symbolize_keys => true)
|
172
|
+
end
|
173
|
+
rescue MultiJson::DecodeError
|
174
|
+
nil
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module JFoundry
|
2
|
+
class ChattyHash
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
def initialize(callback, hash = {})
|
6
|
+
@callback = callback
|
7
|
+
@hash = hash
|
8
|
+
end
|
9
|
+
|
10
|
+
def [](name)
|
11
|
+
@hash[name]
|
12
|
+
end
|
13
|
+
|
14
|
+
def []=(name, value)
|
15
|
+
@hash[name] = value
|
16
|
+
@callback.call(self)
|
17
|
+
value
|
18
|
+
end
|
19
|
+
|
20
|
+
def each(&blk)
|
21
|
+
@hash.each(&blk)
|
22
|
+
end
|
23
|
+
|
24
|
+
def delete(key)
|
25
|
+
value = @hash.delete(key)
|
26
|
+
@callback.call(self)
|
27
|
+
value
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_json(*args)
|
31
|
+
@hash.to_json(*args)
|
32
|
+
end
|
33
|
+
|
34
|
+
def to_hash
|
35
|
+
@hash
|
36
|
+
end
|
37
|
+
|
38
|
+
def to_s
|
39
|
+
@hash.to_s
|
40
|
+
end
|
41
|
+
|
42
|
+
def inspect
|
43
|
+
@hash.inspect
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require "jfoundry/concerns/proxy_options"
|
2
|
+
|
3
|
+
require "jfoundry/baseclient"
|
4
|
+
require "jfoundry/rest_client"
|
5
|
+
#require "jfoundry/auth_token"
|
6
|
+
|
7
|
+
require "jfoundry/v2/app"
|
8
|
+
require "jfoundry/v2/service"
|
9
|
+
require "jfoundry/v2/service_binding"
|
10
|
+
require "jfoundry/v2/managed_service_instance"
|
11
|
+
require "jfoundry/v2/user_provided_service_instance"
|
12
|
+
require "jfoundry/v2/service_plan"
|
13
|
+
#require "jfoundry/v2/service_auth_token"
|
14
|
+
require "jfoundry/v2/user"
|
15
|
+
require "jfoundry/v2/organization"
|
16
|
+
require "jfoundry/v2/space"
|
17
|
+
require "jfoundry/v2/domain"
|
18
|
+
require "jfoundry/v2/route"
|
19
|
+
require "jfoundry/v2/stack"
|
20
|
+
require "jfoundry/v2/quota_definition"
|
21
|
+
require "jfoundry/v2/app_event"
|
22
|
+
require "jfoundry/v2/service_broker"
|
23
|
+
|
24
|
+
require "jfoundry/v2/base"
|
25
|
+
require "jfoundry/v2/client"
|
26
|
+
#require "jfoundry/v2/fake_client"
|
27
|
+
|
28
|
+
module JFoundry
|
29
|
+
class Client < BaseClient
|
30
|
+
def self.new(*args)
|
31
|
+
warn "DEPRECATION WARNING: Please use JFoundry::Client.get instead of JFoundry::Client.new"
|
32
|
+
get(*args)
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.get(*args)
|
36
|
+
JFoundry::V2::Client.new(*args).tap { |client| client.info }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module JFoundry
|
2
|
+
module ProxyOptions
|
3
|
+
def proxy_options_for(uri)
|
4
|
+
ssl = uri.is_a?(URI::HTTPS)
|
5
|
+
proxy_to_use = (ssl ? https_proxy : http_proxy)
|
6
|
+
|
7
|
+
if proxy_to_use.blank?
|
8
|
+
[]
|
9
|
+
else
|
10
|
+
proxy_to_use = "proto://#{proxy_to_use}" unless proxy_to_use =~ /:\/\//
|
11
|
+
proxy_uri = URI.parse(proxy_to_use)
|
12
|
+
proxy_user, proxy_password = proxy_uri.userinfo.split(/:/) if proxy_uri.userinfo
|
13
|
+
[proxy_uri.host, proxy_uri.port, proxy_user, proxy_password]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,163 @@
|
|
1
|
+
require "net/https"
|
2
|
+
require "multi_json"
|
3
|
+
require "yaml"
|
4
|
+
|
5
|
+
module JFoundry
|
6
|
+
# Base class for JFoundry errors (not from the server).
|
7
|
+
class Error < RuntimeError
|
8
|
+
end
|
9
|
+
|
10
|
+
class Deprecated < Error
|
11
|
+
end
|
12
|
+
|
13
|
+
class Mismatch < Error
|
14
|
+
def initialize(expected, got)
|
15
|
+
@expected = expected
|
16
|
+
@got = got
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_s
|
20
|
+
"Invalid value type; expected #{@expected.inspect}, got #{@got.inspect}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class InvalidTarget < Error
|
25
|
+
attr_reader :target
|
26
|
+
|
27
|
+
def initialize(target)
|
28
|
+
@target = target
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_s
|
32
|
+
"Invalid target URI: #{@target}"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
class TargetRefused < Error
|
37
|
+
# Error message.
|
38
|
+
attr_reader :message
|
39
|
+
|
40
|
+
# Message varies as this represents various network errors.
|
41
|
+
def initialize(message)
|
42
|
+
@message = message
|
43
|
+
end
|
44
|
+
|
45
|
+
# Exception message.
|
46
|
+
def to_s
|
47
|
+
"target refused connection (#@message)"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
class Timeout < Timeout::Error
|
52
|
+
attr_reader :method, :uri, :parent
|
53
|
+
|
54
|
+
def initialize(method, uri, parent = nil)
|
55
|
+
@method = method
|
56
|
+
@uri = uri
|
57
|
+
@parent = parent
|
58
|
+
super(to_s)
|
59
|
+
end
|
60
|
+
|
61
|
+
def to_s
|
62
|
+
"#{method} #{uri} timed out"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Exception representing errors returned by the API.
|
67
|
+
class APIError < RuntimeError
|
68
|
+
include TraceHelpers
|
69
|
+
|
70
|
+
class << self
|
71
|
+
def error_classes
|
72
|
+
@error_classes ||= {}
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
attr_reader :error_code, :description, :request, :response
|
77
|
+
|
78
|
+
# Create an APIError with a given request and response.
|
79
|
+
def initialize(description = nil, error_code = nil, request = nil, response = nil)
|
80
|
+
@response = response
|
81
|
+
@request = request
|
82
|
+
@error_code = error_code || (response ? response[:status] : nil)
|
83
|
+
@description = description || parse_description
|
84
|
+
end
|
85
|
+
|
86
|
+
# Exception message.
|
87
|
+
def to_s
|
88
|
+
"#{error_code}: #{description}"
|
89
|
+
end
|
90
|
+
|
91
|
+
def request_trace
|
92
|
+
super(request)
|
93
|
+
end
|
94
|
+
|
95
|
+
def response_trace
|
96
|
+
super(response)
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
|
101
|
+
def parse_description
|
102
|
+
return unless response
|
103
|
+
|
104
|
+
parse_json(response[:body])[:description]
|
105
|
+
rescue MultiJson::DecodeError
|
106
|
+
response[:body]
|
107
|
+
end
|
108
|
+
|
109
|
+
def parse_json(x)
|
110
|
+
if x.empty?
|
111
|
+
raise MultiJson::DecodeError.new("Empty JSON string", [], "")
|
112
|
+
else
|
113
|
+
MultiJson.load(x, :symbolize_keys => true)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
class NotFound < APIError
|
119
|
+
end
|
120
|
+
|
121
|
+
class Denied < APIError
|
122
|
+
end
|
123
|
+
|
124
|
+
class Unauthorized < APIError
|
125
|
+
end
|
126
|
+
|
127
|
+
class BadResponse < APIError
|
128
|
+
end
|
129
|
+
|
130
|
+
class UAAError < APIError
|
131
|
+
end
|
132
|
+
|
133
|
+
def self.define_error(class_name, code)
|
134
|
+
base =
|
135
|
+
case class_name
|
136
|
+
when /NotFound$/
|
137
|
+
NotFound
|
138
|
+
else
|
139
|
+
APIError
|
140
|
+
end
|
141
|
+
|
142
|
+
klass =
|
143
|
+
if const_defined?(class_name)
|
144
|
+
const_get(class_name)
|
145
|
+
else
|
146
|
+
Class.new(base)
|
147
|
+
end
|
148
|
+
|
149
|
+
APIError.error_classes[code] = klass
|
150
|
+
|
151
|
+
unless const_defined?(class_name)
|
152
|
+
const_set(class_name, klass)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
VENDOR_DIR = File.expand_path("../../../vendor", __FILE__)
|
157
|
+
|
158
|
+
%w{errors/v1.yml errors/v2.yml}.each do |errors|
|
159
|
+
YAML.load_file("#{VENDOR_DIR}/#{errors}").each do |code, meta|
|
160
|
+
define_error(meta["name"], code)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|