cfoundry 0.4.21 → 0.5.0
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.
- 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
|