jfoundry 0.1.0.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. data/LICENSE +746 -0
  2. data/Rakefile +10 -0
  3. data/lib/cc_api_stub/applications.rb +53 -0
  4. data/lib/cc_api_stub/domains.rb +32 -0
  5. data/lib/cc_api_stub/frameworks.rb +22 -0
  6. data/lib/cc_api_stub/helper.rb +139 -0
  7. data/lib/cc_api_stub/login.rb +21 -0
  8. data/lib/cc_api_stub/organization_users.rb +21 -0
  9. data/lib/cc_api_stub/organizations.rb +70 -0
  10. data/lib/cc_api_stub/routes.rb +26 -0
  11. data/lib/cc_api_stub/runtimes.rb +22 -0
  12. data/lib/cc_api_stub/service_bindings.rb +22 -0
  13. data/lib/cc_api_stub/service_instances.rb +22 -0
  14. data/lib/cc_api_stub/services.rb +21 -0
  15. data/lib/cc_api_stub/spaces.rb +49 -0
  16. data/lib/cc_api_stub/users.rb +85 -0
  17. data/lib/cc_api_stub.rb +16 -0
  18. data/lib/jfoundry/auth_token.rb +63 -0
  19. data/lib/jfoundry/baseclient.rb +177 -0
  20. data/lib/jfoundry/chatty_hash.rb +46 -0
  21. data/lib/jfoundry/client.rb +39 -0
  22. data/lib/jfoundry/concerns/proxy_options.rb +17 -0
  23. data/lib/jfoundry/errors.rb +163 -0
  24. data/lib/jfoundry/rest_client.rb +331 -0
  25. data/lib/jfoundry/signature/version.rb +27 -0
  26. data/lib/jfoundry/signer.rb +13 -0
  27. data/lib/jfoundry/test_support.rb +3 -0
  28. data/lib/jfoundry/timer.rb +13 -0
  29. data/lib/jfoundry/trace_helpers.rb +64 -0
  30. data/lib/jfoundry/upload_helpers.rb +222 -0
  31. data/lib/jfoundry/v2/app.rb +357 -0
  32. data/lib/jfoundry/v2/app_event.rb +13 -0
  33. data/lib/jfoundry/v2/base.rb +92 -0
  34. data/lib/jfoundry/v2/client.rb +78 -0
  35. data/lib/jfoundry/v2/domain.rb +20 -0
  36. data/lib/jfoundry/v2/managed_service_instance.rb +13 -0
  37. data/lib/jfoundry/v2/model.rb +209 -0
  38. data/lib/jfoundry/v2/model_magic/attribute.rb +49 -0
  39. data/lib/jfoundry/v2/model_magic/client_extensions.rb +170 -0
  40. data/lib/jfoundry/v2/model_magic/has_summary.rb +49 -0
  41. data/lib/jfoundry/v2/model_magic/queryable_by.rb +39 -0
  42. data/lib/jfoundry/v2/model_magic/to_many.rb +138 -0
  43. data/lib/jfoundry/v2/model_magic/to_one.rb +81 -0
  44. data/lib/jfoundry/v2/model_magic.rb +93 -0
  45. data/lib/jfoundry/v2/organization.rb +22 -0
  46. data/lib/jfoundry/v2/quota_definition.rb +12 -0
  47. data/lib/jfoundry/v2/route.rb +25 -0
  48. data/lib/jfoundry/v2/service.rb +20 -0
  49. data/lib/jfoundry/v2/service_auth_token.rb +10 -0
  50. data/lib/jfoundry/v2/service_binding.rb +10 -0
  51. data/lib/jfoundry/v2/service_broker.rb +11 -0
  52. data/lib/jfoundry/v2/service_instance.rb +13 -0
  53. data/lib/jfoundry/v2/service_plan.rb +13 -0
  54. data/lib/jfoundry/v2/space.rb +18 -0
  55. data/lib/jfoundry/v2/stack.rb +10 -0
  56. data/lib/jfoundry/v2/user.rb +104 -0
  57. data/lib/jfoundry/v2/user_provided_service_instance.rb +7 -0
  58. data/lib/jfoundry/validator.rb +41 -0
  59. data/lib/jfoundry/version.rb +4 -0
  60. data/lib/jfoundry/zip.rb +56 -0
  61. data/lib/jfoundry.rb +5 -0
  62. data/lib/tasks/gem_release.rake +42 -0
  63. data/vendor/errors/README.md +3 -0
  64. data/vendor/errors/v1.yml +189 -0
  65. data/vendor/errors/v2.yml +470 -0
  66. metadata +269 -0
@@ -0,0 +1,357 @@
1
+ require "tmpdir"
2
+ require "multi_json"
3
+
4
+ require "jfoundry/zip"
5
+ require "jfoundry/upload_helpers"
6
+ require "jfoundry/chatty_hash"
7
+
8
+ require "jfoundry/v2/model"
9
+
10
+ module JFoundry::V2
11
+ # Class for representing a user's application on a given target (via
12
+ # Client).
13
+ #
14
+ # Does not guarantee that the app exists; used for both app creation and
15
+ # retrieval, as the attributes are all lazily retrieved. Setting attributes
16
+ # does not perform any requests; use #update! to commit your changes.
17
+ class App < Model
18
+ include JFoundry::UploadHelpers
19
+
20
+ attribute :name, :string
21
+ to_one :space
22
+ attribute :environment_json, :hash, :default => {}
23
+ attribute :memory, :integer, :default => 256
24
+ attribute :total_instances, :integer, :default => 1, :at => :instances
25
+ attribute :disk_quota, :integer, :default => 256
26
+ attribute :state, :string, :default => "STOPPED"
27
+ attribute :command, :string, :default => nil
28
+ attribute :console, :boolean, :default => false
29
+ attribute :buildpack, :string, :default => nil
30
+ to_one :stack, :default => nil
31
+ attribute :debug, :string, :default => nil
32
+ to_many :service_bindings
33
+ to_many :routes
34
+ to_many :events, :as => :app_event
35
+
36
+ scoped_to_space
37
+
38
+ queryable_by :name, :space_guid, :organization_guid
39
+
40
+ has_summary :urls => proc { |x| self.cache[:uris] = x },
41
+ :running_instances => proc { |x|
42
+ self.cache[:running_instances] = x
43
+ },
44
+ :instances => proc { |x|
45
+ self.total_instances = x
46
+ }
47
+
48
+ private :environment_json
49
+
50
+ def delete!(opts = {})
51
+ super(opts.merge(:recursive => true))
52
+ end
53
+
54
+ def instances
55
+ @client.base.instances(@guid).collect do |i, m|
56
+ Instance.new(self, i.to_s, @client, m)
57
+ end
58
+ end
59
+
60
+ def crashes
61
+ @client.base.crashes(@guid).collect do |m|
62
+ Instance.new(self, m[:instance], @client, m)
63
+ end
64
+ end
65
+
66
+ def stats
67
+ stats = {}
68
+
69
+ @client.base.stats(@guid).each do |idx, info|
70
+ stats[idx.to_s] = info
71
+ end
72
+
73
+ stats
74
+ end
75
+
76
+ def services
77
+ service_bindings.collect(&:service_instance)
78
+ end
79
+
80
+ def env
81
+ JFoundry::ChattyHash.new(
82
+ method(:env=),
83
+ stringify(environment_json))
84
+ end
85
+
86
+ def env=(x)
87
+ self.environment_json = stringify(x.to_hash)
88
+ end
89
+
90
+ alias :debug_mode :debug
91
+
92
+ def uris
93
+ return @cache[:uris] if @cache[:uris]
94
+
95
+ routes.collect do |r|
96
+ "#{r.host}.#{r.domain.name}"
97
+ end
98
+ end
99
+ alias :urls :uris
100
+
101
+ def uris=(uris)
102
+ raise JFoundry::Deprecated,
103
+ "App#uris= is invalid against V2 APIs; use add/remove_route"
104
+ end
105
+ alias :urls= :uris=
106
+
107
+ def uri
108
+ if uris = @cache[:uris]
109
+ return uris.first
110
+ end
111
+
112
+ if route = routes.first
113
+ "#{route.host}.#{route.domain.name}"
114
+ end
115
+ end
116
+ alias :url :uri
117
+
118
+ def host
119
+ return nil if routes.empty?
120
+ routes.first.host
121
+ end
122
+
123
+ def domain
124
+ return nil if routes.empty?
125
+ routes.first.domain.name
126
+ end
127
+
128
+ def uri=(x)
129
+ self.uris = [x]
130
+ end
131
+ alias :url= :uri=
132
+
133
+ # Stop the application.
134
+ def stop!
135
+ self.state = "STOPPED"
136
+ update!
137
+ end
138
+
139
+ # Start the application.
140
+ def start!(&blk)
141
+ self.state = "STARTED"
142
+ update!(&blk)
143
+ end
144
+
145
+ # Restart the application.
146
+ def restart!(&blk)
147
+ stop!
148
+ start!(&blk)
149
+ end
150
+
151
+ def update!
152
+ response = @client.base.update_app(@guid, @diff)
153
+
154
+ yield response[:headers]["x-app-staging-log"] if block_given?
155
+
156
+ @manifest = @client.base.send(:parse_json, response[:body])
157
+
158
+ @diff.clear
159
+
160
+ true
161
+ end
162
+
163
+ def stream_update_log(log_url)
164
+ offset = 0
165
+
166
+ while true
167
+ begin
168
+ @client.stream_url(log_url + "&tail&tail_offset=#{offset}") do |out|
169
+ offset += out.size
170
+ yield out
171
+ end
172
+ rescue Timeout::Error
173
+ end
174
+ end
175
+ rescue JFoundry::APIError
176
+ end
177
+
178
+ # Determine application health.
179
+ #
180
+ # If all instances are running, returns "RUNNING". If only some are
181
+ # started, returns the percentage of them that are healthy.
182
+ #
183
+ # Otherwise, returns application's status.
184
+ def health
185
+ if state == "STARTED"
186
+ healthy_count = running_instances
187
+ expected = total_instances
188
+
189
+ if expected > 0
190
+ ratio = healthy_count / expected.to_f
191
+ if ratio == 1.0
192
+ "RUNNING"
193
+ else
194
+ "#{(ratio * 100).to_i}%"
195
+ end
196
+ else
197
+ "N/A"
198
+ end
199
+ else
200
+ state
201
+ end
202
+ rescue JFoundry::StagingError, JFoundry::NotStaged
203
+ "STAGING FAILED"
204
+ end
205
+
206
+ def running_instances
207
+ return @cache[:running_instances] if @cache[:running_instances]
208
+
209
+ running = 0
210
+
211
+ instances.each do |i|
212
+ running += 1 if i.state == "RUNNING"
213
+ end
214
+
215
+ running
216
+ end
217
+
218
+ # Check that all application instances are running.
219
+ def healthy?
220
+ # invalidate cache so the check is fresh
221
+ invalidate!
222
+ health == "RUNNING"
223
+ end
224
+ alias_method :running?, :healthy?
225
+
226
+ # Is the application stopped?
227
+ def stopped?
228
+ state == "STOPPED"
229
+ end
230
+
231
+ # Is the application started?
232
+ #
233
+ # Note that this does not imply that all instances are running. See
234
+ # #healthy?
235
+ def started?
236
+ state == "STARTED"
237
+ end
238
+
239
+ # Bind services to application.
240
+ def bind(*instances)
241
+ instances.each do |i|
242
+ binding = @client.service_binding
243
+ binding.app = self
244
+ binding.service_instance = i
245
+ binding.create!
246
+ end
247
+
248
+ self
249
+ end
250
+
251
+ # Unbind services from application.
252
+ def unbind(*instances)
253
+ service_bindings.each do |b|
254
+ if instances.include? b.service_instance
255
+ b.delete!
256
+ end
257
+ end
258
+
259
+ self
260
+ end
261
+
262
+ def binds?(instance)
263
+ service_bindings.any? { |b|
264
+ b.service_instance == instance
265
+ }
266
+ end
267
+
268
+ def files(*path)
269
+ Instance.new(self, "0", @client).files(*path)
270
+ end
271
+
272
+ def file(*path)
273
+ Instance.new(self, "0", @client).file(*path)
274
+ end
275
+
276
+ def stream_file(*path, &blk)
277
+ Instance.new(self, "0", @client).stream_file(*path, &blk)
278
+ end
279
+
280
+ private
281
+
282
+ def stringify(hash)
283
+ new = {}
284
+
285
+ hash.each do |k, v|
286
+ new[k.to_s] = v.to_s
287
+ end
288
+
289
+ new
290
+ end
291
+
292
+ class Instance
293
+ attr_reader :app, :id
294
+
295
+ def initialize(app, id, client, manifest = {})
296
+ @app = app
297
+ @id = id
298
+ @client = client
299
+ @manifest = manifest
300
+ end
301
+
302
+ def inspect
303
+ "#<App::Instance '#{@app.name}' \##@id>"
304
+ end
305
+
306
+ def state
307
+ @manifest[:state]
308
+ end
309
+ alias_method :status, :state
310
+
311
+ def since
312
+ if since = @manifest[:since]
313
+ Time.at(@manifest[:since])
314
+ end
315
+ end
316
+
317
+ def debugger
318
+ return unless @manifest[:debug_ip] and @manifest[:debug_port]
319
+
320
+ { :ip => @manifest[:debug_ip],
321
+ :port => @manifest[:debug_port]
322
+ }
323
+ end
324
+
325
+ def console
326
+ return unless @manifest[:console_ip] and @manifest[:console_port]
327
+
328
+ { :ip => @manifest[:console_ip],
329
+ :port => @manifest[:console_port]
330
+ }
331
+ end
332
+
333
+ def healthy?
334
+ case state
335
+ when "STARTING", "RUNNING"
336
+ true
337
+ when "DOWN", "FLAPPING"
338
+ false
339
+ end
340
+ end
341
+
342
+ def files(*path)
343
+ @client.base.files(@app.guid, @id, *path).split("\n").collect do |entry|
344
+ path + [entry.split(/\s+/, 2)[0]]
345
+ end
346
+ end
347
+
348
+ def file(*path)
349
+ @client.base.files(@app.guid, @id, *path)
350
+ end
351
+
352
+ def stream_file(*path, &blk)
353
+ @client.base.stream_file(@app.guid, @id, *path, &blk)
354
+ end
355
+ end
356
+ end
357
+ end
@@ -0,0 +1,13 @@
1
+ require "jfoundry/v2/model"
2
+
3
+ module JFoundry::V2
4
+ class AppEvent < Model
5
+ to_one :app
6
+
7
+ attribute :instance_guid, :string
8
+ attribute :instance_index, :integer
9
+ attribute :exit_status, :integer
10
+ attribute :exit_description, :string, :default => ""
11
+ attribute :timestamp, :string
12
+ end
13
+ end
@@ -0,0 +1,92 @@
1
+ require "multi_json"
2
+
3
+ require "jfoundry/baseclient"
4
+
5
+ require "jfoundry/errors"
6
+
7
+ module JFoundry::V2
8
+ class Base < JFoundry::BaseClient
9
+ include BaseClientMethods
10
+
11
+ def resource_match(fingerprints)
12
+ put("v2", "resource_match", :content => :json, :accept => :json, :payload => fingerprints)
13
+ end
14
+
15
+ def upload_app(guid, zipfile = nil, resources = [])
16
+ payload = {}
17
+ payload[:resources] = MultiJson.dump(resources)
18
+
19
+ if zipfile
20
+ payload[:application] =
21
+ UploadIO.new(
22
+ if zipfile.is_a? File
23
+ zipfile
24
+ elsif zipfile.is_a? String
25
+ File.new(zipfile, "rb")
26
+ end,
27
+ "application/zip")
28
+ end
29
+
30
+ put("v2", "apps", guid, "bits", :payload => payload)
31
+ rescue EOFError
32
+ retry
33
+ end
34
+
35
+ def files(guid, instance, *path)
36
+ get("v2", "apps", guid, "instances", instance, "files", *path)
37
+ end
38
+
39
+ alias :file :files
40
+
41
+ def stream_file(guid, instance, *path, &blk)
42
+ path_and_options = path + [{:return_response => true, :follow_redirects => false}]
43
+ redirect = get("v2", "apps", guid, "instances", instance, "files", *path_and_options)
44
+
45
+ if location = redirect[:headers]["location"]
46
+ stream_url(location + "&tail", &blk)
47
+ else
48
+ yield redirect[:body]
49
+ end
50
+ end
51
+
52
+ def instances(guid)
53
+ get("v2", "apps", guid, "instances", :accept => :json)
54
+ end
55
+
56
+ def crashes(guid)
57
+ get("v2", "apps", guid, "crashes", :accept => :json)
58
+ end
59
+
60
+ def stats(guid)
61
+ get("v2", "apps", guid, "stats", :accept => :json)
62
+ end
63
+
64
+ def update_app(guid, diff)
65
+ put("v2", "apps", guid,
66
+ :content => :json,
67
+ :payload => diff,
68
+ :return_response => true)
69
+ end
70
+
71
+ def user_info
72
+ get("v2", "user_info", :accept => :json)
73
+ end
74
+
75
+ def for_each(paginated, &block)
76
+ paginated[:resources].each &block
77
+
78
+ while next_page = paginated[:next_url]
79
+ paginated = get(next_page, :accept => :json)
80
+ paginated[:resources].each &block
81
+ end
82
+ end
83
+
84
+ def all_pages(paginated)
85
+ payload = []
86
+ for_each(paginated) do |resource|
87
+ payload << resource
88
+ end
89
+ payload
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,78 @@
1
+ require "forwardable"
2
+
3
+ module JFoundry::V2
4
+ # The primary API entrypoint. Wraps a BaseClient to provide nicer return
5
+ # values. Initialize with the target and, optionally, an auth token. These
6
+ # are the only two internal states.
7
+ class Client
8
+ include ClientMethods
9
+ extend Forwardable
10
+
11
+ # Internal BaseClient instance. Normally won't be touching this.
12
+ attr_reader :base
13
+
14
+ # [Organization] Currently targeted organization.
15
+ attr_accessor :current_organization
16
+
17
+ # [Space] Currently targeted space.
18
+ attr_accessor :current_space
19
+
20
+ def_delegators :@base, :target, :target=, :access_key, :access_key=, :secret_key,
21
+ :secret_key=, :version, :version=, :http_proxy, :http_proxy=, :https_proxy,
22
+ :https_proxy=, :trace, :trace=, :log, :log=, :info
23
+
24
+ # Create a new Client for interfacing with the given target.
25
+ #
26
+ # A token may also be provided to skip the login step.
27
+ def initialize(target, access_key, secret_key, version)
28
+ @base = Base.new(target, access_key, secret_key, version)
29
+ end
30
+
31
+ def version
32
+ 2
33
+ end
34
+
35
+ # The currently authenticated user.
36
+ def current_user
37
+ user = user(@base.access_key)
38
+ user_info = @base.user_info
39
+ if user_info && user_info.key?(:user)
40
+ user.emails = [{ :value => user_info[:user]}]
41
+ end
42
+ user
43
+ end
44
+
45
+ def query_target(klass)
46
+ if klass.scoped_space && space = current_space
47
+ space
48
+ elsif klass.scoped_organization && org = current_organization
49
+ org
50
+ else
51
+ self
52
+ end
53
+ end
54
+
55
+ def stream_url(url, &blk)
56
+ @base.stream_url(url, &blk)
57
+ end
58
+
59
+ def service_instances(opts={})
60
+ opts[:user_provided] = true
61
+ super(opts)
62
+ end
63
+
64
+ def service_instances_from(path, *args)
65
+ opts = args.first || {}
66
+ opts[:user_provided] = true
67
+ super(path, opts, *args)
68
+ end
69
+
70
+ def make_service_instance(json)
71
+ klass = "JFoundry::V2::#{json[:entity][:type].camelize}".constantize
72
+ klass.new(
73
+ json[:metadata][:guid],
74
+ self,
75
+ json)
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,20 @@
1
+ require "jfoundry/v2/model"
2
+
3
+ module JFoundry::V2
4
+ class Domain < Model
5
+ validates_presence_of :name
6
+ validates_format_of :name, :with => /\A([^\.]+\.)+[^\.]+\Z/
7
+ validates_presence_of :owning_organization
8
+
9
+ attribute :name, :string
10
+ attribute :wildcard, :boolean
11
+ to_one :owning_organization, :as => :organization, :default => nil
12
+ to_many :spaces
13
+
14
+ queryable_by :name, :owning_organization_guid, :space_guid
15
+
16
+ def system?
17
+ guid.present? && !owning_organization.present?
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,13 @@
1
+ require "jfoundry/v2/service_instance"
2
+
3
+ module JFoundry::V2
4
+ class ManagedServiceInstance < ServiceInstance
5
+ attribute :dashboard_url, :string
6
+ attribute :credentials, :hash
7
+ to_one :service_plan
8
+
9
+ def self.object_name
10
+ 'service_instance'
11
+ end
12
+ end
13
+ end