cfoundry 0.5.0 → 0.5.1.rc1

Sign up to get free protection for your applications and to get access to all the features.
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