cfoundry 0.5.0 → 0.5.1.rc1

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 CHANGED
@@ -36,29 +36,3 @@ namespace :deploy do
36
36
  sh "git push origin latest-release"
37
37
  end
38
38
  end
39
-
40
- namespace :release do
41
- DEPENDENTS = %w[
42
- vmc/vmc.gemspec
43
- vmc-plugins/admin/admin-vmc-plugin.gemspec
44
- vmc-plugins/console/console-vmc-plugin.gemspec
45
- vmc-plugins/manifests/manifests-vmc-plugin.gemspec
46
- vmc-plugins/mcf/mcf-vmc-plugin.gemspec
47
- vmc-plugins/tunnel/tunnel-vmc-plugin.gemspec
48
- ].freeze
49
-
50
- def bump_dependent(file, dep, ver)
51
- puts "Bumping #{dep} to #{ver} in #{file}"
52
-
53
- old = File.read(file)
54
- new = old.sub(/(\.add.+#{dep}\D+)[^'"]+(.+)/, "\\1#{ver}\\2")
55
-
56
- File.open(file, "w") { |io| io.print new }
57
- end
58
-
59
- task :bump_dependents do
60
- DEPENDENTS.each do |dep|
61
- bump_dependent(File.expand_path("../../#{dep}", __FILE__), "cfoundry", CFoundry::VERSION)
62
- end
63
- end
64
- end
@@ -21,7 +21,8 @@ module CFoundry
21
21
  @refresh_token = refresh_token
22
22
  end
23
23
 
24
- attr_reader :auth_header
24
+ attr_accessor :auth_header
25
+ attr_reader :refresh_token
25
26
 
26
27
  def to_hash
27
28
  {
@@ -35,6 +36,7 @@ module CFoundry
35
36
  # TODO: rename to #data
36
37
  def token_data
37
38
  return @token_data if @token_data
39
+ return {} unless @auth_header
38
40
 
39
41
  json_hashes = Base64.decode64(@auth_header.split(" ", 2).last)
40
42
  data_json = json_hashes.sub(JSON_HASH, "")[JSON_HASH]
@@ -44,5 +46,18 @@ module CFoundry
44
46
  rescue MultiJson::DecodeError
45
47
  {}
46
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
47
62
  end
48
63
  end
@@ -24,14 +24,21 @@ module CFoundry
24
24
 
25
25
  def uaa
26
26
  @uaa ||= begin
27
- endpoint = info[:authorization_endpoint]
28
- uaa = CFoundry::UAAClient.new(endpoint)
29
- uaa.trace = trace
30
- uaa.token = token
31
- uaa
27
+ if(endpoint = info[:authorization_endpoint])
28
+ uaa = CFoundry::UAAClient.new(endpoint)
29
+ uaa.trace = trace
30
+ uaa.token = token
31
+ uaa
32
+ else
33
+ nil
34
+ end
32
35
  end
33
36
  end
34
37
 
38
+ def password_score(password)
39
+ uaa ? uaa.password_score(password) : :unknown
40
+ end
41
+
35
42
  def token=(token)
36
43
  if token.is_a?(String)
37
44
  token = CFoundry::AuthToken.new(token)
@@ -68,6 +75,11 @@ module CFoundry
68
75
  end
69
76
 
70
77
  def request(method, *args)
78
+ if needs_token_refresh?
79
+ token.auth_header = nil
80
+ refresh_token!
81
+ end
82
+
71
83
  path, options = normalize_arguments(args)
72
84
  request, response = request_raw(method, path, options)
73
85
  handle_response(response, options, request)
@@ -77,8 +89,17 @@ module CFoundry
77
89
  @rest_client.request(method, path, options)
78
90
  end
79
91
 
92
+ def refresh_token!
93
+ self.token = uaa.try_to_refresh_token!
94
+ end
95
+
80
96
  private
81
97
 
98
+ def needs_token_refresh?
99
+ token && token.auth_header && token.refresh_token && \
100
+ token.expires_soon?
101
+ end
102
+
82
103
  def status_is_successful?(code)
83
104
  (code >= 200) && (code < 400)
84
105
  end
@@ -6,6 +6,7 @@ require "cfoundry/v1/app"
6
6
  require "cfoundry/v1/framework"
7
7
  require "cfoundry/v1/runtime"
8
8
  require "cfoundry/v1/service"
9
+ require "cfoundry/v1/service_plan"
9
10
  require "cfoundry/v1/service_instance"
10
11
  require "cfoundry/v1/user"
11
12
  require "cfoundry/v1/base"
@@ -41,9 +42,5 @@ module CFoundry
41
42
  CFoundry::V1::Client.new(*args)
42
43
  end
43
44
  end
44
-
45
- def info
46
- get("info", :accept => :json)
47
- end
48
45
  end
49
46
  end
@@ -151,6 +151,9 @@ module CFoundry
151
151
  end
152
152
 
153
153
  def construct_url(path)
154
+ uri = URI.parse(path)
155
+ return path if uri.scheme
156
+
154
157
  path = "/#{path}" unless path[0] == ?\/
155
158
  target + path
156
159
  end
@@ -2,7 +2,7 @@ require "cfoundry/baseclient"
2
2
  require 'uaa'
3
3
 
4
4
  module CFoundry
5
- class UAAClient < BaseClient
5
+ class UAAClient
6
6
  attr_accessor :target, :client_id, :token, :trace
7
7
 
8
8
  def initialize(target = "https://uaa.cloudfoundry.com", client_id = "vmc")
@@ -20,11 +20,12 @@ module CFoundry
20
20
  def authorize(username, password)
21
21
  wrap_uaa_errors do
22
22
  begin
23
- creds = { :username => username, :password => password }
24
- token_issuer.implicit_grant_with_creds(creds)
23
+ token_issuer.owner_password_grant(username, password)
25
24
  rescue CF::UAA::BadResponse => e
26
25
  status_code = e.message[/\d+/] || 400
27
26
  raise CFoundry::Denied.new("Authorization failed", status_code)
27
+ rescue CF::UAA::TargetError
28
+ token_issuer.implicit_grant_with_creds(:username => username, :password => password)
28
29
  end
29
30
  end
30
31
  end
@@ -70,18 +71,29 @@ module CFoundry
70
71
  end
71
72
  end
72
73
 
74
+ def try_to_refresh_token!
75
+ wrap_uaa_errors do
76
+ begin
77
+ token_info = token_issuer.refresh_token_grant(token.refresh_token)
78
+ self.token = AuthToken.from_uaa_token_info(token_info)
79
+ rescue CF::UAA::TargetError
80
+ self.token
81
+ end
82
+ end
83
+ end
84
+
73
85
  private
74
86
 
75
87
  def token_issuer
76
88
  @token_issuer ||= CF::UAA::TokenIssuer.new(target, client_id, nil, :symbolize_keys => true)
77
- @token_issuer.logger.level = @trace ? 0 : 1
89
+ @token_issuer.logger.level = @trace ? Logger::Severity::TRACE : 1
78
90
  @token_issuer
79
91
  end
80
92
 
81
93
  def scim
82
94
  auth_header = token && token.auth_header
83
95
  scim = CF::UAA::Scim.new(target, auth_header)
84
- scim.logger.level = @trace ? 0 : 1
96
+ scim.logger.level = @trace ? Logger::Severity::TRACE : 1
85
97
  scim
86
98
  end
87
99
 
@@ -94,7 +106,7 @@ module CFoundry
94
106
  rescue CF::UAA::InvalidToken
95
107
  raise CFoundry::Denied
96
108
  rescue CF::UAA::TargetError => e
97
- raise CFoundry::UAAError.new(e.info["error_description"], e.info["error"])
109
+ raise CFoundry::UAAError.new(e.info[:error_description], e.info[:error])
98
110
  end
99
111
  end
100
112
  end
@@ -135,9 +135,33 @@ module CFoundry
135
135
  resource[:fn].sub!("#{path}/", "")
136
136
  end
137
137
 
138
+ prune_empty_directories(path)
139
+
138
140
  resources
139
141
  end
140
142
 
143
+ # OK, HERES THE PLAN...
144
+ #
145
+ # 1. Get all the directories in the entire file tree.
146
+ # 2. Sort them by the length of their absolute path.
147
+ # 3. Go through the list, longest paths first, and remove
148
+ # the directories that are empty.
149
+ #
150
+ # This ensures that directories containing empty directories
151
+ # are also pruned.
152
+ def prune_empty_directories(path)
153
+ all_files = Dir["#{path}/**/{*,.*}"]
154
+ all_files.reject! { |fn| fn =~ /\/\.+$/ }
155
+
156
+ directories = all_files.select { |x| File.directory?(x) }
157
+ directories.sort! { |a, b| b.size <=> a.size }
158
+
159
+ directories.each do |directory|
160
+ entries = Dir.entries(directory) - %w{. ..}
161
+ FileUtils.rmdir(directory) if entries.empty?
162
+ end
163
+ end
164
+
141
165
  def make_fingerprints(path)
142
166
  fingerprints = []
143
167
  total_size = 0
@@ -49,7 +49,7 @@ module CFoundry::V1
49
49
  post("resources", :content => :json, :accept => :json, :payload => fingerprints)
50
50
  end
51
51
 
52
- def upload_app(name, zipfile, resources = [])
52
+ def upload_app(name, zipfile = nil, resources = [])
53
53
  payload = {
54
54
  :_method => "put",
55
55
  :resources => MultiJson.dump(resources),
@@ -106,7 +106,8 @@ module CFoundry::V1
106
106
 
107
107
  services <<
108
108
  Service.new(vendor.to_s, ver.to_s, meta[:description],
109
- type.to_s, provider.to_s, state && state.first)
109
+ type.to_s, provider.to_s, state && state.first,
110
+ generate_plans(meta))
110
111
  end
111
112
  end
112
113
  end
@@ -116,6 +117,17 @@ module CFoundry::V1
116
117
  services
117
118
  end
118
119
 
120
+ def generate_plans(meta)
121
+ names = meta[:plans]
122
+ descriptions = meta[:plan_descriptions]
123
+ default_name = meta[:default_plan]
124
+ names.map { |name|
125
+ description = descriptions[name] if descriptions
126
+ is_default = name == default_name || names.length == 1
127
+ ServicePlan.new(name, description, is_default)
128
+ }
129
+ end
130
+
119
131
  # Retrieve available runtimes.
120
132
  def runtimes(options = {})
121
133
  runtimes = []
@@ -1,15 +1,17 @@
1
1
  module CFoundry::V1
2
2
  class Service
3
- attr_accessor :label, :version, :description, :type, :provider, :state
3
+ attr_accessor :label, :version, :description, :type, :provider, :state, :service_plans
4
4
 
5
5
  def initialize(label, version = nil, description = nil,
6
- type = nil, provider = "core", state = nil)
6
+ type = nil, provider = "core", state = nil,
7
+ service_plans = [])
7
8
  @label = label
8
9
  @description = description
9
10
  @version = version
10
11
  @type = type
11
12
  @provider = provider
12
13
  @state = state
14
+ @service_plans = service_plans
13
15
  end
14
16
 
15
17
  def eql?(other)
@@ -28,5 +30,10 @@ module CFoundry::V1
28
30
  def current?
29
31
  @state == :current
30
32
  end
33
+
34
+ def default_service_plan
35
+ service_plans.find(&:default?)
36
+ end
37
+
31
38
  end
32
39
  end
@@ -0,0 +1,19 @@
1
+ module CFoundry::V1
2
+
3
+ class ServicePlan
4
+
5
+ attr_accessor :name, :description
6
+
7
+ def initialize(name, description, is_default)
8
+ @name = name
9
+ @description = description
10
+ @is_default = is_default
11
+ end
12
+
13
+ def default?
14
+ @is_default
15
+ end
16
+
17
+ end
18
+
19
+ end
@@ -43,6 +43,10 @@ module CFoundry::V2
43
43
  self.cache[:running_instances] = x
44
44
  },
45
45
 
46
+ :instances => proc { |x|
47
+ self.total_instances = x
48
+ },
49
+
46
50
  # TODO: remove these when cc consistently returns nested hashes
47
51
  :framework_guid => proc { |x|
48
52
  if f = self.cache[:framework]
@@ -104,13 +108,13 @@ module CFoundry::V2
104
108
  end
105
109
 
106
110
  def env
107
- @env ||= CFoundry::ChattyHash.new(
111
+ CFoundry::ChattyHash.new(
108
112
  method(:env=),
109
- environment_json)
113
+ stringify(environment_json))
110
114
  end
111
115
 
112
116
  def env=(x)
113
- self.environment_json = x.to_hash
117
+ self.environment_json = stringify(x.to_hash)
114
118
  end
115
119
 
116
120
  def debug_mode # TODO v2
@@ -296,6 +300,18 @@ module CFoundry::V2
296
300
  Instance.new(self, "0", @client).stream_file(*path, &blk)
297
301
  end
298
302
 
303
+ private
304
+
305
+ def stringify(hash)
306
+ new = {}
307
+
308
+ hash.each do |k, v|
309
+ new[k.to_s] = v.to_s
310
+ end
311
+
312
+ new
313
+ end
314
+
299
315
  class Instance
300
316
  attr_reader :app, :id
301
317
 
@@ -13,10 +13,12 @@ module CFoundry::V2
13
13
  put("v2", "resource_match", :content => :json, :accept => :json, :payload => fingerprints)
14
14
  end
15
15
 
16
- def upload_app(guid, zipfile, resources = [])
17
- payload = {
18
- :resources => MultiJson.dump(resources),
19
- :application =>
16
+ def upload_app(guid, zipfile = nil, resources = [])
17
+ payload = {}
18
+ payload[:resources] = MultiJson.dump(resources)
19
+
20
+ if zipfile
21
+ payload[:application] =
20
22
  UploadIO.new(
21
23
  if zipfile.is_a? File
22
24
  zipfile
@@ -24,7 +26,7 @@ module CFoundry::V2
24
26
  File.new(zipfile, "rb")
25
27
  end,
26
28
  "application/zip")
27
- }
29
+ end
28
30
 
29
31
  put("v2", "apps", guid, "bits", :payload => payload)
30
32
  rescue EOFError
@@ -82,6 +82,8 @@ module CFoundry::V2
82
82
 
83
83
  # The currently authenticated user.
84
84
  def current_user
85
+ return unless token
86
+
85
87
  token_data = @base.token.token_data
86
88
  if guid = token_data[:user_id]
87
89
  user = user(guid)
@@ -1,4 +1,4 @@
1
1
  module CFoundry # :nodoc:
2
2
  # CFoundry library version number.
3
- VERSION = "0.5.0".freeze
3
+ VERSION = "0.5.1.rc1".freeze
4
4
  end
@@ -46,6 +46,14 @@ describe CFoundry::AuthToken do
46
46
  let(:access_token) { Base64.encode64('{"algo": "h1234"}{"user_id", "a6", "email": "a@b.com"}random-bytes') }
47
47
  its(:token_data) { should eq({}) }
48
48
  end
49
+
50
+ context "when the auth header is nil" do
51
+ before do
52
+ subject.auth_header = nil
53
+ end
54
+
55
+ its(:token_data) { should eq({}) }
56
+ end
49
57
  end
50
58
  end
51
59
 
@@ -74,4 +82,44 @@ describe CFoundry::AuthToken do
74
82
  its(:token_data) { should eq({ :baz => "buzz" }) }
75
83
  end
76
84
  end
77
- end
85
+
86
+ describe "#auth_header=" do
87
+ let(:access_token) { Base64.encode64('{"algo": "h1234"}{"user_id": "a6", "email": "a@b.com"}random-bytes') }
88
+ let(:other_access_token) { Base64.encode64('{"algo": "h1234"}{"user_id": "b6", "email": "a@b.com"}random-bytes') }
89
+
90
+ subject { CFoundry::AuthToken.new("bearer #{access_token}") }
91
+
92
+ it "invalidates @token_data" do
93
+ subject.token_data
94
+ expect {
95
+ subject.auth_header = "bearer #{other_access_token}"
96
+ }.to change { subject.token_data[:user_id] }.from("a6").to("b6")
97
+ end
98
+ end
99
+
100
+ describe "#expires_soon?" do
101
+ let(:access_token) { Base64.encode64(%Q|{"algo": "h1234"}{"exp":#{expiration.to_i}}random-bytes|) }
102
+
103
+ subject { CFoundry::AuthToken.new("bearer #{access_token}") }
104
+
105
+ context "when the token expires in less than 1 minute" do
106
+ let(:expiration) { Time.now + 59 }
107
+
108
+ it "returns true" do
109
+ Timecop.freeze do
110
+ expect(subject.expires_soon?).to be_true
111
+ end
112
+ end
113
+ end
114
+
115
+ context "when the token expires in greater than or equal to 1 minute" do
116
+ let(:expiration) { Time.now + 60 }
117
+
118
+ it "returns false" do
119
+ Timecop.freeze do
120
+ expect(subject.expires_soon?).to be_false
121
+ end
122
+ end
123
+ end
124
+ end
125
+ end
@@ -30,46 +30,155 @@ describe CFoundry::BaseClient do
30
30
  end
31
31
  end
32
32
  end
33
+
34
+ context "when there is a token with an auth_header" do
35
+ let(:refresh_token) { nil }
36
+ let(:token) { CFoundry::AuthToken.new("bearer something", refresh_token) }
37
+
38
+ before do
39
+ stub(subject).request_raw
40
+ subject.token = token
41
+ stub(token).expires_soon? { expires_soon? }
42
+ end
43
+
44
+ context "and the token is about to expire" do
45
+ let(:uaa) { Object.new }
46
+ let(:expires_soon?) { true }
47
+
48
+ context "and there is a refresh token" do
49
+ let(:refresh_token) { "some-refresh-token" }
50
+
51
+ it "sets the token's auth header to nil to prevent recursion" do
52
+ stub(subject).refresh_token!
53
+ subject.request("GET", "foo")
54
+ end
55
+
56
+ it "refreshes the access token" do
57
+ mock(subject).refresh_token!
58
+ subject.request("GET", "foo")
59
+ end
60
+ end
61
+
62
+ context "and there is NOT a refresh token" do
63
+ let(:refresh_token) { nil }
64
+
65
+ it "moves along" do
66
+ mock(subject).request_raw(anything, anything, anything)
67
+ dont_allow(subject).refresh_token!
68
+ subject.request("GET", "foo")
69
+ end
70
+ end
71
+ end
72
+
73
+ context "and the token is NOT about to expire" do
74
+ let(:expires_soon?) { nil }
75
+
76
+ it "moves along" do
77
+ mock(subject).request_raw(anything, anything, anything)
78
+ dont_allow(subject).refresh_token!
79
+ subject.request("GET", "foo")
80
+ end
81
+ end
82
+ end
83
+
84
+ describe "#refresh_token!" do
85
+ let(:uaa) { stub }
86
+ let(:access_token) { Base64.encode64(%Q|{"algo": "h1234"}{"a":"b"}random-bytes|) }
87
+ let(:refresh_token) { "xyz" }
88
+ let(:new_access_token) { Base64.encode64(%Q|{"algo": "h1234"}{"a":"x"}random-bytes|) }
89
+ let(:auth_token) { CFoundry::AuthToken.new("bearer #{access_token}", refresh_token) }
90
+
91
+ before { stub(subject).uaa { uaa } }
92
+
93
+ it "refreshes the token with UAA client and assigns it" do
94
+ mock(uaa).try_to_refresh_token! {
95
+ CFoundry::AuthToken.new("bearer #{new_access_token}", auth_token.refresh_token)
96
+ }
97
+
98
+ subject.refresh_token!
99
+
100
+ expect(subject.token.auth_header).to eq "bearer #{new_access_token}"
101
+ end
102
+ end
103
+
33
104
  end
34
105
 
35
106
  describe "UAAClient" do
36
- before do
37
- stub(subject).info { { :authorization_endpoint => "http://uaa.example.com" } }
38
- end
107
+ context "with a valid uaa endpoint" do
108
+ before do
109
+ stub(subject).info { { :authorization_endpoint => "http://uaa.example.com" } }
110
+ end
111
+
112
+ describe "#uaa" do
113
+ it "creates a UAAClient on the first call" do
114
+ expect(subject.uaa).to be_a CFoundry::UAAClient
115
+ end
39
116
 
40
- describe "#uaa" do
41
- it "creates a UAAClient on the first call" do
42
- expect(subject.uaa).to be_a CFoundry::UAAClient
117
+ it "returns the same object on later calls" do
118
+ uaa = subject.uaa
119
+ expect(subject.uaa).to eq uaa
120
+ end
121
+
122
+ it "has the same AuthToken as BaseClient" do
123
+ token = CFoundry::AuthToken.new(nil)
124
+ stub(subject).token { token }
125
+ expect(subject.uaa.token).to eq token
126
+ end
43
127
  end
44
128
 
45
- it "returns the same object on later calls" do
46
- uaa = subject.uaa
47
- expect(subject.uaa).to eq uaa
129
+ describe "#token=" do
130
+ it "propagates the change to #uaa" do
131
+ subject.uaa
132
+ expect(subject.uaa.token).to eq subject.token
133
+ subject.token = CFoundry::AuthToken.new(nil)
134
+ expect(subject.uaa.token).to eq subject.token
135
+ end
48
136
  end
49
137
 
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
138
+ describe "#trace=" do
139
+ it "propagates the change to #uaa" do
140
+ subject.uaa
141
+ subject.trace = true
142
+ expect(subject.uaa.trace).to eq true
143
+ subject.trace = false
144
+ expect(subject.uaa.trace).to eq false
145
+ end
54
146
  end
55
147
  end
56
148
 
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
149
+ context "with no uaa endpoint" do
150
+ before do
151
+ stub(subject).info { { :something => "else" } }
63
152
  end
153
+
154
+ describe "#uaa" do
155
+ it "does not return a UAAClient" do
156
+ expect(subject.uaa).to be_nil
157
+ end
158
+ end
159
+
64
160
  end
161
+ end
162
+
163
+ describe "#password_score" do
164
+ context "with a uaa" do
165
+ before do
166
+ stub(subject).info { { :authorization_endpoint => "http://uaa.example.com" } }
167
+ end
168
+
169
+ it "delegates to the uaa's password strength method" do
170
+ mock(subject.uaa).password_score('password')
171
+ subject.password_score('password')
172
+ end
173
+ end
174
+
175
+ context "without a uaa" do
176
+ before do
177
+ stub(subject).info { { :something => "else" } }
178
+ end
65
179
 
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
180
+ it "returns :unknown" do
181
+ expect(subject.password_score('password')).to eq(:unknown)
73
182
  end
74
183
  end
75
184
  end
@@ -0,0 +1,15 @@
1
+ require 'spec_helper'
2
+
3
+ describe CFoundry::Client do
4
+ subject { CFoundry::Client.new('http://example.com') }
5
+
6
+ it "returns a v1 client when used on a v1 target" do
7
+ stub_request(:get, "http://example.com/info").to_return(:status => 200, :body => '{"version":1}')
8
+ subject.should be_a(CFoundry::V1::Client)
9
+ end
10
+
11
+ it "returns a v2 client when used on a v2 target" do
12
+ stub_request(:get, "http://example.com/info").to_return(:status => 200, :body => '{"version":2}')
13
+ subject.should be_a(CFoundry::V2::Client)
14
+ end
15
+ end
@@ -200,6 +200,24 @@ describe CFoundry::RestClient do
200
200
  end
201
201
  end
202
202
 
203
+ describe "when the path is a full url" do
204
+ let(:path) { "http://example.com" }
205
+
206
+ it "requests the given url" do
207
+ stub = stub_request(:get, "http://example.com")
208
+ subject
209
+ expect(stub).to have_been_requested
210
+ end
211
+ end
212
+
213
+ describe "when the path is malformed" do
214
+ let(:path) { "#%&*$(#%&$%)" }
215
+
216
+ it "blows up" do
217
+ expect { subject }.to raise_error(URI::InvalidURIError)
218
+ end
219
+ end
220
+
203
221
  describe 'trace' do
204
222
  before do
205
223
  rest_client.trace = true
@@ -7,6 +7,7 @@ describe CFoundry::UAAClient do
7
7
 
8
8
  before do
9
9
  uaa.token = CFoundry::AuthToken.new(auth_header)
10
+ CF::UAA::Util.default_logger.level = 1
10
11
  end
11
12
 
12
13
  shared_examples "UAA wrapper" do
@@ -61,33 +62,44 @@ EOF
61
62
  let(:state) { 'somestate' }
62
63
  let(:redirect_uri) { 'https://uaa.cloudfoundry.com/redirect/vmc' }
63
64
  let(:auth) { Object.new }
65
+ let(:issuer) { Object.new }
64
66
 
65
67
  subject { uaa.authorize(username, password) }
66
68
 
67
- before { stub(uaa).token_issuer.stub!.implicit_grant_with_creds { auth } }
69
+ before do
70
+ stub(issuer).owner_password_grant { auth }
71
+ stub(uaa).token_issuer { issuer }
72
+ end
68
73
 
69
74
  include_examples "UAA wrapper"
70
75
 
71
76
  it 'returns the token on successful authentication' do
72
- stub(uaa).token_issuer.mock!.implicit_grant_with_creds(creds) { auth }
77
+ mock(issuer).owner_password_grant(username, password) { auth }
73
78
  expect(subject).to eq auth
74
79
  end
75
80
 
76
81
  context 'when authorization fails' do
77
82
  context 'in the expected way' do
78
83
  it 'raises a CFoundry::Denied error' do
79
- stub(uaa).token_issuer.stub!.implicit_grant_with_creds { raise CF::UAA::BadResponse.new("401: FooBar") }
80
-
84
+ mock(issuer).owner_password_grant(anything, anything) { raise CF::UAA::BadResponse.new("401: FooBar") }
81
85
  expect { subject }.to raise_error(CFoundry::Denied, "401: Authorization failed")
82
86
  end
83
87
  end
84
88
 
85
89
  context 'in an unexpected way' do
86
90
  it 'raises a CFoundry::Denied error' do
87
- stub(uaa).token_issuer.stub!.implicit_grant_with_creds { raise CF::UAA::BadResponse.new("no_status_code") }
91
+ mock(issuer).owner_password_grant(anything, anything) { raise CF::UAA::BadResponse.new("no_status_code") }
88
92
  expect { subject }.to raise_error(CFoundry::Denied, "400: Authorization failed")
89
93
  end
90
94
  end
95
+
96
+ context "with a CF::UAA::TargetError" do
97
+ it "retries with implicit grant" do
98
+ stub(issuer).owner_password_grant { raise CF::UAA::TargetError.new("useless info") }
99
+ mock(issuer).implicit_grant_with_creds(:username => username, :password => password)
100
+ expect { subject }.to_not raise_error
101
+ end
102
+ end
91
103
  end
92
104
  end
93
105
 
@@ -172,7 +184,7 @@ EOF
172
184
  it { should == :good }
173
185
  end
174
186
 
175
- context 'when the score is less than the required core' do
187
+ context 'when the score is less than the required score' do
176
188
  let(:response) { MultiJson.encode "score" => 1, "requiredScore" => 5 }
177
189
  it { should == :weak }
178
190
  end
@@ -259,7 +271,7 @@ EOF
259
271
  end
260
272
 
261
273
  context "when the block raises CF::UAA::TargetError" do
262
- let(:error) { CF::UAA::TargetError.new({ "error" => "foo", "error_description" => "bar" }) }
274
+ let(:error) { CF::UAA::TargetError.new({ :error => "foo", :error_description => "bar" }) }
263
275
 
264
276
  it "raises CFoundry::UAAError" do
265
277
  expect { subject }.to raise_exception(CFoundry::UAAError, "foo: bar")
@@ -270,7 +282,7 @@ EOF
270
282
  describe "#token_issuer" do
271
283
  it "has logging level 0 if #trace is true" do
272
284
  uaa.trace = true
273
- expect(uaa.send(:token_issuer).logger.level).to eq 0
285
+ expect(uaa.send(:token_issuer).logger.level).to eq -1
274
286
  end
275
287
 
276
288
  it "has logging level 1 if #trace is false" do
@@ -282,7 +294,7 @@ EOF
282
294
  describe "#scim" do
283
295
  it "has logging level 0 if #trace is true" do
284
296
  uaa.trace = true
285
- expect(uaa.send(:scim).logger.level).to eq 0
297
+ expect(uaa.send(:scim).logger.level).to eq -1
286
298
  end
287
299
 
288
300
  it "has logging level 1 if #trace is false" do
@@ -290,4 +302,31 @@ EOF
290
302
  expect(uaa.send(:scim).logger.level).to eq 1
291
303
  end
292
304
  end
305
+
306
+ describe "#try_to_refresh_token!" do
307
+ it "uses the refresh token to get a new access token" do
308
+ mock(uaa.send(:token_issuer)).refresh_token_grant(uaa.token.refresh_token) do
309
+ CF::UAA::TokenInfo.new(
310
+ :token_type => "bearer",
311
+ :access_token => "refreshed-token",
312
+ :refresh_token => "some-refresh-token")
313
+ end
314
+
315
+ uaa.try_to_refresh_token!
316
+ expect(uaa.token.auth_header).to eq "bearer refreshed-token"
317
+ expect(uaa.token.refresh_token).to eq "some-refresh-token"
318
+ end
319
+
320
+ context "when the refresh token has expired" do
321
+ it "returns the current token" do
322
+ stub(uaa.send(:token_issuer)).refresh_token_grant do
323
+ raise CF::UAA::TargetError.new
324
+ end
325
+
326
+ expect {
327
+ uaa.try_to_refresh_token!
328
+ }.to_not change { uaa.token }
329
+ end
330
+ end
331
+ end
293
332
  end
@@ -28,15 +28,16 @@ describe CFoundry::UploadHelpers do
28
28
  end
29
29
 
30
30
  before do
31
+ FileUtils.rm_rf tmpdir
32
+
31
33
  stub(Dir).tmpdir do
32
34
  FileUtils.mkdir_p tmpdir
33
35
  tmpdir
34
36
  end
37
+
35
38
  stub(base).upload_app.with_any_args
36
39
  end
37
40
 
38
- after { FileUtils.rm_rf tmpdir }
39
-
40
41
  subject { fake_model.upload(path, check_resources) }
41
42
 
42
43
  def relative_glob(dir)
@@ -125,5 +126,49 @@ describe CFoundry::UploadHelpers do
125
126
  subject
126
127
  end
127
128
  end
129
+
130
+ context 'when all files match existing resources' do
131
+ context 'and there are directories' do
132
+ let(:path) { "#{SPEC_ROOT}/fixtures/apps/with_nested_directories" }
133
+
134
+ it 'prunes them before zipping' do
135
+ stub(fake_model).make_fingerprints(anything) do
136
+ [[], CFoundry::UploadHelpers::RESOURCE_CHECK_LIMIT + 1]
137
+ end
138
+
139
+ stub(base).resource_match(anything) do
140
+ %w{ xyz foo/bar/baz/fizz }.map do |path|
141
+ { :fn => "#{tmpdir}/.vmc_#{guid}_files/#{path}" }
142
+ end
143
+ end
144
+
145
+ mock(base).upload_app(anything, false, anything)
146
+
147
+ fake_model.upload(path)
148
+ end
149
+ end
150
+ end
151
+
152
+ context "when only dotfiles don't match existing resources" do
153
+ let(:path) { "#{SPEC_ROOT}/fixtures/apps/with_dotfiles" }
154
+
155
+ it 'does not prune them' do
156
+ stub(fake_model).make_fingerprints(anything) do
157
+ [[], CFoundry::UploadHelpers::RESOURCE_CHECK_LIMIT + 1]
158
+ end
159
+
160
+ stub(base).resource_match(anything) do
161
+ %w{ xyz }.map do |path|
162
+ { :fn => "#{tmpdir}/.vmc_#{guid}_files/#{path}" }
163
+ end
164
+ end
165
+
166
+ mock(base).upload_app(anything, anything, anything) do |_, zip, _|
167
+ expect(zip).to be_a(String)
168
+ end
169
+
170
+ fake_model.upload(path)
171
+ end
172
+ end
128
173
  end
129
- end
174
+ end
@@ -0,0 +1,49 @@
1
+ require "spec_helper"
2
+
3
+ describe CFoundry::V2::App do
4
+ let(:client) { fake_client }
5
+
6
+ describe "environment" do
7
+ let(:app) { fake :app, :env => { "FOO" => "1" } }
8
+
9
+ it "returns a hash-like object" do
10
+ expect(app.env["FOO"]).to eq "1"
11
+ end
12
+
13
+ describe "converting keys and values to strings" do
14
+ let(:app) { fake :app, :env => { :FOO => 1 } }
15
+
16
+ it "converts keys and values to strings" do
17
+ expect(app.env.to_hash).to eq("FOO" => "1")
18
+ end
19
+ end
20
+
21
+ context "when changes are made to the hash-like object" do
22
+ it "reflects the changes in .env" do
23
+ expect {
24
+ app.env["BAR"] = "2"
25
+ }.to change { app.env.to_hash }.from("FOO" => "1").to("FOO" => "1", "BAR" => "2")
26
+ end
27
+ end
28
+
29
+ context "when the env is set to something else" do
30
+ it "reflects the changes in .env" do
31
+ expect {
32
+ app.env = { "BAR" => "2" }
33
+ }.to change { app.env.to_hash }.from("FOO" => "1").to("BAR" => "2")
34
+ end
35
+ end
36
+ end
37
+
38
+ describe "#summarize!" do
39
+ let(:app) { fake :app }
40
+
41
+ it "assigns :instances as #total_instances" do
42
+ stub(app).summary { { :instances => 4 } }
43
+
44
+ app.summarize!
45
+
46
+ expect(app.total_instances).to eq(4)
47
+ end
48
+ end
49
+ end
@@ -244,6 +244,23 @@ describe CFoundry::V2::Base do
244
244
  base.upload_app(guid, fake_zipfile)
245
245
  expect(stub).to have_been_requested
246
246
  end
247
+
248
+ context "when there is no file to upload" do
249
+ it "does not include 'application' in the request hash" do
250
+ stub =
251
+ stub_request(
252
+ :put,
253
+ "https://api.cloudfoundry.com/v2/apps/#{guid}/bits"
254
+ ).with { |request|
255
+ request.body =~ /name="resources"/ &&
256
+ request.body !~ /name="application"/
257
+ }.to_return(
258
+ :body => "{}"
259
+ )
260
+ base.upload_app(guid)
261
+ expect(stub).to have_been_requested
262
+ end
263
+ end
247
264
  end
248
265
 
249
266
  describe "#stream_file" do
@@ -26,6 +26,11 @@ describe CFoundry::V2::Client do
26
26
  subject { client.current_user }
27
27
  before { client.token = token }
28
28
 
29
+ context "when there is no token" do
30
+ let(:token) { nil }
31
+ it { should eq nil }
32
+ end
33
+
29
34
  context "when there is no access_token_data" do
30
35
  let(:token) { CFoundry::AuthToken.new("bearer some-access-token", "some-refresh-token") }
31
36
  it { should eq nil }
@@ -1,3 +1,5 @@
1
+ require "spec_helper"
2
+
1
3
  describe CFoundry::V2::Organization do
2
4
  let(:client) { fake_client }
3
5
 
@@ -1,3 +1,5 @@
1
+ require "spec_helper"
2
+
1
3
  describe CFoundry::V2::Space do
2
4
  let(:client) { fake_client }
3
5
 
@@ -0,0 +1 @@
1
+ bar
File without changes
data/spec/spec_helper.rb CHANGED
@@ -4,6 +4,7 @@ require "rspec"
4
4
  require "cfoundry"
5
5
  require "webmock/rspec"
6
6
  require "ostruct"
7
+ require "timecop"
7
8
 
8
9
  Dir[File.expand_path('../{support,fakes}/**/*.rb', __FILE__)].each do |file|
9
10
  require file
@@ -13,4 +14,4 @@ RSpec.configure do |c|
13
14
  c.include Fake::FakeMethods
14
15
  c.include V1Fake::FakeMethods
15
16
  c.mock_with :rr
16
- end
17
+ end
@@ -79,7 +79,7 @@ module CFoundry::V2
79
79
  end
80
80
 
81
81
  def fake_attributes(attributes)
82
- fakes = default_fakes.merge(attributes)
82
+ fakes = default_fakes
83
83
 
84
84
  # default relationships to other fake objects
85
85
  self.class.to_one_relations.each do |name, opts|
@@ -95,6 +95,17 @@ module CFoundry::V2
95
95
  end
96
96
  end
97
97
 
98
+ # make sure that the attributes provided are set after the defaults
99
+ #
100
+ # we have to do this for cases like environment_json vs. env,
101
+ # where one would clobber the other
102
+ attributes.each do |k, _|
103
+ fakes.delete k
104
+ end
105
+
106
+ fakes = fakes.to_a
107
+ fakes += attributes.to_a
108
+
98
109
  fakes
99
110
  end
100
111
 
metadata CHANGED
@@ -1,13 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cfoundry
3
3
  version: !ruby/object:Gem::Version
4
- hash: 11
5
- prerelease:
4
+ hash: 3470363275
5
+ prerelease: 6
6
6
  segments:
7
7
  - 0
8
8
  - 5
9
- - 0
10
- version: 0.5.0
9
+ - 1
10
+ - rc
11
+ - 1
12
+ version: 0.5.1.rc1
11
13
  platform: ruby
12
14
  authors:
13
15
  - Cloud Foundry Team
@@ -16,7 +18,7 @@ autorequire:
16
18
  bindir: bin
17
19
  cert_chain: []
18
20
 
19
- date: 2013-02-06 00:00:00 Z
21
+ date: 2013-02-20 00:00:00 Z
20
22
  dependencies:
21
23
  - !ruby/object:Gem::Dependency
22
24
  version_requirements: &id001 !ruby/object:Gem::Requirement
@@ -153,6 +155,20 @@ dependencies:
153
155
  type: :development
154
156
  name: gem-release
155
157
  requirement: *id009
158
+ - !ruby/object:Gem::Dependency
159
+ version_requirements: &id010 !ruby/object:Gem::Requirement
160
+ none: false
161
+ requirements:
162
+ - - ">="
163
+ - !ruby/object:Gem::Version
164
+ hash: 3
165
+ segments:
166
+ - 0
167
+ version: "0"
168
+ prerelease: false
169
+ type: :development
170
+ name: timecop
171
+ requirement: *id010
156
172
  description:
157
173
  email:
158
174
  - vcap-dev@googlegroups.com
@@ -185,6 +201,7 @@ files:
185
201
  - lib/cfoundry/v1/runtime.rb
186
202
  - lib/cfoundry/v1/service.rb
187
203
  - lib/cfoundry/v1/service_instance.rb
204
+ - lib/cfoundry/v1/service_plan.rb
188
205
  - lib/cfoundry/v1/user.rb
189
206
  - lib/cfoundry/v2/app.rb
190
207
  - lib/cfoundry/v2/base.rb
@@ -209,6 +226,7 @@ files:
209
226
  - lib/cfoundry.rb
210
227
  - spec/cfoundry/auth_token_spec.rb
211
228
  - spec/cfoundry/baseclient_spec.rb
229
+ - spec/cfoundry/client_spec.rb
212
230
  - spec/cfoundry/errors_spec.rb
213
231
  - spec/cfoundry/rest_client_spec.rb
214
232
  - spec/cfoundry/trace_helpers_spec.rb
@@ -217,6 +235,7 @@ files:
217
235
  - spec/cfoundry/v1/base_spec.rb
218
236
  - spec/cfoundry/v1/client_spec.rb
219
237
  - spec/cfoundry/v1/model_magic_spec.rb
238
+ - spec/cfoundry/v2/app_spec.rb
220
239
  - spec/cfoundry/v2/base_spec.rb
221
240
  - spec/cfoundry/v2/client_spec.rb
222
241
  - spec/cfoundry/v2/model_magic_spec.rb
@@ -233,6 +252,9 @@ files:
233
252
  - spec/fakes/service_plan_fake.rb
234
253
  - spec/fakes/space_fake.rb
235
254
  - spec/fakes/user_fake.rb
255
+ - spec/fixtures/apps/with_dotfiles/xyz
256
+ - spec/fixtures/apps/with_nested_directories/foo/bar/baz/fizz
257
+ - spec/fixtures/apps/with_nested_directories/xyz
236
258
  - spec/fixtures/apps/with_vmcignore/ignored_dir/file_in_ignored_dir.txt
237
259
  - spec/fixtures/apps/with_vmcignore/ignored_file.txt
238
260
  - spec/fixtures/apps/with_vmcignore/non_ignored_dir/file_in_non_ignored_dir.txt
@@ -265,12 +287,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
265
287
  required_rubygems_version: !ruby/object:Gem::Requirement
266
288
  none: false
267
289
  requirements:
268
- - - ">="
290
+ - - ">"
269
291
  - !ruby/object:Gem::Version
270
- hash: 3
292
+ hash: 25
271
293
  segments:
272
- - 0
273
- version: "0"
294
+ - 1
295
+ - 3
296
+ - 1
297
+ version: 1.3.1
274
298
  requirements: []
275
299
 
276
300
  rubyforge_project: cfoundry
@@ -281,6 +305,7 @@ summary: High-level library for working with the Cloud Foundry API.
281
305
  test_files:
282
306
  - spec/cfoundry/auth_token_spec.rb
283
307
  - spec/cfoundry/baseclient_spec.rb
308
+ - spec/cfoundry/client_spec.rb
284
309
  - spec/cfoundry/errors_spec.rb
285
310
  - spec/cfoundry/rest_client_spec.rb
286
311
  - spec/cfoundry/trace_helpers_spec.rb
@@ -289,6 +314,7 @@ test_files:
289
314
  - spec/cfoundry/v1/base_spec.rb
290
315
  - spec/cfoundry/v1/client_spec.rb
291
316
  - spec/cfoundry/v1/model_magic_spec.rb
317
+ - spec/cfoundry/v2/app_spec.rb
292
318
  - spec/cfoundry/v2/base_spec.rb
293
319
  - spec/cfoundry/v2/client_spec.rb
294
320
  - spec/cfoundry/v2/model_magic_spec.rb
@@ -305,6 +331,9 @@ test_files:
305
331
  - spec/fakes/service_plan_fake.rb
306
332
  - spec/fakes/space_fake.rb
307
333
  - spec/fakes/user_fake.rb
334
+ - spec/fixtures/apps/with_dotfiles/xyz
335
+ - spec/fixtures/apps/with_nested_directories/foo/bar/baz/fizz
336
+ - spec/fixtures/apps/with_nested_directories/xyz
308
337
  - spec/fixtures/apps/with_vmcignore/ignored_dir/file_in_ignored_dir.txt
309
338
  - spec/fixtures/apps/with_vmcignore/ignored_file.txt
310
339
  - spec/fixtures/apps/with_vmcignore/non_ignored_dir/file_in_non_ignored_dir.txt