startapp 0.1.6

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