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