crazy-yard 3.2.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +19 -0
- data/README.md +438 -0
- data/bin/ey +9 -0
- data/lib/engineyard.rb +9 -0
- data/lib/engineyard/cli.rb +816 -0
- data/lib/engineyard/cli/api.rb +98 -0
- data/lib/engineyard/cli/recipes.rb +129 -0
- data/lib/engineyard/cli/ui.rb +275 -0
- data/lib/engineyard/cli/web.rb +85 -0
- data/lib/engineyard/config.rb +158 -0
- data/lib/engineyard/deploy_config.rb +65 -0
- data/lib/engineyard/deploy_config/ref.rb +56 -0
- data/lib/engineyard/error.rb +82 -0
- data/lib/engineyard/eyrc.rb +59 -0
- data/lib/engineyard/repo.rb +105 -0
- data/lib/engineyard/serverside_runner.rb +159 -0
- data/lib/engineyard/templates.rb +6 -0
- data/lib/engineyard/templates/ey.yml.erb +196 -0
- data/lib/engineyard/templates/ey_yml.rb +119 -0
- data/lib/engineyard/thor.rb +215 -0
- data/lib/engineyard/version.rb +4 -0
- data/lib/vendor/thor/Gemfile +15 -0
- data/lib/vendor/thor/LICENSE.md +20 -0
- data/lib/vendor/thor/README.md +35 -0
- data/lib/vendor/thor/lib/thor.rb +473 -0
- data/lib/vendor/thor/lib/thor/actions.rb +318 -0
- data/lib/vendor/thor/lib/thor/actions/create_file.rb +105 -0
- data/lib/vendor/thor/lib/thor/actions/create_link.rb +60 -0
- data/lib/vendor/thor/lib/thor/actions/directory.rb +119 -0
- data/lib/vendor/thor/lib/thor/actions/empty_directory.rb +137 -0
- data/lib/vendor/thor/lib/thor/actions/file_manipulation.rb +314 -0
- data/lib/vendor/thor/lib/thor/actions/inject_into_file.rb +109 -0
- data/lib/vendor/thor/lib/thor/base.rb +652 -0
- data/lib/vendor/thor/lib/thor/command.rb +136 -0
- data/lib/vendor/thor/lib/thor/core_ext/hash_with_indifferent_access.rb +80 -0
- data/lib/vendor/thor/lib/thor/core_ext/io_binary_read.rb +12 -0
- data/lib/vendor/thor/lib/thor/core_ext/ordered_hash.rb +100 -0
- data/lib/vendor/thor/lib/thor/error.rb +28 -0
- data/lib/vendor/thor/lib/thor/group.rb +282 -0
- data/lib/vendor/thor/lib/thor/invocation.rb +172 -0
- data/lib/vendor/thor/lib/thor/parser.rb +4 -0
- data/lib/vendor/thor/lib/thor/parser/argument.rb +74 -0
- data/lib/vendor/thor/lib/thor/parser/arguments.rb +171 -0
- data/lib/vendor/thor/lib/thor/parser/option.rb +121 -0
- data/lib/vendor/thor/lib/thor/parser/options.rb +218 -0
- data/lib/vendor/thor/lib/thor/rake_compat.rb +72 -0
- data/lib/vendor/thor/lib/thor/runner.rb +322 -0
- data/lib/vendor/thor/lib/thor/shell.rb +88 -0
- data/lib/vendor/thor/lib/thor/shell/basic.rb +393 -0
- data/lib/vendor/thor/lib/thor/shell/color.rb +148 -0
- data/lib/vendor/thor/lib/thor/shell/html.rb +127 -0
- data/lib/vendor/thor/lib/thor/util.rb +270 -0
- data/lib/vendor/thor/lib/thor/version.rb +3 -0
- data/lib/vendor/thor/thor.gemspec +24 -0
- data/spec/engineyard/cli/api_spec.rb +50 -0
- data/spec/engineyard/cli_spec.rb +28 -0
- data/spec/engineyard/config_spec.rb +61 -0
- data/spec/engineyard/deploy_config_spec.rb +194 -0
- data/spec/engineyard/eyrc_spec.rb +76 -0
- data/spec/engineyard/repo_spec.rb +83 -0
- data/spec/engineyard_spec.rb +7 -0
- data/spec/ey/console_spec.rb +57 -0
- data/spec/ey/deploy_spec.rb +435 -0
- data/spec/ey/ey_spec.rb +23 -0
- data/spec/ey/init_spec.rb +123 -0
- data/spec/ey/list_environments_spec.rb +120 -0
- data/spec/ey/login_spec.rb +33 -0
- data/spec/ey/logout_spec.rb +24 -0
- data/spec/ey/logs_spec.rb +36 -0
- data/spec/ey/rebuild_spec.rb +18 -0
- data/spec/ey/recipes/apply_spec.rb +29 -0
- data/spec/ey/recipes/download_spec.rb +43 -0
- data/spec/ey/recipes/upload_spec.rb +99 -0
- data/spec/ey/rollback_spec.rb +73 -0
- data/spec/ey/scp_spec.rb +176 -0
- data/spec/ey/servers_spec.rb +209 -0
- data/spec/ey/ssh_spec.rb +273 -0
- data/spec/ey/status_spec.rb +45 -0
- data/spec/ey/timeout_deploy_spec.rb +18 -0
- data/spec/ey/web/disable_spec.rb +21 -0
- data/spec/ey/web/enable_spec.rb +26 -0
- data/spec/ey/web/restart_spec.rb +21 -0
- data/spec/ey/whoami_spec.rb +30 -0
- data/spec/spec_helper.rb +84 -0
- data/spec/support/bundled_ey +7 -0
- data/spec/support/fixture_recipes.tgz +0 -0
- data/spec/support/git_repos.rb +115 -0
- data/spec/support/helpers.rb +330 -0
- data/spec/support/matchers.rb +16 -0
- data/spec/support/ruby_ext.rb +13 -0
- data/spec/support/shared_behavior.rb +278 -0
- metadata +411 -0
data/bin/ey
ADDED
data/lib/engineyard.rb
ADDED
@@ -0,0 +1,816 @@
|
|
1
|
+
require 'engineyard'
|
2
|
+
require 'engineyard/error'
|
3
|
+
require 'engineyard/thor'
|
4
|
+
require 'engineyard/deploy_config'
|
5
|
+
require 'engineyard/serverside_runner'
|
6
|
+
require 'launchy'
|
7
|
+
require 'fileutils'
|
8
|
+
|
9
|
+
module EY
|
10
|
+
class CLI < EY::Thor
|
11
|
+
require 'engineyard/cli/recipes'
|
12
|
+
require 'engineyard/cli/web'
|
13
|
+
require 'engineyard/cli/api'
|
14
|
+
require 'engineyard/cli/ui'
|
15
|
+
require 'engineyard/error'
|
16
|
+
require 'engineyard-cloud-client/errors'
|
17
|
+
|
18
|
+
include Thor::Actions
|
19
|
+
|
20
|
+
def self.start(given_args=ARGV, config={})
|
21
|
+
Thor::Base.shell = EY::CLI::UI
|
22
|
+
ui = EY::CLI::UI.new
|
23
|
+
super(given_args, {shell: ui}.merge(config))
|
24
|
+
rescue Thor::Error, EY::Error, EY::CloudClient::Error => e
|
25
|
+
ui.print_exception(e)
|
26
|
+
raise
|
27
|
+
rescue Interrupt => e
|
28
|
+
puts
|
29
|
+
ui.print_exception(e)
|
30
|
+
ui.say("Quitting...")
|
31
|
+
raise
|
32
|
+
rescue SystemExit, Errno::EPIPE
|
33
|
+
# don't print a message for safe exits
|
34
|
+
raise
|
35
|
+
rescue Exception => e
|
36
|
+
ui.print_exception(e)
|
37
|
+
raise
|
38
|
+
end
|
39
|
+
|
40
|
+
class_option :api_token, type: :string, desc: "Use API_TOKEN to authenticate this command"
|
41
|
+
class_option :serverside_version, type: :string, desc: "Please use with care! Override deploy system version (same as ENV variable ENGINEYARD_SERVERSIDE_VERSION)"
|
42
|
+
class_option :quiet, aliases: %w[-q], type: :boolean, desc: "Quieter CLI output."
|
43
|
+
|
44
|
+
desc "init",
|
45
|
+
"Initialize the current directory with an ey.yml configuration file."
|
46
|
+
long_desc <<-DESC
|
47
|
+
Initialize the current directory with an ey.yml configuration file.
|
48
|
+
|
49
|
+
Please read the generated file and make adjustments.
|
50
|
+
Many applications will need only the default behavior.
|
51
|
+
For reference, many available options are explained in the generated file.
|
52
|
+
|
53
|
+
IMPORTANT: THE GENERATED FILE '#{EY::Config.pathname_for_write}'
|
54
|
+
MUST BE COMMITTED TO YOUR REPOSITORY OR OPTIONS WILL NOT BE LOADED.
|
55
|
+
DESC
|
56
|
+
method_option :path, type: :string, aliases: %w(-p),
|
57
|
+
desc: "Path for ey.yml (supported paths: #{EY::Config::CONFIG_FILES.join(', ')})"
|
58
|
+
def init
|
59
|
+
unless EY::Repo.exist?
|
60
|
+
raise EY::Error, "Working directory is not a repository. Aborting."
|
61
|
+
end
|
62
|
+
|
63
|
+
path = Pathname.new(options['path'] || EY::Config.pathname_for_write)
|
64
|
+
|
65
|
+
existing = {}
|
66
|
+
if path.exist?
|
67
|
+
ui.warn "Reinitializing existing file: #{path}"
|
68
|
+
existing = EY::Config.load_config
|
69
|
+
end
|
70
|
+
|
71
|
+
template = EY::Templates::EyYml.new(existing)
|
72
|
+
template.write(path)
|
73
|
+
|
74
|
+
ui.info <<-GIT
|
75
|
+
|
76
|
+
Configuration generated: #{path}
|
77
|
+
Go look at it, then add it to your repository!
|
78
|
+
|
79
|
+
\tgit add #{path}
|
80
|
+
\tgit commit -m "Add generated #{path} from ey init"
|
81
|
+
|
82
|
+
GIT
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
desc "deploy [--environment ENVIRONMENT] [--ref GIT-REF]",
|
87
|
+
"Deploy specified branch, tag, or sha to specified environment."
|
88
|
+
long_desc <<-DESC
|
89
|
+
This command must be run with the current directory containing the app to be
|
90
|
+
deployed. If ey.yml specifies a default branch then the ref parameter can be
|
91
|
+
omitted. Furthermore, if a default branch is specified but a different command
|
92
|
+
is supplied the deploy will fail unless -R or --force-ref is used.
|
93
|
+
|
94
|
+
Migrations are run based on the settings in your ey.yml file.
|
95
|
+
With each deploy the default migration setting can be overriden by
|
96
|
+
specifying --migrate or --migrate 'rake db:migrate'.
|
97
|
+
Migrations can also be skipped by using --no-migrate.
|
98
|
+
DESC
|
99
|
+
method_option :ignore_bad_master, type: :boolean, aliases: %w(--ignore-bad-bridge),
|
100
|
+
desc: "Force a deploy even if the master is in a bad state"
|
101
|
+
method_option :migrate, type: :string, aliases: %w(-m),
|
102
|
+
lazy_default: true,
|
103
|
+
desc: "Run migrations via [MIGRATE]; use --no-migrate to avoid running migrations"
|
104
|
+
method_option :ref, type: :string, aliases: %w(-r --branch --tag),
|
105
|
+
required: true, default: '',
|
106
|
+
desc: "Git ref to deploy. May be a branch, a tag, or a SHA. Use -R to deploy a different ref if a default is set."
|
107
|
+
method_option :force_ref, type: :string, aliases: %w(--ignore-default-branch -R),
|
108
|
+
lazy_default: true,
|
109
|
+
desc: "Force a deploy of the specified git ref even if a default is set in ey.yml."
|
110
|
+
method_option :environment, type: :string, aliases: %w(-e),
|
111
|
+
required: true, default: false,
|
112
|
+
desc: "Environment in which to deploy this application"
|
113
|
+
method_option :app, type: :string, aliases: %w(-a),
|
114
|
+
required: true, default: '',
|
115
|
+
desc: "Name of the application to deploy"
|
116
|
+
method_option :account, type: :string, aliases: %w(-c),
|
117
|
+
required: true, default: '',
|
118
|
+
desc: "Name of the account in which the environment can be found"
|
119
|
+
method_option :verbose, type: :boolean, aliases: %w(-v),
|
120
|
+
desc: "Be verbose"
|
121
|
+
method_option :config, type: :hash, default: {}, aliases: %w(--extra-deploy-hook-options),
|
122
|
+
desc: "Hash made available in deploy hooks (in the 'config' hash), can also override some ey.yml settings."
|
123
|
+
def deploy
|
124
|
+
app_env = fetch_app_environment(options[:app], options[:environment], options[:account])
|
125
|
+
|
126
|
+
env_config = config.environment_config(app_env.environment_name)
|
127
|
+
deploy_config = EY::DeployConfig.new(options, env_config, repo, ui)
|
128
|
+
|
129
|
+
deployment = app_env.new_deployment({
|
130
|
+
ref: deploy_config.ref,
|
131
|
+
migrate: deploy_config.migrate,
|
132
|
+
migrate_command: deploy_config.migrate_command,
|
133
|
+
extra_config: deploy_config.extra_config,
|
134
|
+
serverside_version: serverside_version,
|
135
|
+
})
|
136
|
+
|
137
|
+
runner = serverside_runner(app_env, deploy_config.verbose, deployment.serverside_version, options[:ignore_bad_master])
|
138
|
+
|
139
|
+
out = EY::CLI::UI::Tee.new(ui.out, deployment.output)
|
140
|
+
err = EY::CLI::UI::Tee.new(ui.err, deployment.output)
|
141
|
+
|
142
|
+
ui.info "Beginning deploy...", :green
|
143
|
+
begin
|
144
|
+
deployment.start
|
145
|
+
rescue
|
146
|
+
ui.error "Error encountered before deploy. Deploy not started."
|
147
|
+
raise
|
148
|
+
end
|
149
|
+
|
150
|
+
begin
|
151
|
+
ui.show_deployment(deployment)
|
152
|
+
out << "Deploy initiated.\n"
|
153
|
+
|
154
|
+
runner.deploy do |args|
|
155
|
+
args.config = deployment.config if deployment.config
|
156
|
+
if deployment.migrate
|
157
|
+
args.migrate = deployment.migrate_command
|
158
|
+
else
|
159
|
+
args.migrate = false
|
160
|
+
end
|
161
|
+
args.ref = deployment.resolved_ref
|
162
|
+
end
|
163
|
+
deployment.successful = runner.call(out, err)
|
164
|
+
rescue Interrupt
|
165
|
+
Signal.trap(:INT) { # The fingers you have used to dial are too fat...
|
166
|
+
ui.info "\nRun `ey timeout-deploy` to mark an unfinished deployment as failed."
|
167
|
+
exit 1
|
168
|
+
}
|
169
|
+
err << "Interrupted. Deployment halted.\n"
|
170
|
+
ui.warn <<-WARN
|
171
|
+
Recording interruption of this unfinished deployment in Engine Yard Cloud...
|
172
|
+
|
173
|
+
WARNING: Interrupting again may prevent Engine Yard Cloud from recording this
|
174
|
+
failed deployment. Unfinished deployments can block future deploys.
|
175
|
+
WARN
|
176
|
+
raise
|
177
|
+
rescue StandardError => e
|
178
|
+
deployment.err << "Error encountered during deploy.\n#{e.class} #{e}\n"
|
179
|
+
ui.print_exception(e)
|
180
|
+
raise
|
181
|
+
ensure
|
182
|
+
ui.info "Saving log... ", :green
|
183
|
+
deployment.finished
|
184
|
+
|
185
|
+
if deployment.successful?
|
186
|
+
ui.info "Successful deployment recorded on Engine Yard Cloud.", :green
|
187
|
+
ui.info "Run `ey launch` to open the application in a browser."
|
188
|
+
else
|
189
|
+
ui.info "Failed deployment recorded on Engine Yard Cloud", :green
|
190
|
+
raise EY::Error, "Deploy failed"
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
desc "timeout-deploy [--environment ENVIRONMENT]",
|
196
|
+
"Fail a stuck unfinished deployment."
|
197
|
+
long_desc <<-DESC
|
198
|
+
NOTICE: Timing out a deploy does not stop currently running deploy
|
199
|
+
processes.
|
200
|
+
|
201
|
+
This command must be run in the current directory containing the app.
|
202
|
+
The latest running deployment will be marked as failed, allowing a
|
203
|
+
new deployment to be run. It is possible to mark a potentially successful
|
204
|
+
deployment as failed. Only run this when a deployment is known to be
|
205
|
+
wrongly unfinished/stuck and when further deployments are blocked.
|
206
|
+
DESC
|
207
|
+
method_option :environment, type: :string, aliases: %w(-e),
|
208
|
+
required: true, default: false,
|
209
|
+
desc: "Environment in which to deploy this application"
|
210
|
+
method_option :app, type: :string, aliases: %w(-a),
|
211
|
+
required: true, default: '',
|
212
|
+
desc: "Name of the application to deploy"
|
213
|
+
method_option :account, type: :string, aliases: %w(-c),
|
214
|
+
required: true, default: '',
|
215
|
+
desc: "Name of the account in which the environment can be found"
|
216
|
+
def timeout_deploy
|
217
|
+
app_env = fetch_app_environment(options[:app], options[:environment], options[:account])
|
218
|
+
deployment = app_env.last_deployment
|
219
|
+
if deployment && !deployment.finished?
|
220
|
+
begin
|
221
|
+
ui.info "Marking last deployment failed...", :green
|
222
|
+
deployment.timeout
|
223
|
+
ui.deployment_status(deployment)
|
224
|
+
rescue EY::CloudClient::RequestFailed => e
|
225
|
+
ui.error "Error encountered attempting to timeout previous deployment."
|
226
|
+
raise
|
227
|
+
end
|
228
|
+
else
|
229
|
+
raise EY::Error, "No unfinished deployment was found for #{app_env.hierarchy_name}."
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
desc "status", "Show the deployment status of the app"
|
234
|
+
long_desc <<-DESC
|
235
|
+
Show the current status of most recent deployment of the specified
|
236
|
+
application and environment.
|
237
|
+
DESC
|
238
|
+
method_option :environment, type: :string, aliases: %w(-e),
|
239
|
+
required: true, default: '',
|
240
|
+
desc: "Environment where the application is deployed"
|
241
|
+
method_option :app, type: :string, aliases: %w(-a),
|
242
|
+
required: true, default: '',
|
243
|
+
desc: "Name of the application"
|
244
|
+
method_option :account, type: :string, aliases: %w(-c),
|
245
|
+
required: true, default: '',
|
246
|
+
desc: "Name of the account in which the application can be found"
|
247
|
+
def status
|
248
|
+
app_env = fetch_app_environment(options[:app], options[:environment], options[:account])
|
249
|
+
deployment = app_env.last_deployment
|
250
|
+
if deployment
|
251
|
+
ui.deployment_status(deployment)
|
252
|
+
else
|
253
|
+
raise EY::Error, "Application #{app_env.app.name} has not been deployed on #{app_env.environment.name}."
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
desc "environments [--all]", "List environments for this app; use --all to list all environments."
|
258
|
+
long_desc <<-DESC
|
259
|
+
By default, environments for this app are displayed. The --all option will
|
260
|
+
display all environments, including those for this app.
|
261
|
+
DESC
|
262
|
+
|
263
|
+
method_option :all, type: :boolean, aliases: %(-A),
|
264
|
+
desc: "Show all environments (ignores --app, --account, and --environment arguments)"
|
265
|
+
method_option :simple, type: :boolean, aliases: %(-s),
|
266
|
+
desc: "Display one environment per line with no extra output"
|
267
|
+
method_option :app, type: :string, aliases: %w(-a),
|
268
|
+
required: true, default: '',
|
269
|
+
desc: "Show environments for this application"
|
270
|
+
method_option :account, type: :string, aliases: %w(-c),
|
271
|
+
required: true, default: '',
|
272
|
+
desc: "Show environments in this account"
|
273
|
+
method_option :environment, type: :string, aliases: %w(-e),
|
274
|
+
required: true, default: '',
|
275
|
+
desc: "Show environments matching environment name"
|
276
|
+
def environments
|
277
|
+
if options[:all] && options[:simple]
|
278
|
+
ui.print_simple_envs api.environments
|
279
|
+
elsif options[:all]
|
280
|
+
ui.print_envs api.apps
|
281
|
+
else
|
282
|
+
remotes = nil
|
283
|
+
if options[:app] == ''
|
284
|
+
repo.fail_on_no_remotes!
|
285
|
+
remotes = repo.remotes
|
286
|
+
end
|
287
|
+
|
288
|
+
resolver = api.resolve_app_environments({
|
289
|
+
account_name: options[:account],
|
290
|
+
app_name: options[:app],
|
291
|
+
environment_name: options[:environment],
|
292
|
+
remotes: remotes,
|
293
|
+
})
|
294
|
+
|
295
|
+
resolver.no_matches do |errors|
|
296
|
+
messages = errors
|
297
|
+
messages << "Use #{self.class.send(:banner_base)} environments --all to see all environments."
|
298
|
+
raise EY::NoMatchesError.new(messages.join("\n"))
|
299
|
+
end
|
300
|
+
|
301
|
+
apps = resolver.matches.map { |app_env| app_env.app }.uniq
|
302
|
+
|
303
|
+
if options[:simple]
|
304
|
+
if apps.size > 1
|
305
|
+
message = "# This app matches multiple Applications in Engine Yard Cloud:\n"
|
306
|
+
apps.each { |app| message << "#\t#{app.name}\n" }
|
307
|
+
message << "# The following environments contain those applications:\n\n"
|
308
|
+
ui.warn(message)
|
309
|
+
end
|
310
|
+
ui.print_simple_envs(apps.map{ |app| app.environments }.flatten)
|
311
|
+
else
|
312
|
+
ui.print_envs(apps, config.default_environment)
|
313
|
+
end
|
314
|
+
end
|
315
|
+
end
|
316
|
+
map "envs" => :environments
|
317
|
+
|
318
|
+
desc "servers", "List servers for an environment."
|
319
|
+
long_desc <<-DESC
|
320
|
+
Display a list of all servers on an environment.
|
321
|
+
Specify -s (--simple) to make parsing the output easier
|
322
|
+
or -uS (--user --host) to output bash loop friendly "user@hostname"
|
323
|
+
DESC
|
324
|
+
|
325
|
+
method_option :simple, type: :boolean, aliases: %(-s),
|
326
|
+
desc: "Display all information in a simplified format without extra text or column alignment"
|
327
|
+
method_option :host, type: :boolean, aliases: %(-S),
|
328
|
+
desc: "Display only hostnames, one per newline (use options -uS (--user --host) for user@hostname)"
|
329
|
+
method_option :user, type: :boolean, aliases: %w(-u),
|
330
|
+
desc: "Include the ssh username in front of the hostname for easy SSH scripting"
|
331
|
+
method_option :account, type: :string, aliases: %w(-c),
|
332
|
+
required: true, default: '',
|
333
|
+
desc: "Find environment in this account"
|
334
|
+
method_option :environment, type: :string, aliases: %w(-e),
|
335
|
+
required: true, default: '',
|
336
|
+
desc: "Show servers in environment matching environment name"
|
337
|
+
method_option :all, type: :boolean, aliases: %(-A),
|
338
|
+
desc: "Show all servers (for compatibility only, this is the default for this command)"
|
339
|
+
method_option :app_master, type: :boolean,
|
340
|
+
desc: "Show only app master server"
|
341
|
+
method_option :app_servers, type: :boolean, aliases: %w(--app),
|
342
|
+
desc: "Show only application servers"
|
343
|
+
method_option :db_servers, type: :boolean, aliases: %w(--db),
|
344
|
+
desc: "Show only database servers"
|
345
|
+
method_option :db_master, type: :boolean,
|
346
|
+
desc: "Show only the master database server"
|
347
|
+
method_option :db_slaves, type: :boolean,
|
348
|
+
desc: "Show only the slave database servers"
|
349
|
+
method_option :utilities, type: :array, lazy_default: true, aliases: %w(--util),
|
350
|
+
desc: "Show only utility servers or only utility servers with the given names"
|
351
|
+
def servers
|
352
|
+
if options[:environment] == '' && options[:account] == ''
|
353
|
+
repo.fail_on_no_remotes!
|
354
|
+
end
|
355
|
+
|
356
|
+
environment = nil
|
357
|
+
ui.mute_if(options[:simple] || options[:host]) do
|
358
|
+
environment = fetch_environment(options[:environment], options[:account])
|
359
|
+
end
|
360
|
+
|
361
|
+
username = options[:user] && environment.username
|
362
|
+
|
363
|
+
servers = filter_servers(environment, options, default: {all: true})
|
364
|
+
|
365
|
+
if options[:host]
|
366
|
+
ui.print_hostnames(servers, username)
|
367
|
+
elsif options[:simple]
|
368
|
+
ui.print_simple_servers(servers, username)
|
369
|
+
else
|
370
|
+
ui.print_servers(servers, environment.hierarchy_name, username)
|
371
|
+
end
|
372
|
+
end
|
373
|
+
|
374
|
+
desc "rebuild [--environment ENVIRONMENT]", "Rebuild specified environment."
|
375
|
+
long_desc <<-DESC
|
376
|
+
Engine Yard's main configuration run occurs on all servers. Mainly used to fix
|
377
|
+
failed configuration of new or existing servers, or to update servers to latest
|
378
|
+
Engine Yard stack (e.g. to apply an Engine Yard supplied security
|
379
|
+
patch).
|
380
|
+
|
381
|
+
Note that uploaded recipes are also run after the main configuration run has
|
382
|
+
successfully completed.
|
383
|
+
DESC
|
384
|
+
|
385
|
+
method_option :environment, type: :string, aliases: %w(-e),
|
386
|
+
required: true, default: '',
|
387
|
+
desc: "Environment to rebuild"
|
388
|
+
method_option :account, type: :string, aliases: %w(-c),
|
389
|
+
required: true, default: '',
|
390
|
+
desc: "Name of the account in which the environment can be found"
|
391
|
+
def rebuild
|
392
|
+
environment = fetch_environment(options[:environment], options[:account])
|
393
|
+
ui.info "Updating instances on #{environment.hierarchy_name}"
|
394
|
+
environment.rebuild
|
395
|
+
end
|
396
|
+
map "update" => :rebuild
|
397
|
+
|
398
|
+
desc "rollback [--environment ENVIRONMENT]", "Rollback to the previous deploy."
|
399
|
+
long_desc <<-DESC
|
400
|
+
Uses code from previous deploy in the "/data/APP_NAME/releases" directory on
|
401
|
+
remote server(s) to restart application servers.
|
402
|
+
DESC
|
403
|
+
|
404
|
+
method_option :environment, type: :string, aliases: %w(-e),
|
405
|
+
required: true, default: '',
|
406
|
+
desc: "Environment in which to roll back the application"
|
407
|
+
method_option :app, type: :string, aliases: %w(-a),
|
408
|
+
required: true, default: '',
|
409
|
+
desc: "Name of the application to roll back"
|
410
|
+
method_option :account, type: :string, aliases: %w(-c),
|
411
|
+
required: true, default: '',
|
412
|
+
desc: "Name of the account in which the environment can be found"
|
413
|
+
method_option :verbose, type: :boolean, aliases: %w(-v),
|
414
|
+
desc: "Be verbose"
|
415
|
+
method_option :config, type: :hash, default: {}, aliases: %w(--extra-deploy-hook-options),
|
416
|
+
desc: "Hash made available in deploy hooks (in the 'config' hash), can also override some ey.yml settings."
|
417
|
+
def rollback
|
418
|
+
app_env = fetch_app_environment(options[:app], options[:environment], options[:account])
|
419
|
+
env_config = config.environment_config(app_env.environment_name)
|
420
|
+
deploy_config = EY::DeployConfig.new(options, env_config, repo, ui)
|
421
|
+
|
422
|
+
ui.info "Rolling back #{app_env.hierarchy_name}"
|
423
|
+
|
424
|
+
runner = serverside_runner(app_env, deploy_config.verbose)
|
425
|
+
runner.rollback do |args|
|
426
|
+
args.config = {'deployed_by' => api.current_user.name, 'input_ref' => 'N/A'}.merge(deploy_config.extra_config || {})
|
427
|
+
end
|
428
|
+
|
429
|
+
if runner.call(ui.out, ui.err)
|
430
|
+
ui.info "Rollback complete"
|
431
|
+
else
|
432
|
+
raise EY::Error, "Rollback failed"
|
433
|
+
end
|
434
|
+
end
|
435
|
+
|
436
|
+
desc "ssh [COMMAND] [--all] [--environment ENVIRONMENT]", "Open an ssh session to the master app server, or run a command."
|
437
|
+
long_desc <<-DESC
|
438
|
+
If a command is supplied, it will be run, otherwise a session will be
|
439
|
+
opened. The bridge server (app master) is used for environments with multiple instances.
|
440
|
+
|
441
|
+
Option --all requires a command to be supplied and runs it on all servers or
|
442
|
+
pass --each to connect to each server one after another.
|
443
|
+
|
444
|
+
Note: this command is a bit picky about its ordering. To run a command with arguments on
|
445
|
+
all servers, like "rm -f /some/file", you need to order it like so:
|
446
|
+
|
447
|
+
$ #{banner_base} ssh "rm -f /some/file" -e my-environment --all
|
448
|
+
DESC
|
449
|
+
method_option :environment, type: :string, aliases: %w(-e),
|
450
|
+
required: true, default: '',
|
451
|
+
desc: "Environment to ssh into"
|
452
|
+
method_option :account, type: :string, aliases: %w(-c),
|
453
|
+
required: true, default: '',
|
454
|
+
desc: "Name of the account in which the environment can be found"
|
455
|
+
method_option :all, type: :boolean, aliases: %(-A),
|
456
|
+
desc: "Run command on all servers"
|
457
|
+
method_option :app_servers, type: :boolean,
|
458
|
+
desc: "Run command on all application servers"
|
459
|
+
method_option :db_servers, type: :boolean,
|
460
|
+
desc: "Run command on the database servers"
|
461
|
+
method_option :db_master, type: :boolean,
|
462
|
+
desc: "Run command on the master database server"
|
463
|
+
method_option :db_slaves, type: :boolean,
|
464
|
+
desc: "Run command on the slave database servers"
|
465
|
+
method_option :utilities, type: :array, lazy_default: true,
|
466
|
+
desc: "Run command on the utility servers with the given names. If no names are given, run on all utility servers."
|
467
|
+
method_option :shell, type: :string, default: 'bash', aliases: %w(-s),
|
468
|
+
desc: "Run command in a shell other than bash. Use --no-shell to run the command without a shell."
|
469
|
+
method_option :pty, type: :boolean, default: false, aliases: %w(-t),
|
470
|
+
desc: "If a command is given, run in a pty. Required for interactive commands like sudo."
|
471
|
+
method_option :bind_address, type: :string, aliases: %w(-L),
|
472
|
+
desc: "When a command is not given, pass -L to the ssh command."
|
473
|
+
method_option :each, type: :boolean, default: false,
|
474
|
+
desc: "If no command is given, connect to multiple servers each one after another, instead of exiting with an error."
|
475
|
+
|
476
|
+
def ssh(cmd=nil)
|
477
|
+
environment = fetch_environment(options[:environment], options[:account])
|
478
|
+
instances = filter_servers(environment, options, default: {app_master: true})
|
479
|
+
user = environment.username
|
480
|
+
ssh_opts = []
|
481
|
+
|
482
|
+
if cmd
|
483
|
+
if options[:shell]
|
484
|
+
cmd = Escape.shell_command([options[:shell],'-lc',cmd])
|
485
|
+
end
|
486
|
+
|
487
|
+
if options[:pty]
|
488
|
+
ssh_opts = ["-t"]
|
489
|
+
elsif cmd =~ /sudo/
|
490
|
+
ui.warn "sudo commands often need a tty to run correctly. Use -t option to spawn a tty."
|
491
|
+
end
|
492
|
+
else
|
493
|
+
if instances.size != 1 && options[:each] == false
|
494
|
+
raise NoCommandError.new
|
495
|
+
end
|
496
|
+
|
497
|
+
if options[:bind_address]
|
498
|
+
ssh_opts = ["-L", options[:bind_address]]
|
499
|
+
end
|
500
|
+
end
|
501
|
+
|
502
|
+
ssh_cmd = ["ssh"]
|
503
|
+
ssh_cmd += ssh_opts
|
504
|
+
|
505
|
+
trap(:INT) { abort "Aborting..." }
|
506
|
+
|
507
|
+
exits = []
|
508
|
+
instances.each do |instance|
|
509
|
+
host = instance.public_hostname
|
510
|
+
name = instance.name ? "#{instance.role} (#{instance.name})" : instance.role
|
511
|
+
ui.info "\nConnecting to #{name} #{host}..."
|
512
|
+
unless cmd
|
513
|
+
ui.info "Ctrl + C to abort"
|
514
|
+
sleep 1.3
|
515
|
+
end
|
516
|
+
sshcmd = Escape.shell_command((ssh_cmd + ["#{user}@#{host}"] + [cmd]).compact)
|
517
|
+
ui.debug "$ #{sshcmd}"
|
518
|
+
system sshcmd
|
519
|
+
exits << $?.exitstatus
|
520
|
+
end
|
521
|
+
|
522
|
+
exit exits.detect {|status| status != 0 } || 0
|
523
|
+
end
|
524
|
+
|
525
|
+
desc "console [--app APP] [--environment ENVIRONMENT] [--account ACCOUNT]", "Open a Rails console session to the master app server."
|
526
|
+
long_desc <<-DESC
|
527
|
+
Opens a Rails console session on app master.
|
528
|
+
DESC
|
529
|
+
method_option :environment, type: :string, aliases: %w(-e),
|
530
|
+
required: true, default: '',
|
531
|
+
desc: "Environment to console into"
|
532
|
+
method_option :app, type: :string, aliases: %w(-a),
|
533
|
+
required: true, default: '',
|
534
|
+
desc: "Name of the application"
|
535
|
+
method_option :account, type: :string, aliases: %w(-c),
|
536
|
+
required: true, default: '',
|
537
|
+
desc: "Name of the account in which the environment can be found"
|
538
|
+
|
539
|
+
def console
|
540
|
+
app_env = fetch_app_environment(options[:app], options[:environment], options[:account])
|
541
|
+
instances = filter_servers(app_env.environment, options, default: {app_master: true})
|
542
|
+
user = app_env.environment.username
|
543
|
+
cmd = "cd /data/#{app_env.app.name}/current && current_user=#{api.current_user.name} bundle exec rails console"
|
544
|
+
cmd = Escape.shell_command(['bash','-lc',cmd])
|
545
|
+
|
546
|
+
ssh_cmd = ["ssh"]
|
547
|
+
ssh_cmd += ["-t"]
|
548
|
+
|
549
|
+
trap(:INT) { abort "Aborting..." }
|
550
|
+
|
551
|
+
exits = []
|
552
|
+
instances.each do |instance|
|
553
|
+
host = instance.public_hostname
|
554
|
+
name = instance.name ? "#{instance.role} (#{instance.name})" : instance.role
|
555
|
+
ui.info "\nConnecting to #{name} #{host}..."
|
556
|
+
unless cmd
|
557
|
+
ui.info "Ctrl + C to abort"
|
558
|
+
sleep 1.3
|
559
|
+
end
|
560
|
+
sshcmd = Escape.shell_command((ssh_cmd + ["#{user}@#{host}"] + [cmd]).compact)
|
561
|
+
ui.debug "$ #{sshcmd}"
|
562
|
+
system sshcmd
|
563
|
+
exits << $?.exitstatus
|
564
|
+
end
|
565
|
+
|
566
|
+
exit exits.detect {|status| status != 0 } || 0
|
567
|
+
end
|
568
|
+
|
569
|
+
desc "scp [FROM_PATH] [TO_PATH] [--all] [--environment ENVIRONMENT]", "scp a file to/from multiple servers in an environment"
|
570
|
+
long_desc <<-DESC
|
571
|
+
Use the system `scp` command to copy files to some or all of the servers.
|
572
|
+
|
573
|
+
If `HOST:` is found in the FROM_PATH or TO_PATH, the server name will be
|
574
|
+
substituted in place of `HOST:` when scp is run. This allows you to scp in
|
575
|
+
either direction by putting `HOST:` in the FROM_PATH or TO_PATH, as follows:
|
576
|
+
|
577
|
+
$ #{banner_base} scp example.json HOST:/data/app_name/current/config/ -e env --app-servers
|
578
|
+
|
579
|
+
$ #{banner_base} scp HOST:/data/app_name/current/config/example.json ./ -e env --app-servers
|
580
|
+
|
581
|
+
If `HOST:` is not specified, TO_PATH will be used as the remote path.
|
582
|
+
Be sure to escape shell words so they don't expand locally (e.g. '~').
|
583
|
+
|
584
|
+
Note: this command is a bit picky about its ordering. FROM_PATH TO_PATH
|
585
|
+
must follow immediately after `ey scp` with no flags in between.
|
586
|
+
DESC
|
587
|
+
method_option :environment, :type => :string, :aliases => %w(-e),
|
588
|
+
:required => true, :default => '',
|
589
|
+
:desc => "Name of the destination environment"
|
590
|
+
method_option :account, :type => :string, :aliases => %w(-c),
|
591
|
+
:required => true, :default => '',
|
592
|
+
:desc => "Name of the account in which the environment can be found"
|
593
|
+
method_option :all, :type => :boolean, :aliases => %(-A),
|
594
|
+
:desc => "scp to all servers"
|
595
|
+
method_option :app_servers, :type => :boolean,
|
596
|
+
:desc => "scp to all application servers"
|
597
|
+
method_option :db_servers, :type => :boolean,
|
598
|
+
:desc => "scp to database servers"
|
599
|
+
method_option :db_master, :type => :boolean,
|
600
|
+
:desc => "scp to the master database server"
|
601
|
+
method_option :db_slaves, :type => :boolean,
|
602
|
+
:desc => "scp to the slave database servers"
|
603
|
+
method_option :utilities, :type => :array, :lazy_default => true,
|
604
|
+
:desc => "scp to all utility servers or only those with the given names"
|
605
|
+
|
606
|
+
def scp(from_path, to_path)
|
607
|
+
environment = fetch_environment(options[:environment], options[:account])
|
608
|
+
instances = filter_servers(environment, options, default: {app_master: true})
|
609
|
+
user = environment.username
|
610
|
+
|
611
|
+
ui.info "Copying '#{from_path}' to '#{to_path}' on #{instances.count} server#{instances.count == 1 ? '' : 's'} serially..."
|
612
|
+
|
613
|
+
# default to `scp FROM_PATH HOST:TO_PATH`
|
614
|
+
unless [from_path, to_path].detect { |path| path =~ /HOST:/ }
|
615
|
+
to_path = "HOST:#{to_path}"
|
616
|
+
end
|
617
|
+
|
618
|
+
exits = []
|
619
|
+
instances.each do |instance|
|
620
|
+
host = instance.public_hostname
|
621
|
+
authority = "#{user}@#{host}:"
|
622
|
+
|
623
|
+
name = instance.name ? "#{instance.role} (#{instance.name})" : instance.role
|
624
|
+
ui.info "# #{name} #{host}"
|
625
|
+
|
626
|
+
from = from_path.sub(/^HOST:/, authority)
|
627
|
+
to = to_path.sub(/^HOST:/, authority)
|
628
|
+
|
629
|
+
cmd = Escape.shell_command(["scp", from, to])
|
630
|
+
ui.debug "$ #{cmd}"
|
631
|
+
system cmd
|
632
|
+
exits << $?.exitstatus
|
633
|
+
end
|
634
|
+
|
635
|
+
exit exits.detect {|status| status != 0 } || 0
|
636
|
+
end
|
637
|
+
|
638
|
+
no_tasks do
|
639
|
+
OPT_TO_ROLES = {
|
640
|
+
all: %w[all],
|
641
|
+
app_master: %w[solo app_master],
|
642
|
+
app_servers: %w[solo app app_master],
|
643
|
+
db_servers: %w[solo db_master db_slave],
|
644
|
+
db_master: %w[solo db_master],
|
645
|
+
db_slaves: %w[db_slave],
|
646
|
+
utilities: %w[util],
|
647
|
+
}
|
648
|
+
|
649
|
+
def filter_servers(environment, cli_opts, filter_opts)
|
650
|
+
if (cli_opts.keys.map(&:to_sym) & OPT_TO_ROLES.keys).any?
|
651
|
+
options = cli_opts.dup
|
652
|
+
else
|
653
|
+
options = filter_opts[:default].dup
|
654
|
+
end
|
655
|
+
|
656
|
+
options.keep_if {|k,v| OPT_TO_ROLES.has_key?(k.to_sym) }
|
657
|
+
|
658
|
+
if options[:all]
|
659
|
+
instances = environment.instances
|
660
|
+
else
|
661
|
+
roles = {}
|
662
|
+
options.each do |cli_opt,cli_val|
|
663
|
+
if cli_val && OPT_TO_ROLES.has_key?(cli_opt.to_sym)
|
664
|
+
OPT_TO_ROLES[cli_opt.to_sym].each do |role|
|
665
|
+
roles[role] = cli_val # val is true or an array of strings
|
666
|
+
end
|
667
|
+
end
|
668
|
+
end
|
669
|
+
instances = environment.select_instances(roles)
|
670
|
+
end
|
671
|
+
|
672
|
+
if instances.empty?
|
673
|
+
raise NoInstancesError.new(environment.name)
|
674
|
+
end
|
675
|
+
|
676
|
+
return instances
|
677
|
+
end
|
678
|
+
end
|
679
|
+
|
680
|
+
desc "logs [--environment ENVIRONMENT]", "Retrieve the latest logs for an environment."
|
681
|
+
long_desc <<-DESC
|
682
|
+
Displays Engine Yard configuration logs for all servers in the environment. If
|
683
|
+
recipes were uploaded to the environment & run, their logs will also be
|
684
|
+
displayed beneath the main configuration logs.
|
685
|
+
DESC
|
686
|
+
method_option :environment, type: :string, aliases: %w(-e),
|
687
|
+
required: true, default: '',
|
688
|
+
desc: "Environment with the interesting logs"
|
689
|
+
method_option :account, type: :string, aliases: %w(-c),
|
690
|
+
required: true, default: '',
|
691
|
+
desc: "Name of the account in which the environment can be found"
|
692
|
+
def logs
|
693
|
+
environment = fetch_environment(options[:environment], options[:account])
|
694
|
+
environment.logs.each do |log|
|
695
|
+
ui.say "Instance: #{log.instance_name}"
|
696
|
+
|
697
|
+
if log.main
|
698
|
+
ui.say "Main logs for #{environment.name}:", :green
|
699
|
+
ui.say log.main
|
700
|
+
end
|
701
|
+
|
702
|
+
if log.custom
|
703
|
+
ui.say "Custom logs for #{environment.name}:", :green
|
704
|
+
ui.say log.custom
|
705
|
+
end
|
706
|
+
end
|
707
|
+
end
|
708
|
+
|
709
|
+
desc "recipes", "Commands related to chef recipes."
|
710
|
+
subcommand "recipes", EY::CLI::Recipes
|
711
|
+
|
712
|
+
desc "web", "Commands related to maintenance pages."
|
713
|
+
subcommand "web", EY::CLI::Web
|
714
|
+
|
715
|
+
desc "version", "Print version number."
|
716
|
+
def version
|
717
|
+
ui.say %{engineyard version #{EY::VERSION}}
|
718
|
+
end
|
719
|
+
map ["-v", "--version"] => :version
|
720
|
+
|
721
|
+
desc "help [COMMAND]", "Describe all commands or one specific command."
|
722
|
+
def help(*cmds)
|
723
|
+
if cmds.empty?
|
724
|
+
base = self.class.send(:banner_base)
|
725
|
+
list = self.class.printable_tasks
|
726
|
+
|
727
|
+
ui.say "Usage:"
|
728
|
+
ui.say " #{base} [--help] [--version] COMMAND [ARGS]"
|
729
|
+
ui.say
|
730
|
+
|
731
|
+
ui.say "Deploy commands:"
|
732
|
+
deploy_cmds = %w(deploy environments logs rebuild rollback status)
|
733
|
+
deploy_cmds.map! do |name|
|
734
|
+
list.find{|task| task[0] =~ /^#{base} #{name}/ }
|
735
|
+
end
|
736
|
+
list -= deploy_cmds
|
737
|
+
|
738
|
+
ui.print_help(deploy_cmds)
|
739
|
+
ui.say
|
740
|
+
|
741
|
+
self.class.subcommands.each do |name|
|
742
|
+
klass = self.class.subcommand_class_for(name)
|
743
|
+
list.reject!{|cmd| cmd[0] =~ /^#{base} #{name}/}
|
744
|
+
ui.say "#{name.capitalize} commands:"
|
745
|
+
tasks = klass.printable_tasks.reject{|t| t[0] =~ /help$/ }
|
746
|
+
ui.print_help(tasks)
|
747
|
+
ui.say
|
748
|
+
end
|
749
|
+
|
750
|
+
%w(help version).each{|n| list.reject!{|c| c[0] =~ /^#{base} #{n}/ } }
|
751
|
+
if list.any?
|
752
|
+
ui.say "Other commands:"
|
753
|
+
ui.print_help(list)
|
754
|
+
ui.say
|
755
|
+
end
|
756
|
+
|
757
|
+
self.class.send(:class_options_help, shell)
|
758
|
+
ui.say "See '#{base} help COMMAND' for more information on a specific command."
|
759
|
+
elsif klass = self.class.subcommand_class_for(cmds.first)
|
760
|
+
klass.new.help(*cmds[1..-1])
|
761
|
+
else
|
762
|
+
super
|
763
|
+
end
|
764
|
+
end
|
765
|
+
|
766
|
+
desc "launch [--app APP] [--environment ENVIRONMENT] [--account ACCOUNT]", "Open application in browser."
|
767
|
+
method_option :environment, type: :string, aliases: %w(-e),
|
768
|
+
required: true, default: '',
|
769
|
+
desc: "Environment where the application is deployed"
|
770
|
+
method_option :app, type: :string, aliases: %w(-a),
|
771
|
+
required: true, default: '',
|
772
|
+
desc: "Name of the application"
|
773
|
+
method_option :account, type: :string, aliases: %w(-c),
|
774
|
+
required: true, default: '',
|
775
|
+
desc: "Name of the account in which the application can be found"
|
776
|
+
def launch
|
777
|
+
app_env = fetch_app_environment(options[:app], options[:environment], options[:account])
|
778
|
+
Launchy.open(app_env.uri)
|
779
|
+
end
|
780
|
+
|
781
|
+
desc "whoami", "Who am I logged in as?"
|
782
|
+
def whoami
|
783
|
+
current_user = api.current_user
|
784
|
+
ui.say "#{current_user.name} (#{current_user.email})"
|
785
|
+
end
|
786
|
+
|
787
|
+
desc "login", "Log in and verify access to Engine Yard Cloud."
|
788
|
+
long_desc <<-DESC
|
789
|
+
You may run this command to log in to EY Cloud without performing
|
790
|
+
any other action.
|
791
|
+
|
792
|
+
Once you are logged in, a file will be stored at ~/.eyrc with your
|
793
|
+
API token. You may override the location of this file using the
|
794
|
+
$EYRC environment variable.
|
795
|
+
|
796
|
+
Instead of logging in, you may specify a token on the command line
|
797
|
+
with --api-token or using the $ENGINEYARD_API_TOKEN environment
|
798
|
+
variable.
|
799
|
+
DESC
|
800
|
+
def login
|
801
|
+
whoami
|
802
|
+
end
|
803
|
+
|
804
|
+
desc "logout", "Remove the current API key from ~/.eyrc or env variable $EYRC"
|
805
|
+
def logout
|
806
|
+
eyrc = EYRC.load
|
807
|
+
if eyrc.delete_api_token
|
808
|
+
ui.info "API token removed: #{eyrc.path}"
|
809
|
+
ui.info "Run any other command to login again."
|
810
|
+
else
|
811
|
+
ui.info "Already logged out. Run any other command to login again."
|
812
|
+
end
|
813
|
+
end
|
814
|
+
|
815
|
+
end # CLI
|
816
|
+
end # EY
|