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.
Files changed (50) hide show
  1. data/Rakefile +47 -24
  2. data/lib/cfoundry/auth_token.rb +48 -0
  3. data/lib/cfoundry/baseclient.rb +96 -277
  4. data/lib/cfoundry/client.rb +2 -0
  5. data/lib/cfoundry/concerns/login_helpers.rb +13 -0
  6. data/lib/cfoundry/errors.rb +21 -13
  7. data/lib/cfoundry/rest_client.rb +290 -0
  8. data/lib/cfoundry/test_support.rb +3 -0
  9. data/lib/cfoundry/trace_helpers.rb +9 -9
  10. data/lib/cfoundry/uaaclient.rb +66 -74
  11. data/lib/cfoundry/upload_helpers.rb +2 -0
  12. data/lib/cfoundry/v1/app.rb +2 -2
  13. data/lib/cfoundry/v1/base.rb +4 -51
  14. data/lib/cfoundry/v1/client.rb +7 -30
  15. data/lib/cfoundry/v1/model.rb +22 -5
  16. data/lib/cfoundry/v1/model_magic.rb +30 -15
  17. data/lib/cfoundry/v2/app.rb +2 -5
  18. data/lib/cfoundry/v2/base.rb +10 -74
  19. data/lib/cfoundry/v2/client.rb +19 -30
  20. data/lib/cfoundry/v2/domain.rb +1 -4
  21. data/lib/cfoundry/v2/model.rb +1 -3
  22. data/lib/cfoundry/v2/model_magic.rb +13 -23
  23. data/lib/cfoundry/version.rb +1 -1
  24. data/lib/cfoundry/zip.rb +1 -1
  25. data/spec/cfoundry/auth_token_spec.rb +77 -0
  26. data/spec/cfoundry/baseclient_spec.rb +54 -30
  27. data/spec/cfoundry/errors_spec.rb +10 -13
  28. data/spec/cfoundry/rest_client_spec.rb +238 -0
  29. data/spec/cfoundry/trace_helpers_spec.rb +10 -5
  30. data/spec/cfoundry/uaaclient_spec.rb +141 -114
  31. data/spec/cfoundry/upload_helpers_spec.rb +129 -0
  32. data/spec/cfoundry/v1/base_spec.rb +2 -2
  33. data/spec/cfoundry/v1/client_spec.rb +17 -0
  34. data/spec/cfoundry/v1/model_magic_spec.rb +43 -0
  35. data/spec/cfoundry/v2/base_spec.rb +256 -33
  36. data/spec/cfoundry/v2/client_spec.rb +68 -0
  37. data/spec/cfoundry/v2/model_magic_spec.rb +49 -0
  38. data/spec/fixtures/apps/with_vmcignore/ignored_dir/file_in_ignored_dir.txt +1 -0
  39. data/spec/fixtures/apps/with_vmcignore/ignored_file.txt +1 -0
  40. data/spec/fixtures/apps/with_vmcignore/non_ignored_dir/file_in_non_ignored_dir.txt +1 -0
  41. data/spec/fixtures/apps/with_vmcignore/non_ignored_dir/ignored_file.txt +1 -0
  42. data/spec/fixtures/apps/with_vmcignore/non_ignored_file.txt +1 -0
  43. data/spec/fixtures/empty_file +0 -0
  44. data/spec/spec_helper.rb +4 -4
  45. data/spec/support/randoms.rb +3 -0
  46. data/spec/support/shared_examples/client_login_examples.rb +46 -0
  47. data/spec/support/{summaries.rb → shared_examples/model_summary_examples.rb} +0 -0
  48. data/spec/support/v1_fake_helper.rb +144 -0
  49. metadata +101 -37
  50. data/lib/cfoundry/spec_helper.rb +0 -1
@@ -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
- if guid = @base.token_data[:user_id]
85
+ token_data = @base.token.token_data
86
+ if guid = token_data[:user_id]
80
87
  user = user(guid)
81
- user.emails = [{ :value => @base.token_data[:email] }]
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
- # Login prompts
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
- @base.token =
114
- if @base.uaa
115
- @base.uaa.authorize(credentials)
116
- else
117
- @base.create_token(
118
- { :password => credentials[:password] },
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.
@@ -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, :default => true
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
@@ -72,7 +72,7 @@ module CFoundry::V2
72
72
  @manifest ||= {}
73
73
  @manifest[:entity] ||= {}
74
74
 
75
- self.class.defaults.merge(@manifest[:entity]).each do |k, v|
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(payload, "v2", plural, :content => :json, :accept => :json)
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(payload, "v2", plural, guid, :content => :json, :accept => :json)
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.request_path(
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.request_path(
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?(name)
166
- manifest[:entity][name]
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][name]
181
+ old = @manifest[:entity][json_name]
183
182
  @changes[name] = [old, val] if old != val
184
- @manifest[:entity][name] = val
183
+ @manifest[:entity][json_name] = val
185
184
 
186
- @diff[name] = val
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.request_path(
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.request_path(
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.request_path(
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|
@@ -1,4 +1,4 @@
1
1
  module CFoundry # :nodoc:
2
2
  # CFoundry library version number.
3
- VERSION = "0.4.21".freeze
3
+ VERSION = "0.5.0".freeze
4
4
  end
data/lib/cfoundry/zip.rb CHANGED
@@ -5,7 +5,7 @@ module CFoundry
5
5
  # to use system zip command if necessary.
6
6
  module Zip
7
7
  # Directory entries to exclude from packing.
8
- PACK_EXCLUSION_GLOBS = ['..', '.', '*~', '#*#', '*.log']
8
+ PACK_EXCLUSION_GLOBS = %w(.. . *~ #*# *.log)
9
9
 
10
10
  module_function
11
11
 
@@ -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
- let(:base) { CFoundry::BaseClient.new("https://api.cloudfoundry.com") }
4
+ subject { CFoundry::BaseClient.new }
5
5
 
6
- describe '#request_uri' do
7
- subject { base.request_uri URI.parse(base.target + "/foo"), Net::HTTP::Get }
8
-
9
- context 'when a timeout exception occurs' do
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
- it 'raises the correct error' do
13
- expect { subject }.to raise_error CFoundry::Timeout, "GET https://api.cloudfoundry.com/foo timed out"
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 'when an HTTPNotFound error occurs' do
18
- before {
19
-
20
- stub_request(:get, 'https://api.cloudfoundry.com/foo').to_return :status => 404,
21
- :body => "NOT FOUND"
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
- it 'raises the correct error' do
25
- expect {subject}.to raise_error CFoundry::NotFound, "404: NOT FOUND"
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
- context 'when an HTTPForbidden error occurs' do
31
- before {
32
- stub_request(:get, 'https://api.cloudfoundry.com/foo').to_return :status => 403,
33
- :body => "NONE SHALL PASS"
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 'raises the correct error' do
37
- expect {subject}.to raise_error CFoundry::Denied, "403: NONE SHALL PASS"
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
- context "when any other type of error occurs" do
42
- before {
43
- stub_request(:get, 'https://api.cloudfoundry.com/foo').to_return :status => 411,
44
- :body => "NOT LONG ENOUGH"
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
- it 'raises the correct error' do
48
- expect {subject}.to raise_error CFoundry::BadResponse, "411: NOT LONG ENOUGH"
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(Net::HTTP::Post, '/blah', parent) }
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 Net::HTTP::Post }
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) { Net::HTTP::Get.new("http://api.cloudfoundry.com/foo") }
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
- subject { CFoundry::APIError.new(request, response) }
18
+ let(:response) { { :status => 404, :headers => {}, :body => response_body } }
20
19
 
21
- before do
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(request, response, "My description").description.should == "My description"
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(request, response, nil, 303).error_code.should == 303
68
+ CFoundry::APIError.new(nil, 303, request, response).error_code.should == 303
72
69
  end
73
70
 
74
71
  end