pbox 1.17.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/COPYRIGHT +1 -0
- data/LICENSE +11 -0
- data/README.md +40 -0
- data/Rakefile +6 -0
- data/autocomplete/pbox_bash +1639 -0
- data/bin/pbox +37 -0
- data/conf/protonbox.conf +8 -0
- data/features/assets/deploy.tar.gz +0 -0
- data/features/core_feature.rb +178 -0
- data/features/deployments_feature.rb +127 -0
- data/features/domains_feature.rb +49 -0
- data/features/keys_feature.rb +37 -0
- data/features/members_feature.rb +166 -0
- data/lib/rhc/auth/basic.rb +64 -0
- data/lib/rhc/auth/token.rb +102 -0
- data/lib/rhc/auth/token_store.rb +53 -0
- data/lib/rhc/auth.rb +5 -0
- data/lib/rhc/autocomplete.rb +66 -0
- data/lib/rhc/autocomplete_templates/bash.erb +39 -0
- data/lib/rhc/cartridge_helpers.rb +118 -0
- data/lib/rhc/cli.rb +40 -0
- data/lib/rhc/command_runner.rb +186 -0
- data/lib/rhc/commands/account.rb +25 -0
- data/lib/rhc/commands/alias.rb +124 -0
- data/lib/rhc/commands/app.rb +701 -0
- data/lib/rhc/commands/apps.rb +20 -0
- data/lib/rhc/commands/authorization.rb +96 -0
- data/lib/rhc/commands/base.rb +174 -0
- data/lib/rhc/commands/cartridge.rb +326 -0
- data/lib/rhc/commands/deployment.rb +82 -0
- data/lib/rhc/commands/domain.rb +167 -0
- data/lib/rhc/commands/env.rb +142 -0
- data/lib/rhc/commands/git_clone.rb +29 -0
- data/lib/rhc/commands/logout.rb +51 -0
- data/lib/rhc/commands/member.rb +148 -0
- data/lib/rhc/commands/port_forward.rb +197 -0
- data/lib/rhc/commands/server.rb +40 -0
- data/lib/rhc/commands/setup.rb +60 -0
- data/lib/rhc/commands/snapshot.rb +137 -0
- data/lib/rhc/commands/ssh.rb +51 -0
- data/lib/rhc/commands/sshkey.rb +97 -0
- data/lib/rhc/commands/tail.rb +47 -0
- data/lib/rhc/commands/threaddump.rb +14 -0
- data/lib/rhc/commands.rb +396 -0
- data/lib/rhc/config.rb +320 -0
- data/lib/rhc/context_helper.rb +121 -0
- data/lib/rhc/core_ext.rb +202 -0
- data/lib/rhc/coverage_helper.rb +33 -0
- data/lib/rhc/deployment_helpers.rb +88 -0
- data/lib/rhc/exceptions.rb +232 -0
- data/lib/rhc/git_helpers.rb +91 -0
- data/lib/rhc/help_formatter.rb +55 -0
- data/lib/rhc/helpers.rb +477 -0
- data/lib/rhc/highline_extensions.rb +479 -0
- data/lib/rhc/json.rb +51 -0
- data/lib/rhc/output_helpers.rb +260 -0
- data/lib/rhc/rest/activation.rb +11 -0
- data/lib/rhc/rest/alias.rb +42 -0
- data/lib/rhc/rest/api.rb +87 -0
- data/lib/rhc/rest/application.rb +332 -0
- data/lib/rhc/rest/attributes.rb +36 -0
- data/lib/rhc/rest/authorization.rb +8 -0
- data/lib/rhc/rest/base.rb +79 -0
- data/lib/rhc/rest/cartridge.rb +154 -0
- data/lib/rhc/rest/client.rb +650 -0
- data/lib/rhc/rest/deployment.rb +18 -0
- data/lib/rhc/rest/domain.rb +98 -0
- data/lib/rhc/rest/environment_variable.rb +15 -0
- data/lib/rhc/rest/gear_group.rb +16 -0
- data/lib/rhc/rest/httpclient.rb +145 -0
- data/lib/rhc/rest/key.rb +44 -0
- data/lib/rhc/rest/membership.rb +105 -0
- data/lib/rhc/rest/mock.rb +1024 -0
- data/lib/rhc/rest/user.rb +32 -0
- data/lib/rhc/rest.rb +148 -0
- data/lib/rhc/ssh_helpers.rb +378 -0
- data/lib/rhc/tar_gz.rb +51 -0
- data/lib/rhc/usage_templates/command_help.erb +51 -0
- data/lib/rhc/usage_templates/command_syntax_help.erb +11 -0
- data/lib/rhc/usage_templates/help.erb +35 -0
- data/lib/rhc/usage_templates/missing_help.erb +1 -0
- data/lib/rhc/usage_templates/options_help.erb +12 -0
- data/lib/rhc/vendor/okjson.rb +600 -0
- data/lib/rhc/vendor/parseconfig.rb +178 -0
- data/lib/rhc/vendor/sshkey.rb +253 -0
- data/lib/rhc/vendor/zliby.rb +628 -0
- data/lib/rhc/version.rb +5 -0
- data/lib/rhc/wizard.rb +633 -0
- data/lib/rhc.rb +34 -0
- data/spec/coverage_helper.rb +89 -0
- data/spec/direct_execution_helper.rb +338 -0
- data/spec/keys/example.pem +23 -0
- data/spec/keys/example_private.pem +27 -0
- data/spec/keys/server.pem +19 -0
- data/spec/rest_spec_helper.rb +31 -0
- data/spec/rhc/assets/cert.crt +22 -0
- data/spec/rhc/assets/cert_key_rsa +27 -0
- data/spec/rhc/assets/empty.txt +0 -0
- data/spec/rhc/assets/env_vars.txt +7 -0
- data/spec/rhc/assets/env_vars_2.txt +1 -0
- data/spec/rhc/assets/foo.txt +1 -0
- data/spec/rhc/assets/targz_corrupted.tar.gz +1 -0
- data/spec/rhc/assets/targz_sample.tar.gz +0 -0
- data/spec/rhc/auth_spec.rb +442 -0
- data/spec/rhc/cli_spec.rb +188 -0
- data/spec/rhc/command_spec.rb +435 -0
- data/spec/rhc/commands/account_spec.rb +42 -0
- data/spec/rhc/commands/alias_spec.rb +333 -0
- data/spec/rhc/commands/app_spec.rb +754 -0
- data/spec/rhc/commands/apps_spec.rb +39 -0
- data/spec/rhc/commands/authorization_spec.rb +145 -0
- data/spec/rhc/commands/cartridge_spec.rb +641 -0
- data/spec/rhc/commands/deployment_spec.rb +286 -0
- data/spec/rhc/commands/domain_spec.rb +383 -0
- data/spec/rhc/commands/env_spec.rb +493 -0
- data/spec/rhc/commands/git_clone_spec.rb +80 -0
- data/spec/rhc/commands/logout_spec.rb +86 -0
- data/spec/rhc/commands/member_spec.rb +228 -0
- data/spec/rhc/commands/port_forward_spec.rb +217 -0
- data/spec/rhc/commands/server_spec.rb +69 -0
- data/spec/rhc/commands/setup_spec.rb +118 -0
- data/spec/rhc/commands/snapshot_spec.rb +179 -0
- data/spec/rhc/commands/ssh_spec.rb +163 -0
- data/spec/rhc/commands/sshkey_spec.rb +188 -0
- data/spec/rhc/commands/tail_spec.rb +81 -0
- data/spec/rhc/commands/threaddump_spec.rb +84 -0
- data/spec/rhc/config_spec.rb +407 -0
- data/spec/rhc/helpers_spec.rb +524 -0
- data/spec/rhc/highline_extensions_spec.rb +314 -0
- data/spec/rhc/json_spec.rb +30 -0
- data/spec/rhc/rest_application_spec.rb +248 -0
- data/spec/rhc/rest_client_spec.rb +752 -0
- data/spec/rhc/rest_spec.rb +740 -0
- data/spec/rhc/targz_spec.rb +55 -0
- data/spec/rhc/wizard_spec.rb +756 -0
- data/spec/spec_helper.rb +575 -0
- data/spec/wizard_spec_helper.rb +330 -0
- metadata +435 -0
@@ -0,0 +1,701 @@
|
|
1
|
+
require 'rhc/commands/base'
|
2
|
+
require 'resolv'
|
3
|
+
require 'rhc/git_helpers'
|
4
|
+
require 'rhc/cartridge_helpers'
|
5
|
+
require 'rhc/deployment_helpers'
|
6
|
+
|
7
|
+
module RHC::Commands
|
8
|
+
class App < Base
|
9
|
+
summary "Commands for creating and managing applications"
|
10
|
+
description <<-DESC
|
11
|
+
Creates and controls an ProtonBox application. To see the list of all
|
12
|
+
applications use the rhc domain show command. Note that delete is not
|
13
|
+
reversible and will stop your application and then remove the application
|
14
|
+
and repo from the remote server. No local changes are made.
|
15
|
+
DESC
|
16
|
+
syntax "<action>"
|
17
|
+
default_action :help
|
18
|
+
suppress_wizard
|
19
|
+
|
20
|
+
summary "Create an application"
|
21
|
+
description <<-DESC
|
22
|
+
Create an application. Every ProtonBox application must have one
|
23
|
+
web cartridge which serves web requests, and can have a number of
|
24
|
+
other cartridges which provide capabilities like databases,
|
25
|
+
scheduled jobs, or continuous integration.
|
26
|
+
|
27
|
+
You can see a list of all valid cartridge types by running
|
28
|
+
'pbox cartridge list'. ProtonBox also supports downloading cartridges -
|
29
|
+
pass a URL in place of the cartridge name and we'll download
|
30
|
+
and install that cartridge into your app. Keep in mind that
|
31
|
+
these cartridges receive no security updates. Note that not
|
32
|
+
all ProtonBox servers allow downloaded cartridges.
|
33
|
+
|
34
|
+
When your application is created, a URL combining the name of
|
35
|
+
your app and the name of your domain will be registered in DNS.
|
36
|
+
A copy of the code for your application will be checked out locally
|
37
|
+
into a folder with the same name as your application. Note that
|
38
|
+
different types of applications may require different folder
|
39
|
+
structures - check the README provided with the cartridge if
|
40
|
+
you have questions.
|
41
|
+
|
42
|
+
ProtonBox runs the components of your application on small virtual
|
43
|
+
servers called "gears". Each account or plan is limited to a number
|
44
|
+
of gears which you can use across multiple applications. Some
|
45
|
+
accounts or plans provide access to gears with more memory or more
|
46
|
+
CPU. Run 'pbox account' to see the number and sizes of gears available
|
47
|
+
to you. When creating an application the --gear-size parameter
|
48
|
+
may be specified to change the gears used.
|
49
|
+
|
50
|
+
DESC
|
51
|
+
syntax "<name> <cartridge> [... <cartridge>] [... VARIABLE=VALUE] [-n namespace]"
|
52
|
+
option ["-n", "--namespace NAME"], "Namespace for the application"
|
53
|
+
option ["-g", "--gear-size SIZE"], "Gear size controls how much memory and CPU your cartridges can use."
|
54
|
+
option ["-s", "--scaling"], "Enable scaling for the web cartridge."
|
55
|
+
option ["-r", "--repo DIR"], "Path to the Git repository (defaults to ./$app_name)"
|
56
|
+
option ["-e", "--env VARIABLE=VALUE"], "Environment variable(s) to be set on this app, or path to a file containing environment variables", :type => :list
|
57
|
+
option ["--from-code URL"], "URL to a Git repository that will become the initial contents of the application"
|
58
|
+
option ["--[no-]git"], "Skip creating the local Git repository."
|
59
|
+
option ["--[no-]dns"], "Skip waiting for the application DNS name to resolve. Must be used in combination with --no-git"
|
60
|
+
option ['--no-keys'], "Skip checking SSH keys during app creation", :hide => true
|
61
|
+
option ["--enable-jenkins [NAME]"], "Enable Jenkins builds for this application (will create a Jenkins application if not already available). The default name will be 'jenkins' if not specified."
|
62
|
+
argument :name, "Name for your application", ["-a", "--app NAME"], :optional => true
|
63
|
+
argument :cartridges, "The web framework this application should use", ["-t", "--type CARTRIDGE"], :optional => true, :type => :list
|
64
|
+
def create(name, cartridges)
|
65
|
+
check_config!
|
66
|
+
|
67
|
+
check_name!(name)
|
68
|
+
|
69
|
+
arg_envs, cartridges = cartridges.partition{|item| item.match(env_var_regex_pattern)}
|
70
|
+
|
71
|
+
cartridges = check_cartridges(cartridges, &require_one_web_cart)
|
72
|
+
|
73
|
+
options.default \
|
74
|
+
:dns => true,
|
75
|
+
:git => true
|
76
|
+
|
77
|
+
raise ArgumentError, "You have named both your main application and your Jenkins application '#{name}'. In order to continue you'll need to specify a different name with --enable-jenkins or choose a different application name." if jenkins_app_name == name && enable_jenkins?
|
78
|
+
|
79
|
+
rest_domain = check_domain!
|
80
|
+
rest_app = nil
|
81
|
+
repo_dir = nil
|
82
|
+
|
83
|
+
cart_names = cartridges.collect do |c|
|
84
|
+
c.usage_rate? ? "#{c.short_name} (addtl. costs may apply)" : c.short_name
|
85
|
+
end.join(', ')
|
86
|
+
|
87
|
+
env = collect_env_vars(arg_envs.concat(Array(options.env)))
|
88
|
+
if env.present? && !rest_domain.supports_add_application_with_env_vars?
|
89
|
+
env = []
|
90
|
+
warn "Server does not support environment variables."
|
91
|
+
end
|
92
|
+
|
93
|
+
paragraph do
|
94
|
+
header "Application Options"
|
95
|
+
table([["Domain:", options.namespace],
|
96
|
+
["Cartridges:", cart_names],
|
97
|
+
(["Source Code:", options.from_code] if options.from_code),
|
98
|
+
["Gear Size:", options.gear_size || "default"],
|
99
|
+
["Scaling:", options.scaling ? "yes" : "no"],
|
100
|
+
(["Environment Variables:", env.map{|item| "#{item.name}=#{item.value}"}.join(', ')] if env.present?),
|
101
|
+
].compact
|
102
|
+
).each { |s| say " #{s}" }
|
103
|
+
end
|
104
|
+
|
105
|
+
paragraph do
|
106
|
+
say "Creating application '#{name}' ... "
|
107
|
+
|
108
|
+
# create the main app
|
109
|
+
rest_app = create_app(name, cartridges, rest_domain, options.gear_size, options.scaling, options.from_code, env, options.auto_deploy, options.keep_deployments, options.deployment_branch)
|
110
|
+
success "done"
|
111
|
+
|
112
|
+
paragraph{ indent{ success rest_app.messages.map(&:strip) } }
|
113
|
+
end
|
114
|
+
|
115
|
+
build_app_exists = rest_app.building_app
|
116
|
+
|
117
|
+
if enable_jenkins?
|
118
|
+
|
119
|
+
unless build_app_exists
|
120
|
+
paragraph do
|
121
|
+
say "Setting up a Jenkins application ... "
|
122
|
+
|
123
|
+
begin
|
124
|
+
build_app_exists = add_jenkins_app(rest_domain)
|
125
|
+
|
126
|
+
success "done"
|
127
|
+
paragraph{ indent{ success build_app_exists.messages.map(&:strip) } }
|
128
|
+
|
129
|
+
rescue Exception => e
|
130
|
+
warn "not complete"
|
131
|
+
add_issue("Jenkins failed to install - #{e}",
|
132
|
+
"Installing jenkins and jenkins-client",
|
133
|
+
"pbox create-app jenkins",
|
134
|
+
"pbox add-cartridge jenkins-client -a #{rest_app.name}")
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
paragraph do
|
140
|
+
messages = []
|
141
|
+
add_jenkins_client_to(rest_app, messages)
|
142
|
+
paragraph{ indent{ success messages.map(&:strip) } }
|
143
|
+
end if build_app_exists
|
144
|
+
end
|
145
|
+
|
146
|
+
debug "Checking SSH keys through the wizard"
|
147
|
+
check_sshkeys! unless options.no_keys
|
148
|
+
|
149
|
+
if options.dns
|
150
|
+
paragraph do
|
151
|
+
say "Waiting for your DNS name to be available ... "
|
152
|
+
if dns_propagated? rest_app.host
|
153
|
+
success "done"
|
154
|
+
else
|
155
|
+
warn "failure"
|
156
|
+
add_issue("We were unable to lookup your hostname (#{rest_app.host}) in a reasonable amount of time and can not clone your application.",
|
157
|
+
"Clone your git repo",
|
158
|
+
"pbox git-clone #{rest_app.name}")
|
159
|
+
|
160
|
+
output_issues(rest_app)
|
161
|
+
return 0
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
if options.git
|
166
|
+
section(:now => true, :top => 1, :bottom => 1) do
|
167
|
+
begin
|
168
|
+
repo_dir = git_clone_application(rest_app)
|
169
|
+
rescue RHC::GitException => e
|
170
|
+
warn "#{e}"
|
171
|
+
unless RHC::Helpers.windows? and windows_nslookup_bug?(rest_app)
|
172
|
+
add_issue("We were unable to clone your application's git repo - #{e}",
|
173
|
+
"Clone your git repo",
|
174
|
+
"pbox git-clone #{rest_app.name}")
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
output_issues(rest_app) if issues?
|
182
|
+
|
183
|
+
paragraph do
|
184
|
+
say "Your application '#{rest_app.name}' is now available."
|
185
|
+
paragraph do
|
186
|
+
indent do
|
187
|
+
say table [
|
188
|
+
['URL:', rest_app.app_url],
|
189
|
+
['SSH to:', rest_app.ssh_string],
|
190
|
+
['Git remote:', rest_app.git_url],
|
191
|
+
(['Cloned to:', repo_dir] if repo_dir)
|
192
|
+
].compact
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
paragraph{ say "Run 'pbox show-app #{name}' for more details about your app." }
|
197
|
+
|
198
|
+
0
|
199
|
+
end
|
200
|
+
|
201
|
+
|
202
|
+
summary "Delete an application from the server"
|
203
|
+
description "Deletes your application and all of its data from the server.",
|
204
|
+
"Use with caution as this operation is permanent."
|
205
|
+
syntax "<app> [--namespace NAME]"
|
206
|
+
takes_application :argument => true
|
207
|
+
option ["--confirm"], "Pass to confirm deleting the application"
|
208
|
+
alias_action :destroy, :deprecated => true
|
209
|
+
def delete(app)
|
210
|
+
rest_app = find_app
|
211
|
+
|
212
|
+
confirm_action "#{color("This is a non-reversible action! Your application code and data will be permanently deleted if you continue!", :yellow)}\n\nAre you sure you want to delete the application '#{app}'?"
|
213
|
+
|
214
|
+
say "Deleting application '#{rest_app.name}' ... "
|
215
|
+
rest_app.destroy
|
216
|
+
success "deleted"
|
217
|
+
|
218
|
+
paragraph{ rest_app.messages.each{ |s| success s } }
|
219
|
+
|
220
|
+
0
|
221
|
+
end
|
222
|
+
|
223
|
+
summary "Start the application"
|
224
|
+
syntax "<app> [--namespace NAME]"
|
225
|
+
takes_application :argument => true
|
226
|
+
def start(app)
|
227
|
+
app_action :start
|
228
|
+
|
229
|
+
results { say "#{app} started" }
|
230
|
+
0
|
231
|
+
end
|
232
|
+
|
233
|
+
summary "Stop the application"
|
234
|
+
syntax "<app> [--namespace NAME]"
|
235
|
+
takes_application :argument => true
|
236
|
+
def stop(app)
|
237
|
+
app_action :stop
|
238
|
+
|
239
|
+
results { say "#{app} stopped" }
|
240
|
+
0
|
241
|
+
end
|
242
|
+
|
243
|
+
summary "Stops all application processes"
|
244
|
+
syntax "<app> [--namespace NAME]"
|
245
|
+
takes_application :argument => true
|
246
|
+
def force_stop(app)
|
247
|
+
app_action :stop, true
|
248
|
+
|
249
|
+
results { say "#{app} force stopped" }
|
250
|
+
0
|
251
|
+
end
|
252
|
+
|
253
|
+
summary "Restart the application"
|
254
|
+
syntax "<app> [--namespace NAME]"
|
255
|
+
takes_application :argument => true
|
256
|
+
def restart(app)
|
257
|
+
app_action :restart
|
258
|
+
|
259
|
+
results { say "#{app} restarted" }
|
260
|
+
0
|
261
|
+
end
|
262
|
+
|
263
|
+
summary "Reload the application's configuration"
|
264
|
+
syntax "<app> [--namespace NAME]"
|
265
|
+
takes_application :argument => true
|
266
|
+
def reload(app)
|
267
|
+
app_action :reload
|
268
|
+
|
269
|
+
results { say "#{app} config reloaded" }
|
270
|
+
0
|
271
|
+
end
|
272
|
+
|
273
|
+
summary "Clean out the application's logs and tmp directories and tidy up the git repo on the server"
|
274
|
+
syntax "<app> [--namespace NAME]"
|
275
|
+
takes_application :argument => true
|
276
|
+
def tidy(app)
|
277
|
+
app_action :tidy
|
278
|
+
|
279
|
+
results { say "#{app} cleaned up" }
|
280
|
+
0
|
281
|
+
end
|
282
|
+
|
283
|
+
summary "Show information about an application"
|
284
|
+
description <<-DESC
|
285
|
+
Display the properties of an application, including its URL, the SSH
|
286
|
+
connection string, and the Git remote URL. Will also display any
|
287
|
+
cartridges, their scale, and any values they expose.
|
288
|
+
|
289
|
+
The '--state' option will retrieve information from each cartridge in
|
290
|
+
the application, which may include cartridge specific text.
|
291
|
+
|
292
|
+
The '--configuration' option will display configuration values set in
|
293
|
+
the application. Use 'pbox configure-app' to configure.
|
294
|
+
|
295
|
+
To see information about the individual gears within an application,
|
296
|
+
use '--gears', including whether they are started or stopped and their
|
297
|
+
SSH host strings. Passing '--gears quota' will show the free and maximum
|
298
|
+
storage on each gear.
|
299
|
+
|
300
|
+
If you want to run commands against individual gears, use:
|
301
|
+
|
302
|
+
pbox ssh <app> --gears '<command>'
|
303
|
+
|
304
|
+
to run and display the output from each gear.
|
305
|
+
|
306
|
+
DESC
|
307
|
+
syntax "<app> [--namespace NAME]"
|
308
|
+
takes_application :argument => true
|
309
|
+
option ["--state"], "Get the current state of the cartridges in this application"
|
310
|
+
option ["--configuration"], "Get the current configuration values set in this application"
|
311
|
+
option ["--gears [quota|ssh]"], "Show information about the cartridges on each gear in this application. Pass 'quota' to see per gear disk usage and limits. Pass 'ssh' to print only the SSH connection strings of each gear."
|
312
|
+
def show(app_name)
|
313
|
+
|
314
|
+
if options.state
|
315
|
+
find_app(:with_gear_groups => true).each do |gg|
|
316
|
+
say "Cartridge #{gg.cartridges.collect { |c| c['name'] }.join(', ')} is #{gear_group_state(gg.gears.map{ |g| g['state'] })}"
|
317
|
+
end
|
318
|
+
|
319
|
+
elsif options.gears && options.gears != true
|
320
|
+
groups = find_app(:with_gear_groups => true)
|
321
|
+
|
322
|
+
case options.gears
|
323
|
+
when 'quota'
|
324
|
+
opts = {:as => :gear, :split_cells_on => /\s*\t/, :header => ['Gear', 'Cartridges', 'Used', 'Limit'], :align => [nil, nil, :right, :right]}
|
325
|
+
table_from_gears('echo "$(du --block-size=1 -s 2>/dev/null | cut -f 1)"', groups, opts) do |gear, data, group|
|
326
|
+
[gear['id'], group.cartridges.collect{ |c| c['name'] }.join(' '), (human_size(data.chomp) rescue 'error'), human_size(group.quota)]
|
327
|
+
end
|
328
|
+
when 'ssh'
|
329
|
+
groups.each{ |group| group.gears.each{ |g| say (ssh_string(g['ssh_url']) or raise NoPerGearOperations) } }
|
330
|
+
else
|
331
|
+
run_on_gears(ssh_command_for_op(options.gears), groups)
|
332
|
+
end
|
333
|
+
|
334
|
+
elsif options.gears
|
335
|
+
gear_info = find_app(:with_gear_groups => true).map do |group|
|
336
|
+
group.gears.map do |gear|
|
337
|
+
[
|
338
|
+
gear['id'],
|
339
|
+
gear['state'] == 'started' ? gear['state'] : color(gear['state'], :yellow),
|
340
|
+
group.cartridges.collect{ |c| c['name'] }.join(' '),
|
341
|
+
group.gear_profile,
|
342
|
+
ssh_string(gear['ssh_url'])
|
343
|
+
]
|
344
|
+
end
|
345
|
+
end.flatten(1)
|
346
|
+
|
347
|
+
say table(gear_info, :header => ['ID', 'State', 'Cartridges', 'Size', 'SSH URL'])
|
348
|
+
|
349
|
+
elsif options.configuration
|
350
|
+
display_app_configurations(find_app)
|
351
|
+
paragraph { say "Use 'pbox configure-app' to change the configuration values of this application." }
|
352
|
+
|
353
|
+
else
|
354
|
+
app = find_app(:include => :cartridges)
|
355
|
+
display_app(app, app.cartridges)
|
356
|
+
end
|
357
|
+
|
358
|
+
0
|
359
|
+
end
|
360
|
+
|
361
|
+
summary "Deploy a git reference or binary file of an application"
|
362
|
+
syntax "<ref> --app NAME [--namespace NAME]"
|
363
|
+
description <<-DESC
|
364
|
+
By default ProtonBox applications prepare, distribute, and activate deployments
|
365
|
+
on every git push. Alternatively, a user may choose to disable automatic
|
366
|
+
deployments and use 'pbox deploy' and 'pbox deployment' commands to fully control the
|
367
|
+
deployment lifecycle.
|
368
|
+
|
369
|
+
Use this command to prepare, distribute and deploy manually from a git reference
|
370
|
+
(commit id, tag or branch) or from a binary file. Check also 'pbox configure-app'
|
371
|
+
to configure your application to deploy manually and set the number of deployments
|
372
|
+
to keep in history.
|
373
|
+
|
374
|
+
DESC
|
375
|
+
takes_application
|
376
|
+
argument :ref, "Git tag, branch or commit id or path to binary file to be deployed", ["--ref REF"], :optional => false
|
377
|
+
option "--[no-]hot-deploy", "Perform hot deployment according to the specified argument rather than checking for the presence of the hot_deploy marker in the application git repo"
|
378
|
+
option "--[no-]force-clean-build", "Perform a clean build according to the specified argument rather than checking for the presence of the force_clean_build marker in the application git repo"
|
379
|
+
alias_action :"deploy", :root_command => true
|
380
|
+
def deploy(ref)
|
381
|
+
rest_app = find_app
|
382
|
+
|
383
|
+
raise RHC::DeploymentsNotSupportedException.new if !rest_app.supports? "DEPLOY"
|
384
|
+
|
385
|
+
deploy_artifact(rest_app, ref, options.hot_deploy, options.force_clean_build)
|
386
|
+
|
387
|
+
0
|
388
|
+
end
|
389
|
+
|
390
|
+
summary "Configure several properties that apply to an application"
|
391
|
+
syntax "<app> [--[no-]auto-deploy] [--keep-deployments INTEGER] [--deployment-branch BRANCH] [--deployment-type TYPE] [--namespace NAME]"
|
392
|
+
takes_application :argument => true
|
393
|
+
option ["--[no-]auto-deploy"], "Build and deploy automatically when pushing to the git repo. Defaults to true."
|
394
|
+
option ["--keep-deployments INTEGER", Integer], "Number of deployments to preserve. Defaults to 1."
|
395
|
+
option ["--deployment-branch BRANCH"], "Which branch should trigger an automatic deployment, if automatic deployment is enabled with --auto-deploy. Defaults to master."
|
396
|
+
option ["--deployment-type git|binary"], "Type of deployment the application accepts ('git' or 'binary'). Defaults to git."
|
397
|
+
def configure(app_name)
|
398
|
+
rest_app = find_app
|
399
|
+
|
400
|
+
app_options = {}
|
401
|
+
app_options[:auto_deploy] = options.auto_deploy if !options.auto_deploy.nil?
|
402
|
+
app_options[:keep_deployments] = options.keep_deployments if options.keep_deployments
|
403
|
+
app_options[:deployment_branch] = options.deployment_branch if options.deployment_branch
|
404
|
+
app_options[:deployment_type] = options.deployment_type if options.deployment_type
|
405
|
+
|
406
|
+
if app_options.present?
|
407
|
+
paragraph do
|
408
|
+
say "Configuring application '#{app_name}' ... "
|
409
|
+
rest_app.configure(app_options)
|
410
|
+
success "done"
|
411
|
+
end
|
412
|
+
end
|
413
|
+
|
414
|
+
paragraph { display_app(find_app, nil, [:auto_deploy, :keep_deployments, :deployment_type, :deployment_branch]) }
|
415
|
+
|
416
|
+
paragraph { say "Your application '#{rest_app.name}' is #{app_options.empty? ? '' : 'now '}configured as listed above." }
|
417
|
+
paragraph { say "Use 'pbox show-app #{rest_app.name} --configuration' to check your configuration values any time." } if app_options.present?
|
418
|
+
|
419
|
+
0
|
420
|
+
end
|
421
|
+
|
422
|
+
private
|
423
|
+
include RHC::GitHelpers
|
424
|
+
include RHC::CartridgeHelpers
|
425
|
+
include RHC::SSHHelpers
|
426
|
+
include RHC::DeploymentHelpers
|
427
|
+
|
428
|
+
MAX_RETRIES = 7
|
429
|
+
DEFAULT_DELAY_THROTTLE = 2.0
|
430
|
+
|
431
|
+
def require_one_web_cart
|
432
|
+
lambda{ |carts|
|
433
|
+
match, ambiguous = carts.partition{ |c| not c.is_a?(Array) }
|
434
|
+
selected_web = match.any?{ |c| not c.only_in_existing? }
|
435
|
+
possible_web = ambiguous.flatten.any?{ |c| not c.only_in_existing? }
|
436
|
+
if not (selected_web or possible_web)
|
437
|
+
section(:bottom => 1){ list_cartridges(standalone_cartridges) }
|
438
|
+
raise RHC::CartridgeNotFoundException, "Every application needs a web cartridge to handle incoming web requests. Please provide the short name of one of the carts listed above."
|
439
|
+
end
|
440
|
+
if selected_web
|
441
|
+
carts.map! &other_carts_only
|
442
|
+
elsif possible_web && ambiguous.length == 1
|
443
|
+
carts.map! &web_carts_only
|
444
|
+
end
|
445
|
+
}
|
446
|
+
end
|
447
|
+
|
448
|
+
def check_sshkeys!
|
449
|
+
return unless interactive?
|
450
|
+
RHC::SSHWizard.new(rest_client, config, options).run
|
451
|
+
end
|
452
|
+
|
453
|
+
def check_name!(name)
|
454
|
+
return unless name.blank?
|
455
|
+
|
456
|
+
paragraph{ say "When creating an application, you must provide a name and a cartridge from the list below:" }
|
457
|
+
paragraph{ list_cartridges(standalone_cartridges) }
|
458
|
+
|
459
|
+
raise ArgumentError, "Please specify the name of the application and the web cartridge to install"
|
460
|
+
end
|
461
|
+
|
462
|
+
def check_config!
|
463
|
+
return if not interactive? or (!options.clean && config.has_local_config?) or (options.server && (options.pblogin || options.token))
|
464
|
+
RHC::EmbeddedWizard.new(config, options).run
|
465
|
+
end
|
466
|
+
|
467
|
+
def check_domain!
|
468
|
+
if options.namespace
|
469
|
+
rest_client.find_domain(options.namespace)
|
470
|
+
else
|
471
|
+
if rest_client.domains.empty?
|
472
|
+
raise RHC::Rest::DomainNotFoundException, "No domains found. Please create a domain with 'pbox create-domain <namespace>' before creating applications." unless interactive?
|
473
|
+
RHC::DomainWizard.new(config, options, rest_client).run
|
474
|
+
end
|
475
|
+
domain = rest_client.domains.first
|
476
|
+
raise RHC::Rest::DomainNotFoundException, "No domains found. Please create a domain with 'pbox create-domain <namespace>' before creating applications." unless domain
|
477
|
+
options.namespace = domain.name
|
478
|
+
domain
|
479
|
+
end
|
480
|
+
end
|
481
|
+
|
482
|
+
def gear_group_state(states)
|
483
|
+
return states[0] if states.length == 1 || states.uniq.length == 1
|
484
|
+
"#{states.select{ |s| s == 'started' }.count}/#{states.length} started"
|
485
|
+
end
|
486
|
+
|
487
|
+
def app_action(action, *args)
|
488
|
+
rest_app = find_app
|
489
|
+
result = rest_app.send action, *args
|
490
|
+
result
|
491
|
+
end
|
492
|
+
|
493
|
+
def create_app(name, cartridges, rest_domain, gear_size=nil, scale=nil, from_code=nil, environment_variables=nil, auto_deploy=nil, keep_deployments=nil, deployment_branch=nil)
|
494
|
+
app_options = {:cartridges => Array(cartridges)}
|
495
|
+
app_options[:gear_profile] = gear_size if gear_size
|
496
|
+
app_options[:scale] = scale if scale
|
497
|
+
app_options[:initial_git_url] = from_code if from_code
|
498
|
+
app_options[:debug] = true if @debug
|
499
|
+
app_options[:environment_variables] = environment_variables.map{ |item| item.to_hash } if environment_variables.present?
|
500
|
+
app_options[:auto_deploy] = auto_deploy if !auto_deploy.nil?
|
501
|
+
app_options[:keep_deployments] = keep_deployments if keep_deployments
|
502
|
+
app_options[:deployment_branch] = deployment_branch if deployment_branch
|
503
|
+
debug "Creating application '#{name}' with these options - #{app_options.inspect}"
|
504
|
+
rest_app = rest_domain.add_application(name, app_options)
|
505
|
+
|
506
|
+
rest_app
|
507
|
+
rescue RHC::Rest::Exception => e
|
508
|
+
if e.code == 109
|
509
|
+
paragraph{ say "Valid cartridge types:" }
|
510
|
+
paragraph{ list_cartridges(standalone_cartridges) }
|
511
|
+
end
|
512
|
+
raise
|
513
|
+
end
|
514
|
+
|
515
|
+
def add_jenkins_app(rest_domain)
|
516
|
+
create_app(jenkins_app_name, jenkins_cartridge_name, rest_domain)
|
517
|
+
end
|
518
|
+
|
519
|
+
def add_jenkins_cartridge(rest_app)
|
520
|
+
rest_app.add_cartridge(jenkins_client_cartridge_name)
|
521
|
+
end
|
522
|
+
|
523
|
+
def add_jenkins_client_to(rest_app, messages)
|
524
|
+
say "Setting up Jenkins build ... "
|
525
|
+
successful, attempts, exit_code, exit_message = false, 1, 157, nil
|
526
|
+
while (!successful && exit_code == 157 && attempts < MAX_RETRIES)
|
527
|
+
begin
|
528
|
+
cartridge = add_jenkins_cartridge(rest_app)
|
529
|
+
successful = true
|
530
|
+
|
531
|
+
success "done"
|
532
|
+
messages.concat(cartridge.messages)
|
533
|
+
|
534
|
+
rescue RHC::Rest::ServerErrorException => e
|
535
|
+
if (e.code == 157)
|
536
|
+
# error downloading Jenkins /jnlpJars/jenkins-cli.jar
|
537
|
+
attempts += 1
|
538
|
+
debug "Jenkins server could not be contacted, sleep and then retry: attempt #{attempts}\n #{e.message}"
|
539
|
+
Kernel.sleep(10)
|
540
|
+
end
|
541
|
+
exit_code = e.code
|
542
|
+
exit_message = e.message
|
543
|
+
rescue Exception => e
|
544
|
+
# timeout and other exceptions
|
545
|
+
exit_code = 1
|
546
|
+
exit_message = e.message
|
547
|
+
end
|
548
|
+
end
|
549
|
+
unless successful
|
550
|
+
warn "not complete"
|
551
|
+
add_issue("Jenkins client failed to install - #{exit_message}",
|
552
|
+
"Install the jenkins client",
|
553
|
+
"pbox add-cartridge jenkins-client -a #{rest_app.name}")
|
554
|
+
end
|
555
|
+
end
|
556
|
+
|
557
|
+
def dns_propagated?(host, sleep_time=2)
|
558
|
+
#
|
559
|
+
# Confirm that the host exists in DNS
|
560
|
+
#
|
561
|
+
debug "Start checking for application dns @ '#{host}'"
|
562
|
+
|
563
|
+
found = false
|
564
|
+
|
565
|
+
# Allow DNS to propagate
|
566
|
+
Kernel.sleep 5
|
567
|
+
|
568
|
+
# Now start checking for DNS
|
569
|
+
host_found = hosts_file_contains?(host) or
|
570
|
+
1.upto(MAX_RETRIES) { |i|
|
571
|
+
host_found = host_exists?(host)
|
572
|
+
break found if host_found
|
573
|
+
|
574
|
+
say " retry # #{i} - Waiting for DNS: #{host}"
|
575
|
+
Kernel.sleep sleep_time.to_i
|
576
|
+
sleep_time *= DEFAULT_DELAY_THROTTLE
|
577
|
+
}
|
578
|
+
|
579
|
+
debug "End checking for application dns @ '#{host} - found=#{found}'"
|
580
|
+
|
581
|
+
host_found
|
582
|
+
end
|
583
|
+
|
584
|
+
def enable_jenkins?
|
585
|
+
# legacy issue, commander 4.0.x will place the option in the hash with nil value (BZ878407)
|
586
|
+
options.__hash__.has_key?(:enable_jenkins)
|
587
|
+
end
|
588
|
+
|
589
|
+
def jenkins_app_name
|
590
|
+
if options.enable_jenkins.is_a? String
|
591
|
+
options.enable_jenkins
|
592
|
+
end || "jenkins"
|
593
|
+
end
|
594
|
+
|
595
|
+
def jenkins_cartridge_name
|
596
|
+
jenkins_cartridges.last.name
|
597
|
+
end
|
598
|
+
|
599
|
+
def jenkins_client_cartridge_name
|
600
|
+
jenkins_client_cartridges.last.name
|
601
|
+
end
|
602
|
+
|
603
|
+
def run_nslookup(host)
|
604
|
+
# :nocov:
|
605
|
+
`nslookup #{host}`
|
606
|
+
$?.exitstatus == 0
|
607
|
+
# :nocov:
|
608
|
+
end
|
609
|
+
|
610
|
+
def run_ping(host)
|
611
|
+
# :nocov:
|
612
|
+
`ping #{host} -n 2`
|
613
|
+
$?.exitstatus == 0
|
614
|
+
# :nocov:
|
615
|
+
end
|
616
|
+
|
617
|
+
def windows_nslookup_bug?(rest_app)
|
618
|
+
windows_nslookup = run_nslookup(rest_app.host)
|
619
|
+
windows_ping = run_ping(rest_app.host)
|
620
|
+
|
621
|
+
if windows_nslookup and !windows_ping # this is related to BZ #826769
|
622
|
+
issue = <<WINSOCKISSUE
|
623
|
+
We were unable to lookup your hostname (#{rest_app.host})
|
624
|
+
in a reasonable amount of time. This can happen periodically and may
|
625
|
+
take up to 10 extra minutes to propagate depending on where you are in the
|
626
|
+
world. This may also be related to an issue with Winsock on Windows [1][2].
|
627
|
+
We recommend you wait a few minutes then clone your git repository manually.
|
628
|
+
|
629
|
+
[1] http://support.microsoft.com/kb/299357
|
630
|
+
[2] http://support.microsoft.com/kb/811259
|
631
|
+
WINSOCKISSUE
|
632
|
+
add_issue(issue,
|
633
|
+
"Clone your git repo",
|
634
|
+
"pbox git-clone #{rest_app.name}")
|
635
|
+
|
636
|
+
return true
|
637
|
+
end
|
638
|
+
|
639
|
+
false
|
640
|
+
end
|
641
|
+
|
642
|
+
def output_issues(rest_app)
|
643
|
+
reasons, steps = format_issues(4)
|
644
|
+
warn <<WARNING_OUTPUT
|
645
|
+
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
646
|
+
WARNING: Your application was created successfully but had problems during
|
647
|
+
configuration. Below is a list of the issues and steps you can
|
648
|
+
take to complete the configuration of your application.
|
649
|
+
|
650
|
+
Application URL: #{rest_app.app_url}
|
651
|
+
|
652
|
+
Issues:
|
653
|
+
#{reasons}
|
654
|
+
Steps to complete your configuration:
|
655
|
+
#{steps}
|
656
|
+
If you continue to experience problems after completing these steps,
|
657
|
+
you can try destroying and recreating the application:
|
658
|
+
|
659
|
+
$ pbox app delete #{rest_app.name} --confirm
|
660
|
+
|
661
|
+
Please contact us if you are unable to successfully create your
|
662
|
+
application:
|
663
|
+
|
664
|
+
Support - https://my.protonbox.com
|
665
|
+
|
666
|
+
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
667
|
+
|
668
|
+
WARNING_OUTPUT
|
669
|
+
end
|
670
|
+
|
671
|
+
# Issues collector collects a set of recoverable issues and steps to fix them
|
672
|
+
# for output at the end of a complex command
|
673
|
+
def add_issue(reason, commands_header, *commands)
|
674
|
+
@issues ||= []
|
675
|
+
issue = {:reason => reason,
|
676
|
+
:commands_header => commands_header,
|
677
|
+
:commands => commands}
|
678
|
+
@issues << issue
|
679
|
+
end
|
680
|
+
|
681
|
+
def format_issues(indent)
|
682
|
+
return nil unless issues?
|
683
|
+
|
684
|
+
indentation = " " * indent
|
685
|
+
reasons = ""
|
686
|
+
steps = ""
|
687
|
+
|
688
|
+
@issues.each_with_index do |issue, i|
|
689
|
+
reasons << "#{indentation}#{i+1}. #{issue[:reason].strip}\n"
|
690
|
+
steps << "#{indentation}#{i+1}. #{issue[:commands_header].strip}\n"
|
691
|
+
issue[:commands].each { |cmd| steps << "#{indentation} $ #{cmd}\n" }
|
692
|
+
end
|
693
|
+
|
694
|
+
[reasons, steps]
|
695
|
+
end
|
696
|
+
|
697
|
+
def issues?
|
698
|
+
not @issues.nil?
|
699
|
+
end
|
700
|
+
end
|
701
|
+
end
|