cfoundry-IronFoundry 0.3.34
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +746 -0
- data/Rakefile +40 -0
- data/lib/cfoundry.rb +2 -0
- data/lib/cfoundry/baseclient.rb +235 -0
- data/lib/cfoundry/chatty_hash.rb +34 -0
- data/lib/cfoundry/client.rb +25 -0
- data/lib/cfoundry/errors.rb +106 -0
- data/lib/cfoundry/uaaclient.rb +111 -0
- data/lib/cfoundry/upload_helpers.rb +100 -0
- data/lib/cfoundry/v1/app.rb +562 -0
- data/lib/cfoundry/v1/base.rb +209 -0
- data/lib/cfoundry/v1/client.rb +232 -0
- data/lib/cfoundry/v1/framework.rb +21 -0
- data/lib/cfoundry/v1/runtime.rb +20 -0
- data/lib/cfoundry/v1/service.rb +25 -0
- data/lib/cfoundry/v1/service_instance.rb +112 -0
- data/lib/cfoundry/v1/user.rb +89 -0
- data/lib/cfoundry/v2/app.rb +328 -0
- data/lib/cfoundry/v2/base.rb +175 -0
- data/lib/cfoundry/v2/client.rb +198 -0
- data/lib/cfoundry/v2/domain.rb +8 -0
- data/lib/cfoundry/v2/framework.rb +12 -0
- data/lib/cfoundry/v2/model.rb +268 -0
- data/lib/cfoundry/v2/organization.rb +13 -0
- data/lib/cfoundry/v2/route.rb +9 -0
- data/lib/cfoundry/v2/runtime.rb +9 -0
- data/lib/cfoundry/v2/service.rb +17 -0
- data/lib/cfoundry/v2/service_auth_token.rb +9 -0
- data/lib/cfoundry/v2/service_binding.rb +8 -0
- data/lib/cfoundry/v2/service_instance.rb +10 -0
- data/lib/cfoundry/v2/service_plan.rb +10 -0
- data/lib/cfoundry/v2/space.rb +14 -0
- data/lib/cfoundry/v2/user.rb +58 -0
- data/lib/cfoundry/version.rb +4 -0
- data/lib/cfoundry/zip.rb +56 -0
- data/spec/Rakefile +14 -0
- data/spec/client_spec.rb +206 -0
- data/spec/helpers.rb +29 -0
- metadata +169 -0
@@ -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
|