cf 0.1.0
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 +30 -0
- data/Rakefile +1 -0
- data/bin/cf +14 -0
- data/lib/cf.rb +2 -0
- data/lib/cf/cli.rb +249 -0
- data/lib/cf/cli/app.rb +545 -0
- data/lib/cf/cli/command.rb +433 -0
- data/lib/cf/cli/dots.rb +130 -0
- data/lib/cf/cli/service.rb +91 -0
- data/lib/cf/cli/user.rb +71 -0
- data/lib/cf/constants.rb +10 -0
- data/lib/cf/detect.rb +60 -0
- data/lib/cf/plugin.rb +24 -0
- data/lib/cf/version.rb +3 -0
- data/plugins/manifests/main.rb +508 -0
- metadata +105 -0
data/LICENSE
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
Copyright (c)2012, Alex Suraci
|
2
|
+
|
3
|
+
All rights reserved.
|
4
|
+
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
7
|
+
|
8
|
+
* Redistributions of source code must retain the above copyright
|
9
|
+
notice, this list of conditions and the following disclaimer.
|
10
|
+
|
11
|
+
* Redistributions in binary form must reproduce the above
|
12
|
+
copyright notice, this list of conditions and the following
|
13
|
+
disclaimer in the documentation and/or other materials provided
|
14
|
+
with the distribution.
|
15
|
+
|
16
|
+
* Neither the name of Alex Suraci nor the names of other
|
17
|
+
contributors may be used to endorse or promote products derived
|
18
|
+
from this software without specific prior written permission.
|
19
|
+
|
20
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
21
|
+
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
22
|
+
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
23
|
+
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
24
|
+
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
25
|
+
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
26
|
+
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
27
|
+
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
28
|
+
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
29
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
30
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/bin/cf
ADDED
data/lib/cf.rb
ADDED
data/lib/cf/cli.rb
ADDED
@@ -0,0 +1,249 @@
|
|
1
|
+
require "cf/cli/command"
|
2
|
+
require "cf/cli/app"
|
3
|
+
require "cf/cli/service"
|
4
|
+
require "cf/cli/user"
|
5
|
+
|
6
|
+
module CF
|
7
|
+
class CLI < App # subclass App since we operate on Apps by default
|
8
|
+
class_option :verbose,
|
9
|
+
:type => :boolean, :aliases => "-v", :desc => "Verbose"
|
10
|
+
|
11
|
+
class_option :force,
|
12
|
+
:type => :boolean, :aliases => "-f", :desc => "Force (no interaction)"
|
13
|
+
|
14
|
+
class_option :simple_output,
|
15
|
+
:type => :boolean, :desc => "Simplified output format."
|
16
|
+
|
17
|
+
class_option :script, :type => :boolean, :aliases => "-s",
|
18
|
+
:desc => "--simple-output and --force"
|
19
|
+
|
20
|
+
class_option :color, :type => :boolean, :desc => "Colored output"
|
21
|
+
|
22
|
+
desc "service SUBCOMMAND ...ARGS", "Manage your services"
|
23
|
+
subcommand "service", Service
|
24
|
+
|
25
|
+
desc "user SUBCOMMAND ...ARGS", "User management"
|
26
|
+
subcommand "user", User
|
27
|
+
|
28
|
+
desc "info", "Display information on the current target, user, et."
|
29
|
+
flag(:runtimes)
|
30
|
+
flag(:services)
|
31
|
+
def info
|
32
|
+
info =
|
33
|
+
with_progress("Getting target information") do
|
34
|
+
client.info
|
35
|
+
end
|
36
|
+
|
37
|
+
if input(:runtimes)
|
38
|
+
runtimes = {}
|
39
|
+
info["frameworks"].each do |_, f|
|
40
|
+
f["runtimes"].each do |r|
|
41
|
+
runtimes[r["name"]] = r
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
runtimes = runtimes.values.sort_by { |x| x["name"] }
|
46
|
+
|
47
|
+
if simple_output?
|
48
|
+
runtimes.each do |r|
|
49
|
+
puts r["name"]
|
50
|
+
end
|
51
|
+
return
|
52
|
+
end
|
53
|
+
|
54
|
+
runtimes.each do |r|
|
55
|
+
puts ""
|
56
|
+
puts "#{c(r["name"], :blue)}:"
|
57
|
+
puts " version: #{b(r["version"])}"
|
58
|
+
puts " description: #{b(r["description"])}"
|
59
|
+
end
|
60
|
+
|
61
|
+
return
|
62
|
+
end
|
63
|
+
|
64
|
+
if input(:services)
|
65
|
+
services = {}
|
66
|
+
client.system_services.each do |_, svcs|
|
67
|
+
svcs.each do |name, versions|
|
68
|
+
services[name] = versions.values
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
if simple_output?
|
73
|
+
services.each do |name, _|
|
74
|
+
puts name
|
75
|
+
end
|
76
|
+
|
77
|
+
return
|
78
|
+
end
|
79
|
+
|
80
|
+
services.each do |name, versions|
|
81
|
+
puts ""
|
82
|
+
puts "#{c(name, :blue)}:"
|
83
|
+
puts " versions: #{versions.collect { |v| v["version"] }.join ", "}"
|
84
|
+
puts " description: #{versions[0]["description"]}"
|
85
|
+
puts " type: #{versions[0]["type"]}"
|
86
|
+
end
|
87
|
+
|
88
|
+
return
|
89
|
+
end
|
90
|
+
|
91
|
+
puts ""
|
92
|
+
|
93
|
+
puts info["description"]
|
94
|
+
puts ""
|
95
|
+
puts "target: #{b(client.target)}"
|
96
|
+
puts " version: #{info["version"]}"
|
97
|
+
puts " support: #{info["support"]}"
|
98
|
+
puts ""
|
99
|
+
puts "user: #{b(info["user"])}"
|
100
|
+
puts " usage:"
|
101
|
+
|
102
|
+
limits = info["limits"]
|
103
|
+
info["usage"].each do |k, v|
|
104
|
+
m = limits[k]
|
105
|
+
if k == "memory"
|
106
|
+
puts " #{k}: #{usage(v * 1024 * 1024, m * 1024 * 1024)}"
|
107
|
+
else
|
108
|
+
puts " #{k}: #{b(v)} of #{b(m)} limit"
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
desc "target URL", "Set target cloud"
|
114
|
+
def target(url)
|
115
|
+
target = sane_target_url(url)
|
116
|
+
display = c(target.sub(/https?:\/\//, ""), :blue)
|
117
|
+
with_progress("Setting target to #{display}") do
|
118
|
+
unless force?
|
119
|
+
# check that the target is valid
|
120
|
+
Conduit::Client.new(target).info
|
121
|
+
end
|
122
|
+
|
123
|
+
set_target(target)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
desc "login [EMAIL]", "Authenticate with the target"
|
128
|
+
flag(:email) {
|
129
|
+
ask("Email")
|
130
|
+
}
|
131
|
+
flag(:password)
|
132
|
+
# TODO: implement new authentication scheme
|
133
|
+
def login(email = nil)
|
134
|
+
unless simple_output?
|
135
|
+
puts "Target: #{c(client_target, :blue)}"
|
136
|
+
puts ""
|
137
|
+
end
|
138
|
+
|
139
|
+
email ||= input(:email)
|
140
|
+
password = input(:password)
|
141
|
+
|
142
|
+
authenticated = false
|
143
|
+
failed = false
|
144
|
+
until authenticated
|
145
|
+
unless force?
|
146
|
+
if failed || !password
|
147
|
+
password = ask("Password", :echo => "*", :forget => true)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
with_progress("Authenticating") do |s|
|
152
|
+
begin
|
153
|
+
save_token(client.login(email, password))
|
154
|
+
authenticated = true
|
155
|
+
rescue Conduit::Denied
|
156
|
+
return if force?
|
157
|
+
|
158
|
+
s.fail do
|
159
|
+
failed = true
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
ensure
|
165
|
+
$exit_status = 1 if not authenticated
|
166
|
+
end
|
167
|
+
|
168
|
+
desc "logout", "Log out from the target"
|
169
|
+
def logout
|
170
|
+
with_progress("Logging out") do
|
171
|
+
remove_token
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
desc "register [EMAIL]", "Create a user and log in"
|
176
|
+
flag(:email) {
|
177
|
+
ask("Email")
|
178
|
+
}
|
179
|
+
flag(:password) {
|
180
|
+
ask("Password", :echo => "*", :forget => true)
|
181
|
+
}
|
182
|
+
flag(:no_login, :type => :boolean)
|
183
|
+
def register(email = nil)
|
184
|
+
unless simple_output?
|
185
|
+
puts "Target: #{c(client_target, :blue)}"
|
186
|
+
puts ""
|
187
|
+
end
|
188
|
+
|
189
|
+
email ||= input(:email)
|
190
|
+
password = input(:password)
|
191
|
+
|
192
|
+
with_progress("Creating user") do
|
193
|
+
client.register(email, password)
|
194
|
+
end
|
195
|
+
|
196
|
+
unless input(:skip_login)
|
197
|
+
with_progress("Logging in") do
|
198
|
+
save_token(client.login(email, password))
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
desc "services", "List your services"
|
204
|
+
def services
|
205
|
+
services =
|
206
|
+
with_progress("Getting services") do
|
207
|
+
client.services
|
208
|
+
end
|
209
|
+
|
210
|
+
puts "" unless simple_output?
|
211
|
+
|
212
|
+
services.each do |s|
|
213
|
+
display_service(s)
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
desc "users", "List all users"
|
218
|
+
def users
|
219
|
+
users =
|
220
|
+
with_progress("Getting users") do
|
221
|
+
client.users
|
222
|
+
end
|
223
|
+
|
224
|
+
users.each do |u|
|
225
|
+
display_user(u)
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
private
|
230
|
+
|
231
|
+
def display_service(s)
|
232
|
+
if simple_output?
|
233
|
+
puts s.name
|
234
|
+
else
|
235
|
+
puts "#{c(s.name, :blue)}: #{s.vendor} v#{s.version}"
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
def display_user(u)
|
240
|
+
if simple_output?
|
241
|
+
puts u.email
|
242
|
+
else
|
243
|
+
puts ""
|
244
|
+
puts "#{c(u.email, :blue)}:"
|
245
|
+
puts " admin?: #{c(u.admin?, u.admin? ? :green : :red)}"
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|
data/lib/cf/cli/app.rb
ADDED
@@ -0,0 +1,545 @@
|
|
1
|
+
require "cf/cli/command"
|
2
|
+
require "cf/detect"
|
3
|
+
|
4
|
+
module CF
|
5
|
+
class App < Command
|
6
|
+
MEM_CHOICES = ["64M", "128M", "256M", "512M"]
|
7
|
+
|
8
|
+
desc "apps", "List your applications"
|
9
|
+
def apps
|
10
|
+
apps =
|
11
|
+
with_progress("Getting applications") do
|
12
|
+
client.apps
|
13
|
+
end
|
14
|
+
|
15
|
+
apps.each.with_index do |a, num|
|
16
|
+
display_app(a)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
desc "stop [APP]", "Stop an application"
|
21
|
+
def stop(name)
|
22
|
+
with_progress("Stopping #{c(name, :blue)}") do |s|
|
23
|
+
app = client.app(name)
|
24
|
+
|
25
|
+
unless app.exists?
|
26
|
+
s.fail do
|
27
|
+
err "Unknown application."
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
if app.stopped?
|
32
|
+
s.skip do
|
33
|
+
err "Application is not running."
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
app.stop!
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
desc "start [APP]", "Start an application"
|
42
|
+
flag(:debug_mode)
|
43
|
+
def start(name)
|
44
|
+
app = client.app(name)
|
45
|
+
|
46
|
+
unless app.exists?
|
47
|
+
err "Unknown application."
|
48
|
+
return
|
49
|
+
end
|
50
|
+
|
51
|
+
switch_mode(app, input(:debug_mode))
|
52
|
+
|
53
|
+
with_progress("Starting #{c(name, :blue)}") do |s|
|
54
|
+
if app.running?
|
55
|
+
s.skip do
|
56
|
+
err "Already started."
|
57
|
+
return
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
app.start!
|
62
|
+
end
|
63
|
+
|
64
|
+
check_application(app)
|
65
|
+
|
66
|
+
if app.debug_mode && !simple_output?
|
67
|
+
puts ""
|
68
|
+
instances(name)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
desc "restart [APP]", "Stop and start an application"
|
73
|
+
flag(:debug_mode)
|
74
|
+
def restart(name)
|
75
|
+
stop(name)
|
76
|
+
start(name)
|
77
|
+
end
|
78
|
+
|
79
|
+
desc "delete [APP]", "Delete an application"
|
80
|
+
flag(:really) { |name|
|
81
|
+
force? || ask("Really delete #{c(name, :blue)}?", :default => false)
|
82
|
+
}
|
83
|
+
def delete(name)
|
84
|
+
return unless input(:really, name)
|
85
|
+
|
86
|
+
with_progress("Deleting #{c(name, :blue)}") do
|
87
|
+
client.app(name).delete!
|
88
|
+
end
|
89
|
+
ensure
|
90
|
+
forget(:really)
|
91
|
+
end
|
92
|
+
|
93
|
+
desc "instances [APP]", "List an app's instances"
|
94
|
+
def instances(name)
|
95
|
+
instances =
|
96
|
+
with_progress("Getting instances for #{c(name, :blue)}") do
|
97
|
+
client.app(name).instances
|
98
|
+
end
|
99
|
+
|
100
|
+
instances.each do |i|
|
101
|
+
if simple_output?
|
102
|
+
puts i.index
|
103
|
+
else
|
104
|
+
puts ""
|
105
|
+
display_instance(i)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
desc "files [APP] [PATH]", "Examine an app's files"
|
111
|
+
def files(name, path = "/")
|
112
|
+
files =
|
113
|
+
with_progress("Getting file listing") do
|
114
|
+
client.app(name).files(*path.split("/"))
|
115
|
+
end
|
116
|
+
|
117
|
+
puts "" unless simple_output?
|
118
|
+
|
119
|
+
files.each do |file|
|
120
|
+
puts file
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
desc "file [APP] [PATH]", "Print out an app's file contents"
|
125
|
+
def file(name, path = "/")
|
126
|
+
file =
|
127
|
+
with_progress("Getting file contents") do
|
128
|
+
client.app(name).file(*path.split("/"))
|
129
|
+
end
|
130
|
+
|
131
|
+
puts "" unless simple_output?
|
132
|
+
|
133
|
+
print file
|
134
|
+
end
|
135
|
+
|
136
|
+
desc "logs [APP]", "Print out an app's logs"
|
137
|
+
flag(:instance, :type => :numeric, :default => 0)
|
138
|
+
def logs(name)
|
139
|
+
app = client.app(name)
|
140
|
+
unless app.exists?
|
141
|
+
err "Unknown application."
|
142
|
+
return
|
143
|
+
end
|
144
|
+
|
145
|
+
instances =
|
146
|
+
if input(:instance) == "all"
|
147
|
+
app.instances
|
148
|
+
else
|
149
|
+
app.instances.select { |i| i.index == input(:instance) }
|
150
|
+
end
|
151
|
+
|
152
|
+
if instances.empty?
|
153
|
+
if input(:instance) == "all"
|
154
|
+
err "No instances found."
|
155
|
+
else
|
156
|
+
err "Instance #{name} \##{input(:instance)} not found."
|
157
|
+
end
|
158
|
+
|
159
|
+
return
|
160
|
+
end
|
161
|
+
|
162
|
+
instances.each do |i|
|
163
|
+
logs =
|
164
|
+
with_progress(
|
165
|
+
"Getting logs for " +
|
166
|
+
c(name, :blue) + " " +
|
167
|
+
c("\##{i.index}", :yellow)) do
|
168
|
+
i.files("logs")
|
169
|
+
end
|
170
|
+
|
171
|
+
puts "" unless simple_output?
|
172
|
+
|
173
|
+
logs.each do |log|
|
174
|
+
body =
|
175
|
+
with_progress("Reading " + b(log.join("/"))) do
|
176
|
+
i.file(*log)
|
177
|
+
end
|
178
|
+
|
179
|
+
puts body
|
180
|
+
puts "" unless body.empty?
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
desc "push [NAME]", "Push an application, syncing changes if it exists"
|
186
|
+
flag(:name) { ask("Name") }
|
187
|
+
flag(:path) {
|
188
|
+
ask("Push from...", :default => ".")
|
189
|
+
}
|
190
|
+
flag(:url) { |name, target|
|
191
|
+
ask("URL", :default => "#{name}.#{target}")
|
192
|
+
}
|
193
|
+
flag(:memory) {
|
194
|
+
ask("Memory Limit",
|
195
|
+
:choices => MEM_CHOICES,
|
196
|
+
|
197
|
+
# TODO: base this on framework choice
|
198
|
+
:default => "64M")
|
199
|
+
}
|
200
|
+
flag(:instances) {
|
201
|
+
ask("Instances", :default => 1)
|
202
|
+
}
|
203
|
+
flag(:framework) { |choices, default|
|
204
|
+
ask("Framework", :choices => choices, :default => default)
|
205
|
+
}
|
206
|
+
flag(:runtime) { |choices|
|
207
|
+
ask("Runtime", :choices => choices)
|
208
|
+
}
|
209
|
+
flag(:start, :default => true)
|
210
|
+
flag(:restart, :default => true)
|
211
|
+
def push(name = nil)
|
212
|
+
path = File.expand_path(input(:path))
|
213
|
+
|
214
|
+
name ||= input(:name)
|
215
|
+
|
216
|
+
detector = Detector.new(client, path)
|
217
|
+
frameworks = detector.all_frameworks
|
218
|
+
detected, default = detector.frameworks
|
219
|
+
|
220
|
+
app = client.app(name)
|
221
|
+
|
222
|
+
if app.exists?
|
223
|
+
upload_app(app, path)
|
224
|
+
restart(app.name) if input(:restart)
|
225
|
+
return
|
226
|
+
end
|
227
|
+
|
228
|
+
app.total_instances = input(:instances)
|
229
|
+
|
230
|
+
domain = client.target.sub(/^https?:\/\/api\.(.+)\/?/, '\1')
|
231
|
+
app.urls = [input(:url, name, domain)]
|
232
|
+
|
233
|
+
framework = input(:framework, ["other"] + detected.keys, default)
|
234
|
+
if framework == "other"
|
235
|
+
forget(:framework)
|
236
|
+
framework = input(:framework, frameworks.keys)
|
237
|
+
end
|
238
|
+
|
239
|
+
framework_runtimes =
|
240
|
+
frameworks[framework]["runtimes"].collect do |k|
|
241
|
+
"#{k["name"]} (#{k["description"]})"
|
242
|
+
end
|
243
|
+
|
244
|
+
# TODO: include descriptions
|
245
|
+
runtime = input(:runtime, framework_runtimes).split.first
|
246
|
+
|
247
|
+
app.framework = framework
|
248
|
+
app.runtime = runtime
|
249
|
+
|
250
|
+
app.memory = megabytes(input(:memory))
|
251
|
+
|
252
|
+
with_progress("Creating #{c(name, :blue)}") do
|
253
|
+
app.create!
|
254
|
+
end
|
255
|
+
|
256
|
+
begin
|
257
|
+
upload_app(app, path)
|
258
|
+
rescue
|
259
|
+
err "Upload failed. Try again with 'vmc push'."
|
260
|
+
raise
|
261
|
+
end
|
262
|
+
|
263
|
+
start(name) if input(:start)
|
264
|
+
end
|
265
|
+
|
266
|
+
desc "update", "DEPRECATED", :hide => true
|
267
|
+
def update(*args)
|
268
|
+
err "The 'update' command is no longer used; use 'push' instead."
|
269
|
+
end
|
270
|
+
|
271
|
+
desc "stats", "Display application instance status"
|
272
|
+
def stats(name)
|
273
|
+
stats =
|
274
|
+
with_progress("Getting stats") do
|
275
|
+
client.app(name).stats
|
276
|
+
end
|
277
|
+
|
278
|
+
stats.sort_by { |k, _| k }.each do |idx, info|
|
279
|
+
stats = info["stats"]
|
280
|
+
usage = stats["usage"]
|
281
|
+
puts ""
|
282
|
+
puts "instance #{c("#" + idx, :blue)}:"
|
283
|
+
print " cpu: #{percentage(usage["cpu"])} of"
|
284
|
+
puts " #{b(stats["cores"])} cores"
|
285
|
+
puts " memory: #{usage(usage["mem"] * 1024, stats["mem_quota"])}"
|
286
|
+
puts " disk: #{usage(usage["disk"], stats["disk_quota"])}"
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
desc "scale [APP]", "Update the instances/memory limit for an application"
|
291
|
+
flag(:instances, :type => :numeric) { |default|
|
292
|
+
ask("Instances", :default => default)
|
293
|
+
}
|
294
|
+
flag(:memory) { |default|
|
295
|
+
ask("Memory Limit",
|
296
|
+
:default => human_size(default * 1024 * 1024, 0),
|
297
|
+
:choices => MEM_CHOICES)
|
298
|
+
}
|
299
|
+
def scale(name)
|
300
|
+
app = client.app(name)
|
301
|
+
|
302
|
+
instances = passed_value(:instances)
|
303
|
+
memory = passed_value(:memory)
|
304
|
+
|
305
|
+
unless instances || memory
|
306
|
+
instances = input(:instances, app.total_instances)
|
307
|
+
memory = input(:memory, app.memory)
|
308
|
+
end
|
309
|
+
|
310
|
+
with_progress("Scaling #{c(name, :blue)}") do
|
311
|
+
app.total_instances = instances.to_i if instances
|
312
|
+
app.memory = megabytes(memory) if memory
|
313
|
+
app.update!
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
desc "map NAME URL", "Add a URL mapping for an app"
|
318
|
+
def map(name, url)
|
319
|
+
simple = url.sub(/^https?:\/\/(.*)\/?/i, '\1')
|
320
|
+
|
321
|
+
with_progress("Updating #{c(name, :blue)}") do
|
322
|
+
app = client.app(name)
|
323
|
+
app.urls << simple
|
324
|
+
app.update!
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
328
|
+
desc "unmap NAME URL", "Remove a URL mapping from an app"
|
329
|
+
def unmap(name, url)
|
330
|
+
simple = url.sub(/^https?:\/\/(.*)\/?/i, '\1')
|
331
|
+
|
332
|
+
with_progress("Updating #{c(name, :blue)}") do |s|
|
333
|
+
app = client.app(name)
|
334
|
+
|
335
|
+
unless app.urls.delete(simple)
|
336
|
+
s.fail do
|
337
|
+
err "URL #{url} is not mapped to this application."
|
338
|
+
return
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
app.update!
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
class Env < Command
|
347
|
+
VALID_NAME = /^[a-zA-Za-z_][[:alnum:]_]*$/
|
348
|
+
|
349
|
+
desc "set [APP] [NAME] [VALUE]", "Set an environment variable"
|
350
|
+
def set(appname, name, value)
|
351
|
+
app = client.app(appname)
|
352
|
+
unless name =~ VALID_NAME
|
353
|
+
err "Invalid variable name; must match #{VALID_NAME.inspect}"
|
354
|
+
return
|
355
|
+
end
|
356
|
+
|
357
|
+
unless app.exists?
|
358
|
+
err "Unknown application."
|
359
|
+
return
|
360
|
+
end
|
361
|
+
|
362
|
+
with_progress("Updating #{c(app.name, :blue)}") do
|
363
|
+
app.update!("env" =>
|
364
|
+
app.env.reject { |v|
|
365
|
+
v.start_with?("#{name}=")
|
366
|
+
}.push("#{name}=#{value}"))
|
367
|
+
end
|
368
|
+
end
|
369
|
+
|
370
|
+
desc "unset [APP] [NAME]", "Remove an environment variable"
|
371
|
+
def unset(appname, name)
|
372
|
+
app = client.app(appname)
|
373
|
+
|
374
|
+
unless app.exists?
|
375
|
+
err "Unknown application."
|
376
|
+
return
|
377
|
+
end
|
378
|
+
|
379
|
+
with_progress("Updating #{c(app.name, :blue)}") do
|
380
|
+
app.update!("env" =>
|
381
|
+
app.env.reject { |v|
|
382
|
+
v.start_with?("#{name}=")
|
383
|
+
})
|
384
|
+
end
|
385
|
+
end
|
386
|
+
|
387
|
+
desc "list [APP]", "Show all environment variables set for an app"
|
388
|
+
def list(appname)
|
389
|
+
vars =
|
390
|
+
with_progress("Getting variables") do |s|
|
391
|
+
app = client.app(appname)
|
392
|
+
|
393
|
+
unless app.exists?
|
394
|
+
s.fail do
|
395
|
+
err "Unknown application."
|
396
|
+
return
|
397
|
+
end
|
398
|
+
end
|
399
|
+
|
400
|
+
app.env
|
401
|
+
end
|
402
|
+
|
403
|
+
puts "" unless simple_output?
|
404
|
+
|
405
|
+
vars.each do |pair|
|
406
|
+
name, val = pair.split("=", 2)
|
407
|
+
puts "#{c(name, :blue)}: #{val}"
|
408
|
+
end
|
409
|
+
end
|
410
|
+
end
|
411
|
+
|
412
|
+
desc "env SUBCOMMAND ...ARGS", "Manage application environment variables"
|
413
|
+
subcommand "env", Env
|
414
|
+
|
415
|
+
private
|
416
|
+
|
417
|
+
def upload_app(app, path)
|
418
|
+
with_progress("Uploading #{c(app.name, :blue)}") do
|
419
|
+
app.upload(path)
|
420
|
+
end
|
421
|
+
end
|
422
|
+
|
423
|
+
# set app debug mode, ensuring it's valid, and shutting it down
|
424
|
+
def switch_mode(app, mode)
|
425
|
+
mode = nil if mode == "none"
|
426
|
+
|
427
|
+
return false if app.debug_mode == mode
|
428
|
+
|
429
|
+
if mode.nil?
|
430
|
+
with_progress("Removing debug mode") do
|
431
|
+
app.debug_mode = nil
|
432
|
+
app.stop! if app.running?
|
433
|
+
end
|
434
|
+
|
435
|
+
return true
|
436
|
+
end
|
437
|
+
|
438
|
+
with_progress("Switching mode to #{c(mode, :blue)}") do |s|
|
439
|
+
runtimes = client.system_runtimes
|
440
|
+
modes = runtimes[app.runtime]["debug_modes"] || []
|
441
|
+
if modes.include?(mode)
|
442
|
+
app.debug_mode = mode
|
443
|
+
app.stop! if app.running?
|
444
|
+
true
|
445
|
+
else
|
446
|
+
s.fail do
|
447
|
+
err "Unknown mode '#{mode}'; available: #{modes.inspect}"
|
448
|
+
false
|
449
|
+
end
|
450
|
+
end
|
451
|
+
end
|
452
|
+
end
|
453
|
+
|
454
|
+
APP_CHECK_LIMIT = 60
|
455
|
+
|
456
|
+
def check_application(app)
|
457
|
+
with_progress("Checking #{c(app.name, :blue)}") do |s|
|
458
|
+
if app.debug_mode == "suspend"
|
459
|
+
s.skip do
|
460
|
+
puts "Application is in suspended debugging mode."
|
461
|
+
puts "It will wait for you to attach to it before starting."
|
462
|
+
end
|
463
|
+
end
|
464
|
+
|
465
|
+
seconds = 0
|
466
|
+
until app.healthy?
|
467
|
+
sleep 1
|
468
|
+
seconds += 1
|
469
|
+
if seconds == APP_CHECK_LIMIT
|
470
|
+
s.give_up do
|
471
|
+
err "Application failed to start."
|
472
|
+
# TODO: print logs
|
473
|
+
end
|
474
|
+
end
|
475
|
+
end
|
476
|
+
end
|
477
|
+
end
|
478
|
+
|
479
|
+
# choose the right color for app/instance state
|
480
|
+
def state_color(s)
|
481
|
+
case s
|
482
|
+
when "STARTING"
|
483
|
+
:blue
|
484
|
+
when "STARTED", "RUNNING"
|
485
|
+
:green
|
486
|
+
when "DOWN"
|
487
|
+
:red
|
488
|
+
when "FLAPPING"
|
489
|
+
:magenta
|
490
|
+
when "N/A"
|
491
|
+
:cyan
|
492
|
+
else
|
493
|
+
:yellow
|
494
|
+
end
|
495
|
+
end
|
496
|
+
|
497
|
+
def display_app(a)
|
498
|
+
if simple_output?
|
499
|
+
puts a.name
|
500
|
+
return
|
501
|
+
end
|
502
|
+
|
503
|
+
puts ""
|
504
|
+
|
505
|
+
health = a.health
|
506
|
+
|
507
|
+
if a.debug_mode == "suspend" && health == "0%"
|
508
|
+
status = c("suspended", :yellow)
|
509
|
+
else
|
510
|
+
status = c(health.downcase, state_color(health))
|
511
|
+
end
|
512
|
+
|
513
|
+
print "#{c(a.name, :blue)}: #{status}"
|
514
|
+
|
515
|
+
unless a.total_instances == 1
|
516
|
+
print ", #{b(a.total_instances)} instances"
|
517
|
+
end
|
518
|
+
|
519
|
+
puts ""
|
520
|
+
|
521
|
+
unless a.urls.empty?
|
522
|
+
puts " urls: #{a.urls.collect { |u| b(u) }.join(", ")}"
|
523
|
+
end
|
524
|
+
|
525
|
+
unless a.services.empty?
|
526
|
+
puts " services: #{a.services.collect { |s| b(s) }.join(", ")}"
|
527
|
+
end
|
528
|
+
end
|
529
|
+
|
530
|
+
def display_instance(i)
|
531
|
+
print "instance #{c("\##{i.index}", :blue)}: "
|
532
|
+
puts "#{b(c(i.state.downcase, state_color(i.state)))} "
|
533
|
+
|
534
|
+
puts " started: #{c(i.since.strftime("%F %r"), :cyan)}"
|
535
|
+
|
536
|
+
if d = i.debugger
|
537
|
+
puts " debugger: port #{c(d["port"], :blue)} at #{c(d["ip"], :blue)}"
|
538
|
+
end
|
539
|
+
|
540
|
+
if c = i.console
|
541
|
+
puts " console: port #{b(c["port"])} at #{b(c["ip"])}"
|
542
|
+
end
|
543
|
+
end
|
544
|
+
end
|
545
|
+
end
|