cfoundry-IronFoundry 0.3.34

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.
@@ -0,0 +1,111 @@
1
+ require "cfoundry/baseclient"
2
+
3
+ module CFoundry
4
+ class UAAClient < BaseClient
5
+ attr_accessor :target, :client_id, :redirect_uri, :token, :trace
6
+
7
+ def initialize(
8
+ target = "https://uaa.cloudfoundry.com",
9
+ client_id = "vmc")
10
+ @target = target
11
+ @client_id = client_id
12
+ @redirect_uri = @target + "/redirect/vmc"
13
+ end
14
+
15
+ def prompts
16
+ get("login", nil => :json)[:prompts]
17
+ end
18
+
19
+ def authorize(credentials)
20
+ query = {
21
+ :client_id => @client_id,
22
+ :response_type => "token",
23
+ :redirect_uri => @redirect_uri
24
+ }
25
+
26
+ extract_token(
27
+ post(
28
+ { :credentials => credentials },
29
+ "oauth", "authorize",
30
+ :form => :headers,
31
+ :params => query)[:location])
32
+ end
33
+
34
+ def users
35
+ get("Users", nil => :json)
36
+ end
37
+
38
+ def change_password(guid, new, old)
39
+ put(
40
+ { :schemas => ["urn:scim:schemas:core:1.0"],
41
+ :password => new,
42
+ :oldPassword => old
43
+ },
44
+ "User", guid, "password",
45
+ :json => nil)
46
+ end
47
+
48
+ private
49
+
50
+ def handle_response(response, accept)
51
+ json = accept == :json
52
+
53
+ case response.code
54
+ when 200, 204, 302
55
+ if accept == :headers
56
+ return response.headers
57
+ end
58
+
59
+ if json
60
+ if response.code == 204
61
+ raise "Expected JSON response, got 204 No Content"
62
+ end
63
+
64
+ parse_json(response)
65
+ else
66
+ response
67
+ end
68
+
69
+ when 400, 403
70
+ info = parse_json(response)
71
+ raise Denied.new(403, info[:error_description])
72
+
73
+ when 401
74
+ info = parse_json(response)
75
+ raise Denied.new(401, info[:error_description])
76
+
77
+ when 404
78
+ raise NotFound
79
+
80
+ when 409
81
+ info = parse_json(response)
82
+ raise CFoundry::Denied.new(409, info[:message])
83
+
84
+ when 411, 500, 504
85
+ begin
86
+ raise_error(parse_json(response))
87
+ rescue MultiJson::DecodeError
88
+ raise BadResponse.new(response.code, response)
89
+ end
90
+
91
+ else
92
+ raise BadResponse.new(response.code, response)
93
+ end
94
+ end
95
+
96
+ def extract_token(url)
97
+ _, params = url.split('#')
98
+ return unless params
99
+
100
+ values = {}
101
+ params.split("&").each do |pair|
102
+ key, val = pair.split("=")
103
+ values[key] = val
104
+ end
105
+
106
+ return unless values["access_token"] && values["token_type"]
107
+
108
+ "#{values["token_type"]} #{values["access_token"]}"
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,100 @@
1
+ require "fileutils"
2
+ require "pathname"
3
+ require "digest/sha1"
4
+
5
+ module CFoundry
6
+ module UploadHelpers
7
+ # Default paths to exclude from upload payload.
8
+ UPLOAD_EXCLUDE = %w{.git _darcs .svn}
9
+
10
+ def make_fingerprints(path)
11
+ fingerprints = []
12
+ total_size = 0
13
+
14
+ Dir.glob("#{path}/**/*", File::FNM_DOTMATCH) do |filename|
15
+ next if File.directory?(filename)
16
+
17
+ size = File.size(filename)
18
+
19
+ total_size += size
20
+
21
+ fingerprints << {
22
+ :size => size,
23
+ :sha1 => Digest::SHA1.file(filename).hexdigest,
24
+ :fn => filename
25
+ }
26
+ end
27
+
28
+ [fingerprints, total_size]
29
+ end
30
+
31
+ def prepare_package(path, to)
32
+ if path =~ /\.(jar|war|zip)$/
33
+ CFoundry::Zip.unpack(path, to)
34
+ elsif war_file = Dir.glob("#{path}/*.war").first
35
+ CFoundry::Zip.unpack(war_file, to)
36
+ else
37
+ check_unreachable_links(path)
38
+
39
+ FileUtils.mkdir(to)
40
+
41
+ files = Dir.glob("#{path}/{*,.[^\.]*}")
42
+
43
+ exclude = UPLOAD_EXCLUDE
44
+ if File.exists?("#{path}/.vmcignore")
45
+ exclude += File.read("#{path}/.vmcignore").split(/\n+/)
46
+ end
47
+
48
+ # prevent initial copying if we can, remove sub-files later
49
+ files.reject! do |f|
50
+ exclude.any? do |e|
51
+ File.fnmatch(f.sub(path + "/", ""), e)
52
+ end
53
+ end
54
+
55
+ FileUtils.cp_r(files, to)
56
+
57
+ find_sockets(to).each do |s|
58
+ File.delete s
59
+ end
60
+
61
+ # remove ignored globs more thoroughly
62
+ #
63
+ # note that the above file list only includes toplevel
64
+ # files/directories for cp_r, so this is where sub-files/etc. are
65
+ # removed
66
+ exclude.each do |e|
67
+ Dir.glob("#{to}/#{e}").each do |f|
68
+ FileUtils.rm_rf(f)
69
+ end
70
+ end
71
+ end
72
+ end
73
+
74
+ def check_unreachable_links(path)
75
+ files = Dir.glob("#{path}/**/*", File::FNM_DOTMATCH)
76
+
77
+ # only used for friendlier error message
78
+ pwd = Pathname.pwd
79
+
80
+ abspath = File.expand_path(path)
81
+ unreachable = []
82
+ files.each do |f|
83
+ file = Pathname.new(f)
84
+ if file.symlink? && !file.realpath.to_s.start_with?(abspath)
85
+ unreachable << file.relative_path_from(pwd)
86
+ end
87
+ end
88
+
89
+ unless unreachable.empty?
90
+ root = Pathname.new(path).relative_path_from(pwd)
91
+ raise "Can't deploy application containing links '#{unreachable}' that reach outside its root '#{root}'"
92
+ end
93
+ end
94
+
95
+ def find_sockets(path)
96
+ files = Dir.glob("#{path}/**/*", File::FNM_DOTMATCH)
97
+ files && files.select { |f| File.socket? f }
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,562 @@
1
+ require "tmpdir"
2
+
3
+ require "cfoundry/zip"
4
+ require "cfoundry/upload_helpers"
5
+ require "cfoundry/chatty_hash"
6
+
7
+ require "cfoundry/v1/framework"
8
+ require "cfoundry/v1/runtime"
9
+
10
+ module CFoundry::V1
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
18
+ include CFoundry::UploadHelpers
19
+
20
+ # Application name.
21
+ attr_accessor :name
22
+
23
+ # Application instance count.
24
+ attr_accessor :total_instances
25
+
26
+ # Services bound to the application.
27
+ attr_accessor :services
28
+
29
+ # Application environment variables.
30
+ attr_accessor :env
31
+
32
+ # Application memory limit.
33
+ attr_accessor :memory
34
+
35
+ # Application framework.
36
+ attr_accessor :framework
37
+
38
+ # Application runtime.
39
+ attr_accessor :runtime
40
+
41
+ # Application startup command.
42
+ #
43
+ # Used for standalone apps.
44
+ attr_accessor :command
45
+
46
+ # Application debug mode.
47
+ attr_accessor :debug_mode
48
+
49
+ # Application state.
50
+ attr_accessor :state
51
+ alias_method :status, :state
52
+
53
+ # URIs mapped to the application.
54
+ attr_accessor :uris
55
+ alias_method :urls, :uris
56
+
57
+
58
+ # Create an App object.
59
+ #
60
+ # You'll usually call Client#app instead
61
+ def initialize(name, client, manifest = nil)
62
+ @name = name
63
+ @client = client
64
+ @manifest = manifest
65
+ @diff = {}
66
+ end
67
+
68
+ # Show string representing the application.
69
+ def inspect
70
+ "#<App '#@name'>"
71
+ end
72
+
73
+ # Basic equality test by name.
74
+ def eql?(other)
75
+ other.is_a?(self.class) && other.name == @name
76
+ end
77
+ alias :== :eql?
78
+
79
+ # Delete the application from the target.
80
+ #
81
+ # Keeps the metadata, but clears target-specific state from it.
82
+ def delete!
83
+ @client.base.delete_app(@name)
84
+
85
+ if @manifest
86
+ @diff = read_manifest
87
+ @manifest = nil
88
+ end
89
+ end
90
+
91
+ # Create the application on the target.
92
+ #
93
+ # Call this after setting the various attributes.
94
+ def create!
95
+ @client.base.create_app(create_manifest)
96
+ @diff = {}
97
+ end
98
+
99
+ # Check if the application exists on the target.
100
+ def exists?
101
+ @client.base.app(@name)
102
+ true
103
+ rescue CFoundry::NotFound
104
+ false
105
+ end
106
+
107
+ # Retrieve all of the instances of the app, as Instance objects.
108
+ def instances
109
+ @client.base.instances(@name).collect do |m|
110
+ Instance.new(self, m[:index].to_s, @client, m)
111
+ end
112
+ end
113
+
114
+ # Retrieve crashed instances
115
+ def crashes
116
+ @client.base.crashes(@name).collect do |i|
117
+ Instance.new(self, i[:instance].to_s, @client, i)
118
+ end
119
+ end
120
+
121
+ # Retrieve application statistics, e.g. CPU load and memory usage.
122
+ def stats
123
+ @client.base.stats(@name)
124
+ end
125
+
126
+ # Update application attributes. Does not restart the application.
127
+ def update!(what = {})
128
+ what.each do |k, v|
129
+ send(:"#{k}=", v)
130
+ end
131
+
132
+ @client.base.update_app(@name, update_manifest)
133
+
134
+ @manifest = nil
135
+ @diff = {}
136
+
137
+ self
138
+ end
139
+
140
+ # Stop the application.
141
+ def stop!
142
+ update! :state => "STOPPED"
143
+ end
144
+
145
+ # Start the application.
146
+ def start!
147
+ update! :state => "STARTED"
148
+ end
149
+
150
+ # Restart the application.
151
+ def restart!
152
+ stop!
153
+ start!
154
+ end
155
+
156
+ # Determine application health.
157
+ #
158
+ # If all instances are running, returns "RUNNING". If only some are
159
+ # started, returns the precentage of them that are healthy.
160
+ #
161
+ # Otherwise, returns application's status.
162
+ def health
163
+ s = state
164
+ if s == "STARTED"
165
+ healthy_count = running_instances
166
+ expected = total_instances
167
+ if healthy_count && expected > 0
168
+ ratio = healthy_count / expected.to_f
169
+ if ratio == 1.0
170
+ "RUNNING"
171
+ else
172
+ "#{(ratio * 100).to_i}%"
173
+ end
174
+ else
175
+ "N/A"
176
+ end
177
+ else
178
+ s
179
+ end
180
+ end
181
+
182
+ # Check that all application instances are running.
183
+ def healthy?
184
+ # invalidate cache so the check is fresh
185
+ @manifest = nil
186
+ health == "RUNNING"
187
+ end
188
+ alias_method :running?, :healthy?
189
+
190
+ # Is the application stopped?
191
+ def stopped?
192
+ state == "STOPPED"
193
+ end
194
+
195
+ # Is the application started?
196
+ #
197
+ # Note that this does not imply that all instances are running. See
198
+ # #healthy?
199
+ def started?
200
+ state == "STARTED"
201
+ end
202
+
203
+
204
+ { :total_instances => :instances,
205
+ :running_instances => :running_instances,
206
+ :runtime_name => :runtime,
207
+ :framework_name => :framework,
208
+ :service_names => :services,
209
+ :env_array => :env,
210
+ :state => :state,
211
+ :status => :state,
212
+ :uris => :uris,
213
+ :urls => :uris,
214
+ :command => :command,
215
+ :console => :console,
216
+ :memory => :memory,
217
+ :disk => :disk,
218
+ :fds => :fds,
219
+ :debug_mode => :debug,
220
+ :version => :version,
221
+ :meta_version => :meta_version,
222
+ :created => :created
223
+ }.each do |meth, attr|
224
+ define_method(meth) do
225
+ if @diff.key?(attr)
226
+ @diff[attr]
227
+ else
228
+ read_manifest[attr]
229
+ end
230
+ end
231
+
232
+ define_method(:"#{meth}=") do |v|
233
+ @diff[attr] = v
234
+ end
235
+ end
236
+
237
+
238
+ # Shortcut for uris[0]
239
+ def uri
240
+ uris[0]
241
+ end
242
+
243
+ # Shortcut for uris = [x]
244
+ def uri=(x)
245
+ self.uris = [x]
246
+ end
247
+
248
+ alias :url :uri
249
+ alias :url= :uri=
250
+
251
+ # Application framework.
252
+ def framework
253
+ Framework.new(framework_name)
254
+ end
255
+
256
+ def framework=(v) # :nodoc:
257
+ v = v.name if v.is_a?(Framework)
258
+ self.framework_name = v
259
+ end
260
+
261
+ # Application runtime.
262
+ def runtime
263
+ Runtime.new(runtime_name)
264
+ end
265
+
266
+ def runtime=(v) # :nodoc:
267
+ v = v.name if v.is_a?(Runtime)
268
+ self.runtime_name = v
269
+ end
270
+
271
+ def env
272
+ e = env_array || []
273
+
274
+ env = {}
275
+ e.each do |pair|
276
+ name, val = pair.split("=", 2)
277
+ env[name] = val
278
+ end
279
+
280
+ CFoundry::ChattyHash.new(method(:env=), env)
281
+ end
282
+
283
+ def env=(hash)
284
+ unless hash.is_a?(Array)
285
+ hash = hash.collect { |k, v| "#{k}=#{v}" }
286
+ end
287
+
288
+ self.env_array = hash
289
+ end
290
+
291
+ def services
292
+ service_names.collect do |name|
293
+ @client.service_instance(name)
294
+ end
295
+ end
296
+
297
+ def services=(instances)
298
+ self.service_names = instances.collect(&:name)
299
+ end
300
+
301
+
302
+ # Bind services to application.
303
+ def bind(*instances)
304
+ update!(:services => services + instances)
305
+ end
306
+
307
+ # Unbind services from application.
308
+ def unbind(*instances)
309
+ update!(:services =>
310
+ services.reject { |s|
311
+ instances.any? { |i| i.name == s.name }
312
+ })
313
+ end
314
+
315
+ def binds?(instance)
316
+ services.include? instance
317
+ end
318
+
319
+ # Retrieve file listing under path for the first instance of the application.
320
+ #
321
+ # [path]
322
+ # A sequence of strings representing path segments.
323
+ #
324
+ # For example, <code>files("foo", "bar")</code> for +foo/bar+.
325
+ def files(*path)
326
+ Instance.new(self, "0", @client).files(*path)
327
+ end
328
+
329
+ # Retrieve file contents for the first instance of the application.
330
+ #
331
+ # [path]
332
+ # A sequence of strings representing path segments.
333
+ #
334
+ # For example, <code>files("foo", "bar")</code> for +foo/bar+.
335
+ def file(*path)
336
+ Instance.new(self, "0", @client).file(*path)
337
+ end
338
+
339
+ # Upload application's code to target. Do this after #create! and before
340
+ # #start!
341
+ #
342
+ # [path]
343
+ # A path pointing to either a directory, or a .jar, .war, or .zip
344
+ # file.
345
+ #
346
+ # If a .vmcignore file is detected under the given path, it will be used
347
+ # to exclude paths from the payload, similar to a .gitignore.
348
+ #
349
+ # [check_resources]
350
+ # If set to `false`, the entire payload will be uploaded
351
+ # without checking the resource cache.
352
+ #
353
+ # Only do this if you know what you're doing.
354
+ def upload(path, check_resources = true)
355
+ unless File.exist? path
356
+ raise "invalid application path '#{path}'"
357
+ end
358
+
359
+ zipfile = "#{Dir.tmpdir}/#{@name}.zip"
360
+ tmpdir = "#{Dir.tmpdir}/.vmc_#{@name}_files"
361
+
362
+ FileUtils.rm_f(zipfile)
363
+ FileUtils.rm_rf(tmpdir)
364
+
365
+ prepare_package(path, tmpdir)
366
+
367
+ resources = determine_resources(tmpdir) if check_resources
368
+
369
+ packed = CFoundry::Zip.pack(tmpdir, zipfile)
370
+
371
+ @client.base.upload_app(@name, packed && zipfile, resources || [])
372
+ ensure
373
+ FileUtils.rm_f(zipfile) if zipfile
374
+ FileUtils.rm_rf(tmpdir) if tmpdir
375
+ end
376
+
377
+ private
378
+
379
+ ATTR_MAP = {
380
+ :instances => :instances,
381
+ :state => :state,
382
+ :env => :env,
383
+ :uris => :uris,
384
+ :services => :services,
385
+ :debug => :debug,
386
+ :console => :console,
387
+
388
+ :framework => [:staging, :model],
389
+ :runtime => [:staging, :stack],
390
+ :command => [:staging, :command],
391
+
392
+ :meta_version => [:meta, :version],
393
+ :created => [:meta, :created],
394
+
395
+ :memory => [:resources, :memory],
396
+ :disk => [:resources, :disk],
397
+ :fds => [:resources, :fds]
398
+ }
399
+
400
+ def manifest
401
+ @manifest ||= @client.base.app(@name)
402
+ end
403
+
404
+ def write_manifest(body = read_manifest, onto = {})
405
+ onto[:name] = @name
406
+
407
+ ATTR_MAP.each do |what, where|
408
+ if body.key?(what)
409
+ put(body[what], onto, Array(where))
410
+ end
411
+ end
412
+
413
+ onto
414
+ end
415
+
416
+ def put(what, where, path)
417
+ if path.size == 1
418
+ where[path.last] = what
419
+ elsif name = path.first
420
+ where[name] ||= {}
421
+ put(what, where[name], path[1..-1])
422
+ end
423
+
424
+ nil
425
+ end
426
+
427
+ def update_manifest
428
+ write_manifest(@diff, write_manifest)
429
+ end
430
+
431
+ def create_manifest
432
+ write_manifest(@diff)
433
+ end
434
+
435
+ def read_manifest
436
+ { :name => @name,
437
+ :instances => manifest[:instances],
438
+ :running_instances => manifest[:runningInstances],
439
+ :state => manifest[:state],
440
+ :env => manifest[:env],
441
+ :uris => manifest[:uris],
442
+ :version => manifest[:version],
443
+ :services => manifest[:services],
444
+ :framework => manifest[:staging][:model],
445
+ :runtime => manifest[:staging][:stack],
446
+ :console => manifest[:meta][:console],
447
+ :meta_version => manifest[:meta][:version],
448
+ :debug => manifest[:meta][:debug],
449
+ :created => manifest[:meta][:created],
450
+ :memory => manifest[:resources][:memory],
451
+ :disk => manifest[:resources][:disk],
452
+ :fds => manifest[:resources][:fds]
453
+ }
454
+ end
455
+
456
+ # Minimum size for an application payload to bother checking resources.
457
+ RESOURCE_CHECK_LIMIT = 64 * 1024
458
+
459
+ def determine_resources(path)
460
+ fingerprints, total_size = make_fingerprints(path)
461
+
462
+ return if total_size <= RESOURCE_CHECK_LIMIT
463
+
464
+ resources = @client.base.check_resources(fingerprints)
465
+
466
+ resources.each do |resource|
467
+ FileUtils.rm_f resource[:fn]
468
+ resource[:fn].sub!("#{path}/", "")
469
+ end
470
+
471
+ resources
472
+ end
473
+
474
+ # Class represnting a running instance of an application.
475
+ class Instance
476
+ # The application this instance belongs to.
477
+ attr_reader :app
478
+
479
+ # Application instance number.
480
+ attr_reader :id
481
+
482
+ # Create an Instance object.
483
+ #
484
+ # You'll usually call App#instances instead
485
+ def initialize(app, id, client, manifest = {})
486
+ @app = app
487
+ @id = id
488
+ @client = client
489
+ @manifest = manifest
490
+ end
491
+
492
+ # Show string representing the application instance.
493
+ def inspect
494
+ "#<App::Instance '#{@app.name}' \##@id>"
495
+ end
496
+
497
+ # Instance state.
498
+ def state
499
+ @manifest[:state]
500
+ end
501
+ alias_method :status, :state
502
+
503
+ # Instance start time.
504
+ def since
505
+ Time.at(@manifest[:since])
506
+ end
507
+
508
+ # Instance debugger data. If instance is in debug mode, returns a hash
509
+ # containing :ip and :port keys.
510
+ def debugger
511
+ return unless @manifest[:debug_ip] and @manifest[:debug_port]
512
+
513
+ { :ip => @manifest[:debug_ip],
514
+ :port => @manifest[:debug_port]
515
+ }
516
+ end
517
+
518
+ # Instance console data. If instance has a console, returns a hash
519
+ # containing :ip and :port keys.
520
+ def console
521
+ return unless @manifest[:console_ip] and @manifest[:console_port]
522
+
523
+ { :ip => @manifest[:console_ip],
524
+ :port => @manifest[:console_port]
525
+ }
526
+ end
527
+
528
+ # True if instance is starting or running, false if it's down or
529
+ # flapping.
530
+ def healthy?
531
+ case state
532
+ when "STARTING", "RUNNING"
533
+ true
534
+ when "DOWN", "FLAPPING"
535
+ false
536
+ end
537
+ end
538
+
539
+ # Retrieve file listing under path for this instance.
540
+ #
541
+ # [path]
542
+ # A sequence of strings representing path segments.
543
+ #
544
+ # For example, <code>files("foo", "bar")</code> for +foo/bar+.
545
+ def files(*path)
546
+ @client.base.files(@app.name, @id, *path).split("\n").collect do |entry|
547
+ path + [entry.split(/\s+/, 2)[0]]
548
+ end
549
+ end
550
+
551
+ # Retrieve file contents for this instance.
552
+ #
553
+ # [path]
554
+ # A sequence of strings representing path segments.
555
+ #
556
+ # For example, <code>files("foo", "bar")</code> for +foo/bar+.
557
+ def file(*path)
558
+ @client.base.files(@app.name, @id, *path)
559
+ end
560
+ end
561
+ end
562
+ end