cfoundry 0.4.21 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +47 -24
- data/lib/cfoundry/auth_token.rb +48 -0
- data/lib/cfoundry/baseclient.rb +96 -277
- data/lib/cfoundry/client.rb +2 -0
- data/lib/cfoundry/concerns/login_helpers.rb +13 -0
- data/lib/cfoundry/errors.rb +21 -13
- data/lib/cfoundry/rest_client.rb +290 -0
- data/lib/cfoundry/test_support.rb +3 -0
- data/lib/cfoundry/trace_helpers.rb +9 -9
- data/lib/cfoundry/uaaclient.rb +66 -74
- data/lib/cfoundry/upload_helpers.rb +2 -0
- data/lib/cfoundry/v1/app.rb +2 -2
- data/lib/cfoundry/v1/base.rb +4 -51
- data/lib/cfoundry/v1/client.rb +7 -30
- data/lib/cfoundry/v1/model.rb +22 -5
- data/lib/cfoundry/v1/model_magic.rb +30 -15
- data/lib/cfoundry/v2/app.rb +2 -5
- data/lib/cfoundry/v2/base.rb +10 -74
- data/lib/cfoundry/v2/client.rb +19 -30
- data/lib/cfoundry/v2/domain.rb +1 -4
- data/lib/cfoundry/v2/model.rb +1 -3
- data/lib/cfoundry/v2/model_magic.rb +13 -23
- data/lib/cfoundry/version.rb +1 -1
- data/lib/cfoundry/zip.rb +1 -1
- data/spec/cfoundry/auth_token_spec.rb +77 -0
- data/spec/cfoundry/baseclient_spec.rb +54 -30
- data/spec/cfoundry/errors_spec.rb +10 -13
- data/spec/cfoundry/rest_client_spec.rb +238 -0
- data/spec/cfoundry/trace_helpers_spec.rb +10 -5
- data/spec/cfoundry/uaaclient_spec.rb +141 -114
- data/spec/cfoundry/upload_helpers_spec.rb +129 -0
- data/spec/cfoundry/v1/base_spec.rb +2 -2
- data/spec/cfoundry/v1/client_spec.rb +17 -0
- data/spec/cfoundry/v1/model_magic_spec.rb +43 -0
- data/spec/cfoundry/v2/base_spec.rb +256 -33
- data/spec/cfoundry/v2/client_spec.rb +68 -0
- data/spec/cfoundry/v2/model_magic_spec.rb +49 -0
- data/spec/fixtures/apps/with_vmcignore/ignored_dir/file_in_ignored_dir.txt +1 -0
- data/spec/fixtures/apps/with_vmcignore/ignored_file.txt +1 -0
- data/spec/fixtures/apps/with_vmcignore/non_ignored_dir/file_in_non_ignored_dir.txt +1 -0
- data/spec/fixtures/apps/with_vmcignore/non_ignored_dir/ignored_file.txt +1 -0
- data/spec/fixtures/apps/with_vmcignore/non_ignored_file.txt +1 -0
- data/spec/fixtures/empty_file +0 -0
- data/spec/spec_helper.rb +4 -4
- data/spec/support/randoms.rb +3 -0
- data/spec/support/shared_examples/client_login_examples.rb +46 -0
- data/spec/support/{summaries.rb → shared_examples/model_summary_examples.rb} +0 -0
- data/spec/support/v1_fake_helper.rb +144 -0
- metadata +101 -37
- data/lib/cfoundry/spec_helper.rb +0 -1
data/lib/cfoundry/v2/client.rb
CHANGED
@@ -1,9 +1,11 @@
|
|
1
|
+
require File.expand_path("../../concerns/login_helpers", __FILE__)
|
2
|
+
|
1
3
|
module CFoundry::V2
|
2
4
|
# The primary API entrypoint. Wraps a BaseClient to provide nicer return
|
3
5
|
# values. Initialize with the target and, optionally, an auth token. These
|
4
6
|
# are the only two internal states.
|
5
7
|
class Client
|
6
|
-
include ClientMethods
|
8
|
+
include ClientMethods, CFoundry::LoginHelpers
|
7
9
|
|
8
10
|
# Internal BaseClient instance. Normally won't be touching this.
|
9
11
|
attr_reader :base
|
@@ -22,6 +24,10 @@ module CFoundry::V2
|
|
22
24
|
@base = Base.new(target, token)
|
23
25
|
end
|
24
26
|
|
27
|
+
def version
|
28
|
+
2
|
29
|
+
end
|
30
|
+
|
25
31
|
# The current target URL of the client.
|
26
32
|
def target
|
27
33
|
@base.target
|
@@ -76,9 +82,10 @@ module CFoundry::V2
|
|
76
82
|
|
77
83
|
# The currently authenticated user.
|
78
84
|
def current_user
|
79
|
-
|
85
|
+
token_data = @base.token.token_data
|
86
|
+
if guid = token_data[:user_id]
|
80
87
|
user = user(guid)
|
81
|
-
user.emails = [{ :value =>
|
88
|
+
user.emails = [{ :value => token_data[:email] }]
|
82
89
|
user
|
83
90
|
end
|
84
91
|
end
|
@@ -88,36 +95,18 @@ module CFoundry::V2
|
|
88
95
|
@base.info
|
89
96
|
end
|
90
97
|
|
91
|
-
|
92
|
-
def login_prompts
|
93
|
-
if @base.uaa
|
94
|
-
@base.uaa.prompts
|
95
|
-
else
|
96
|
-
{ :username => ["text", "Email"],
|
97
|
-
:password => ["password", "Password"]
|
98
|
-
}
|
99
|
-
end
|
100
|
-
end
|
101
|
-
|
102
|
-
# Authenticate with the target. Sets the client token.
|
103
|
-
#
|
104
|
-
# Credentials is a hash, typically containing :username and :password
|
105
|
-
# keys.
|
106
|
-
#
|
107
|
-
# The values in the hash should mirror the prompts given by
|
108
|
-
# `login_prompts`.
|
109
|
-
def login(credentials)
|
98
|
+
def login(username, password)
|
110
99
|
@current_organization = nil
|
111
100
|
@current_space = nil
|
101
|
+
super
|
102
|
+
end
|
112
103
|
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
credentials[:username])[:token]
|
120
|
-
end
|
104
|
+
def register(email, password)
|
105
|
+
uaa_user = @base.uaa.add_user(email, password)
|
106
|
+
cc_user = user
|
107
|
+
cc_user.guid = uaa_user['id']
|
108
|
+
cc_user.create!
|
109
|
+
cc_user
|
121
110
|
end
|
122
111
|
|
123
112
|
# Clear client token. No requests are made for this.
|
data/lib/cfoundry/v2/domain.rb
CHANGED
@@ -3,12 +3,9 @@ require "cfoundry/v2/model"
|
|
3
3
|
module CFoundry::V2
|
4
4
|
class Domain < Model
|
5
5
|
attribute :name, :string
|
6
|
-
attribute :wildcard, :boolean
|
6
|
+
attribute :wildcard, :boolean
|
7
7
|
to_one :owning_organization, :as => :organization, :default => nil
|
8
8
|
|
9
9
|
queryable_by :name, :owning_organization_guid, :space_guid
|
10
|
-
|
11
|
-
# hide wildcard support for now
|
12
|
-
private :wildcard=
|
13
10
|
end
|
14
11
|
end
|
data/lib/cfoundry/v2/model.rb
CHANGED
@@ -72,7 +72,7 @@ module CFoundry::V2
|
|
72
72
|
@manifest ||= {}
|
73
73
|
@manifest[:entity] ||= {}
|
74
74
|
|
75
|
-
|
75
|
+
@manifest[:entity].each do |k, v|
|
76
76
|
if v.is_a?(Hash) && v.key?(:metadata)
|
77
77
|
# skip; there's a _guid attribute already
|
78
78
|
elsif v.is_a?(Array) && !v.empty? && v.all? { |x|
|
@@ -87,8 +87,6 @@ module CFoundry::V2
|
|
87
87
|
x
|
88
88
|
end
|
89
89
|
end
|
90
|
-
elsif k.to_s.end_with?("_json") && v.is_a?(String)
|
91
|
-
payload[k] = MultiJson.load(v)
|
92
90
|
elsif k.to_s.end_with?("_url")
|
93
91
|
else
|
94
92
|
payload[k] = v
|
@@ -63,7 +63,7 @@ module CFoundry::V2
|
|
63
63
|
end
|
64
64
|
|
65
65
|
define_method(:"create_#{singular}") do |payload|
|
66
|
-
post(
|
66
|
+
post("v2", plural, :content => :json, :accept => :json, :payload => payload)
|
67
67
|
end
|
68
68
|
|
69
69
|
define_method(:"delete_#{singular}") do |guid|
|
@@ -72,7 +72,7 @@ module CFoundry::V2
|
|
72
72
|
end
|
73
73
|
|
74
74
|
define_method(:"update_#{singular}") do |guid, payload|
|
75
|
-
put(
|
75
|
+
put("v2", plural, guid, :content => :json, :accept => :json, :payload => payload)
|
76
76
|
end
|
77
77
|
|
78
78
|
define_method(plural) do |*args|
|
@@ -118,8 +118,7 @@ module CFoundry::V2
|
|
118
118
|
define_method(:"#{singular}_from") do |path, *args|
|
119
119
|
send(
|
120
120
|
:"make_#{singular}",
|
121
|
-
@base.
|
122
|
-
Net::HTTP::Get,
|
121
|
+
@base.get(
|
123
122
|
path,
|
124
123
|
:accept => :json,
|
125
124
|
:params => ModelMagic.params_from(args)))
|
@@ -127,8 +126,7 @@ module CFoundry::V2
|
|
127
126
|
|
128
127
|
define_method(:"#{plural}_from") do |path, *args|
|
129
128
|
objs = @base.all_pages(
|
130
|
-
@base.
|
131
|
-
Net::HTTP::Get,
|
129
|
+
@base.get(
|
132
130
|
path,
|
133
131
|
:accept => :json,
|
134
132
|
:params => ModelMagic.params_from(args)))
|
@@ -151,6 +149,7 @@ module CFoundry::V2
|
|
151
149
|
|
152
150
|
def attribute(name, type, opts = {})
|
153
151
|
attributes[name] = opts
|
152
|
+
json_name = opts[:at] || name
|
154
153
|
|
155
154
|
default = opts[:default]
|
156
155
|
|
@@ -162,8 +161,8 @@ module CFoundry::V2
|
|
162
161
|
return @cache[name] if @cache.key?(name)
|
163
162
|
|
164
163
|
@cache[name] =
|
165
|
-
if manifest[:entity].key?(
|
166
|
-
manifest[:entity][
|
164
|
+
if manifest[:entity].key?(json_name)
|
165
|
+
manifest[:entity][json_name]
|
167
166
|
else
|
168
167
|
default
|
169
168
|
end
|
@@ -179,11 +178,11 @@ module CFoundry::V2
|
|
179
178
|
@manifest ||= {}
|
180
179
|
@manifest[:entity] ||= {}
|
181
180
|
|
182
|
-
old = @manifest[:entity][
|
181
|
+
old = @manifest[:entity][json_name]
|
183
182
|
@changes[name] = [old, val] if old != val
|
184
|
-
@manifest[:entity][
|
183
|
+
@manifest[:entity][json_name] = val
|
185
184
|
|
186
|
-
@diff[
|
185
|
+
@diff[json_name] = val
|
187
186
|
end
|
188
187
|
end
|
189
188
|
|
@@ -314,10 +313,7 @@ module CFoundry::V2
|
|
314
313
|
cache << x unless cache.include?(x)
|
315
314
|
end
|
316
315
|
|
317
|
-
@client.base.
|
318
|
-
Net::HTTP::Put,
|
319
|
-
["v2", plural_object_name, @guid, plural, x.guid],
|
320
|
-
:accept => :json)
|
316
|
+
@client.base.put("v2", plural_object_name, @guid, plural, x.guid, :accept => :json)
|
321
317
|
end
|
322
318
|
|
323
319
|
define_method(:"remove_#{singular}") do |x|
|
@@ -329,10 +325,7 @@ module CFoundry::V2
|
|
329
325
|
cache.delete(x)
|
330
326
|
end
|
331
327
|
|
332
|
-
@client.base.
|
333
|
-
Net::HTTP::Delete,
|
334
|
-
["v2", plural_object_name, @guid, plural, x.guid],
|
335
|
-
:accept => :json)
|
328
|
+
@client.base.delete("v2", plural_object_name, @guid, plural, x.guid, :accept => :json)
|
336
329
|
end
|
337
330
|
|
338
331
|
define_method(:"#{plural}=") do |xs|
|
@@ -367,10 +360,7 @@ module CFoundry::V2
|
|
367
360
|
|
368
361
|
def has_summary(actions = {})
|
369
362
|
define_method(:summary) do
|
370
|
-
@client.base.
|
371
|
-
Net::HTTP::Get,
|
372
|
-
["v2", plural_object_name, @guid, "summary"],
|
373
|
-
:accept => :json)
|
363
|
+
@client.base.get("v2", plural_object_name, @guid, "summary", :accept => :json)
|
374
364
|
end
|
375
365
|
|
376
366
|
define_method(:summarize!) do |*args|
|
data/lib/cfoundry/version.rb
CHANGED
data/lib/cfoundry/zip.rb
CHANGED
@@ -0,0 +1,77 @@
|
|
1
|
+
require "base64"
|
2
|
+
require "spec_helper"
|
3
|
+
|
4
|
+
describe CFoundry::AuthToken do
|
5
|
+
describe ".from_uaa_token_info" do
|
6
|
+
let(:access_token) { Base64.encode64('{"algo": "h1234"}{"user_id": "a6", "email": "a@b.com"}random-bytes') }
|
7
|
+
let(:info_hash) do
|
8
|
+
{
|
9
|
+
:token_type => "bearer",
|
10
|
+
:access_token => access_token,
|
11
|
+
:refresh_token => "some-refresh-token"
|
12
|
+
}
|
13
|
+
end
|
14
|
+
|
15
|
+
|
16
|
+
let(:token_info) { CF::UAA::TokenInfo.new(info_hash) }
|
17
|
+
|
18
|
+
subject { CFoundry::AuthToken.from_uaa_token_info(token_info) }
|
19
|
+
|
20
|
+
describe "#auth_header" do
|
21
|
+
its(:auth_header) { should eq "bearer #{access_token}" }
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "#to_hash" do
|
25
|
+
let(:result_hash) do
|
26
|
+
{
|
27
|
+
:token => "bearer #{access_token}",
|
28
|
+
:refresh_token => "some-refresh-token"
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
its(:to_hash) { should eq result_hash }
|
33
|
+
end
|
34
|
+
|
35
|
+
describe "#token_data" do
|
36
|
+
context "when the access token is encoded as expected" do
|
37
|
+
its(:token_data) { should eq({ :user_id => "a6", :email => "a@b.com"}) }
|
38
|
+
end
|
39
|
+
|
40
|
+
context "when the access token is not encoded as expected" do
|
41
|
+
let(:access_token) { Base64.encode64('random-bytes') }
|
42
|
+
its(:token_data) { should eq({}) }
|
43
|
+
end
|
44
|
+
|
45
|
+
context "when the access token contains invalid json" do
|
46
|
+
let(:access_token) { Base64.encode64('{"algo": "h1234"}{"user_id", "a6", "email": "a@b.com"}random-bytes') }
|
47
|
+
its(:token_data) { should eq({}) }
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe ".from_hash(hash)" do
|
53
|
+
let(:token_data) { '{"baz":"buzz"}' }
|
54
|
+
let(:token) { Base64.encode64("{\"foo\":1}#{token_data}") }
|
55
|
+
|
56
|
+
let(:hash) do
|
57
|
+
{
|
58
|
+
:token => "bearer #{token}",
|
59
|
+
:refresh_token => "some-refresh-token"
|
60
|
+
}
|
61
|
+
end
|
62
|
+
|
63
|
+
subject { CFoundry::AuthToken.from_hash(hash) }
|
64
|
+
|
65
|
+
describe "#auth_header" do
|
66
|
+
its(:auth_header) { should eq("bearer #{token}") }
|
67
|
+
end
|
68
|
+
|
69
|
+
describe "#to_hash" do
|
70
|
+
its(:to_hash) { should eq(hash) }
|
71
|
+
end
|
72
|
+
|
73
|
+
describe "#token_data" do
|
74
|
+
its(:token_data) { should eq({ :baz => "buzz" }) }
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -1,51 +1,75 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe CFoundry::BaseClient do
|
4
|
-
|
4
|
+
subject { CFoundry::BaseClient.new }
|
5
5
|
|
6
|
-
describe
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
before { stub_request(:get, 'https://api.cloudfoundry.com/foo').to_raise(::Timeout::Error) }
|
6
|
+
describe "#request" do
|
7
|
+
before do
|
8
|
+
stub(subject).handle_response(anything, anything, anything)
|
9
|
+
end
|
11
10
|
|
12
|
-
|
13
|
-
|
11
|
+
context "when given multiple segments" do
|
12
|
+
it "encodes the segments and joins them with '/'" do
|
13
|
+
mock(subject).request_raw("GET", "foo/bar%2Fbaz", {})
|
14
|
+
subject.request("GET", "foo", "bar/baz")
|
14
15
|
end
|
15
16
|
end
|
16
17
|
|
17
|
-
context
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
18
|
+
context "when the first segment starts with a '/'" do
|
19
|
+
context "and there's only one segment" do
|
20
|
+
it "requests with the segment unaltered" do
|
21
|
+
mock(subject).request_raw("GET", "/v2/apps", {})
|
22
|
+
subject.request("GET", "/v2/apps")
|
23
|
+
end
|
24
|
+
end
|
23
25
|
|
24
|
-
|
25
|
-
|
26
|
+
context "and there's more than one segment" do
|
27
|
+
it "encodes the segments and joins them with '/'" do
|
28
|
+
mock(subject).request_raw("GET", "%2Ffoo/bar%2Fbaz", {})
|
29
|
+
subject.request("GET", "/foo", "bar/baz")
|
30
|
+
end
|
26
31
|
end
|
27
32
|
end
|
33
|
+
end
|
28
34
|
|
35
|
+
describe "UAAClient" do
|
36
|
+
before do
|
37
|
+
stub(subject).info { { :authorization_endpoint => "http://uaa.example.com" } }
|
38
|
+
end
|
29
39
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
40
|
+
describe "#uaa" do
|
41
|
+
it "creates a UAAClient on the first call" do
|
42
|
+
expect(subject.uaa).to be_a CFoundry::UAAClient
|
43
|
+
end
|
44
|
+
|
45
|
+
it "returns the same object on later calls" do
|
46
|
+
uaa = subject.uaa
|
47
|
+
expect(subject.uaa).to eq uaa
|
48
|
+
end
|
35
49
|
|
36
|
-
it
|
37
|
-
|
50
|
+
it "has the same AuthToken as BaseClient" do
|
51
|
+
token = CFoundry::AuthToken.new(nil)
|
52
|
+
stub(subject).token { token }
|
53
|
+
expect(subject.uaa.token).to eq token
|
38
54
|
end
|
39
55
|
end
|
40
56
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
57
|
+
describe "#token=" do
|
58
|
+
it "propagates the change to #uaa" do
|
59
|
+
subject.uaa
|
60
|
+
expect(subject.uaa.token).to eq subject.token
|
61
|
+
subject.token = CFoundry::AuthToken.new(nil)
|
62
|
+
expect(subject.uaa.token).to eq subject.token
|
63
|
+
end
|
64
|
+
end
|
46
65
|
|
47
|
-
|
48
|
-
|
66
|
+
describe "#trace=" do
|
67
|
+
it "propagates the change to #uaa" do
|
68
|
+
subject.uaa
|
69
|
+
subject.trace = true
|
70
|
+
expect(subject.uaa.trace).to eq true
|
71
|
+
subject.trace = false
|
72
|
+
expect(subject.uaa.trace).to eq false
|
49
73
|
end
|
50
74
|
end
|
51
75
|
end
|
@@ -4,23 +4,20 @@ describe 'Errors' do
|
|
4
4
|
describe CFoundry::Timeout do
|
5
5
|
let(:parent) { Timeout::Error.new }
|
6
6
|
|
7
|
-
subject { CFoundry::Timeout.new(
|
7
|
+
subject { CFoundry::Timeout.new("POST", '/blah', parent) }
|
8
8
|
|
9
9
|
its(:to_s) { should eq "POST /blah timed out" }
|
10
|
-
its(:method) { should eq
|
10
|
+
its(:method) { should eq "POST" }
|
11
11
|
its(:uri) { should eq '/blah' }
|
12
12
|
its(:parent) { should eq parent }
|
13
13
|
end
|
14
14
|
|
15
15
|
describe CFoundry::APIError do
|
16
|
-
let(:request) {
|
17
|
-
let(:response) { Net::HTTPNotFound.new("foo", 404, "bar")}
|
16
|
+
let(:request) { { :method => "GET", :url => "http://api.cloudfoundry.com/foo", :headers => {} } }
|
18
17
|
let(:response_body) { "NOT FOUND" }
|
19
|
-
|
18
|
+
let(:response) { { :status => 404, :headers => {}, :body => response_body } }
|
20
19
|
|
21
|
-
|
22
|
-
stub(response).body {response_body}
|
23
|
-
end
|
20
|
+
subject { CFoundry::APIError.new(nil, nil, request, response) }
|
24
21
|
|
25
22
|
its(:to_s) { should eq "404: NOT FOUND" }
|
26
23
|
|
@@ -35,7 +32,7 @@ describe 'Errors' do
|
|
35
32
|
let(:response_body) { "{\"description\":\"Something went wrong\"}"}
|
36
33
|
|
37
34
|
it "sets description to description field in parsed JSON" do
|
38
|
-
CFoundry::APIError.new(request, response).description.should == "Something went wrong"
|
35
|
+
CFoundry::APIError.new(nil, nil, request, response).description.should == "Something went wrong"
|
39
36
|
end
|
40
37
|
end
|
41
38
|
|
@@ -45,12 +42,12 @@ describe 'Errors' do
|
|
45
42
|
let(:response_body) { "Some plain text"}
|
46
43
|
|
47
44
|
it "sets description to body text" do
|
48
|
-
CFoundry::APIError.new(request, response).description.should == "Some plain text"
|
45
|
+
CFoundry::APIError.new(nil, nil, request, response).description.should == "Some plain text"
|
49
46
|
end
|
50
47
|
end
|
51
48
|
|
52
49
|
it "allows override of description" do
|
53
|
-
CFoundry::APIError.new(
|
50
|
+
CFoundry::APIError.new("My description", nil, request, response).description.should == "My description"
|
54
51
|
end
|
55
52
|
|
56
53
|
end
|
@@ -64,11 +61,11 @@ describe 'Errors' do
|
|
64
61
|
end
|
65
62
|
|
66
63
|
it "sets error code to response error code by default" do
|
67
|
-
CFoundry::APIError.new(request, response).error_code.should == 404
|
64
|
+
CFoundry::APIError.new(nil, nil, request, response).error_code.should == 404
|
68
65
|
end
|
69
66
|
|
70
67
|
it "allows override of error code" do
|
71
|
-
CFoundry::APIError.new(
|
68
|
+
CFoundry::APIError.new(nil, 303, request, response).error_code.should == 303
|
72
69
|
end
|
73
70
|
|
74
71
|
end
|