jfoundry 0.1.0.pre

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 (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