engineyard 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/lib/engineyard.rb +1 -1
- data/lib/engineyard/api.rb +12 -1
- data/lib/engineyard/cli.rb +116 -48
- data/lib/engineyard/cli/recipes.rb +37 -7
- data/lib/engineyard/cli/ui.rb +8 -0
- data/lib/engineyard/cli/web.rb +41 -0
- data/lib/engineyard/error.rb +8 -1
- data/lib/engineyard/model/environment.rb +32 -6
- data/lib/engineyard/model/instance.rb +39 -20
- data/lib/engineyard/repo.rb +7 -3
- data/lib/engineyard/thor.rb +55 -2
- data/spec/engineyard/api_spec.rb +2 -2
- data/spec/engineyard/cli/api_spec.rb +1 -1
- data/spec/engineyard/model/environment_spec.rb +8 -6
- data/spec/ey/deploy_spec.rb +46 -81
- data/spec/ey/ey_spec.rb +2 -2
- data/spec/ey/list_environments_spec.rb +1 -1
- data/spec/ey/logs_spec.rb +11 -21
- data/spec/ey/rebuild_spec.rb +8 -33
- data/spec/ey/recipes/apply_spec.rb +8 -33
- data/spec/ey/recipes/download_spec.rb +29 -0
- data/spec/ey/recipes/upload_spec.rb +8 -25
- data/spec/ey/rollback_spec.rb +10 -40
- data/spec/ey/ssh_spec.rb +17 -34
- data/spec/ey/web/disable_spec.rb +18 -0
- data/spec/ey/web/enable_spec.rb +18 -0
- data/spec/spec_helper.rb +7 -3
- data/spec/support/fake_awsm.ru +18 -0
- data/spec/support/helpers.rb +13 -0
- data/spec/support/shared_behavior.rb +132 -0
- metadata +8 -3
data/lib/engineyard.rb
CHANGED
data/lib/engineyard/api.rb
CHANGED
@@ -38,6 +38,15 @@ module EY
|
|
38
38
|
app_for_repo(repo) || raise(NoAppError.new(repo))
|
39
39
|
end
|
40
40
|
|
41
|
+
def fetch_app(name)
|
42
|
+
apps.find{|a| a.name == name}
|
43
|
+
end
|
44
|
+
|
45
|
+
def fetch_app!(name)
|
46
|
+
return unless name
|
47
|
+
fetch_app(name) || raise(InvalidAppError.new(name))
|
48
|
+
end
|
49
|
+
|
41
50
|
class InvalidCredentials < EY::Error; end
|
42
51
|
class RequestFailed < EY::Error; end
|
43
52
|
|
@@ -75,7 +84,7 @@ module EY
|
|
75
84
|
|
76
85
|
if resp.body.empty?
|
77
86
|
data = ''
|
78
|
-
|
87
|
+
elsif resp.headers[:content_type] =~ /application\/json/
|
79
88
|
begin
|
80
89
|
data = JSON.parse(resp.body)
|
81
90
|
EY.ui.debug("Response", data)
|
@@ -83,6 +92,8 @@ module EY
|
|
83
92
|
EY.ui.debug("Raw response", resp.body)
|
84
93
|
raise RequestFailed, "Response was not valid JSON."
|
85
94
|
end
|
95
|
+
else
|
96
|
+
data = resp.body
|
86
97
|
end
|
87
98
|
|
88
99
|
data
|
data/lib/engineyard/cli.rb
CHANGED
@@ -7,44 +7,58 @@ module EY
|
|
7
7
|
autoload :API, 'engineyard/cli/api'
|
8
8
|
autoload :UI, 'engineyard/cli/ui'
|
9
9
|
autoload :Recipes, 'engineyard/cli/recipes'
|
10
|
+
autoload :Web, 'engineyard/cli/web'
|
10
11
|
|
11
12
|
include Thor::Actions
|
12
13
|
|
13
14
|
def self.start(*)
|
15
|
+
Thor::Base.shell = EY::CLI::UI
|
14
16
|
EY.ui = EY::CLI::UI.new
|
15
17
|
super
|
16
18
|
end
|
17
19
|
|
18
|
-
desc "deploy [ENVIRONMENT] [
|
20
|
+
desc "deploy [--environment ENVIRONMENT] [--ref GIT-REF]", <<-DESC
|
21
|
+
Deploy specified branch/tag/sha to specified environment.
|
22
|
+
|
23
|
+
This command must be run with the current directory containing the app to be
|
24
|
+
deployed. If ey.yml specifies a default branch then the ref parameter can be
|
25
|
+
omitted. Furthermore, if a default branch is specified but a different command
|
26
|
+
is supplied the deploy will fail unless --force is used.
|
27
|
+
|
28
|
+
Migrations are run by default with 'rake db:migrate'. A different command can be
|
29
|
+
specified via --migrate "ruby do_migrations.rb". Migrations can also be skipped
|
30
|
+
entirely by using --no-migrate.
|
31
|
+
DESC
|
19
32
|
method_option :force, :type => :boolean, :aliases => %w(-f),
|
20
|
-
:desc => "Force a deploy of the specified branch"
|
33
|
+
:desc => "Force a deploy of the specified branch even if a default is set"
|
21
34
|
method_option :migrate, :type => :string, :aliases => %w(-m),
|
22
35
|
:default => 'rake db:migrate',
|
23
36
|
:desc => "Run migrations via [MIGRATE], defaults to 'rake db:migrate'; use --no-migrate to avoid running migrations"
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
37
|
+
method_option :environment, :type => :string, :aliases => %w(-e),
|
38
|
+
:desc => "Environment in which to deploy this application"
|
39
|
+
method_option :ref, :type => :string, :aliases => %w(-r --branch --tag),
|
40
|
+
:desc => "Git ref to deploy. May be a branch, a tag, or a SHA."
|
41
|
+
method_option :app, :type => :string, :aliases => %w(-a),
|
42
|
+
:desc => "Name of the application to deploy"
|
43
|
+
def deploy
|
44
|
+
app = api.fetch_app!(options[:app]) || api.app_for_repo!(repo)
|
45
|
+
environment = fetch_environment(options[:environment], app)
|
46
|
+
deploy_ref = if options[:app]
|
47
|
+
environment.resolve_branch(options[:ref], options[:force]) ||
|
48
|
+
raise(EY::Error, "When specifying the application, you must also specify the ref to deploy\nUsage: ey deploy --app <app name> --ref <branch|tag|ref>")
|
49
|
+
else
|
50
|
+
environment.resolve_branch(options[:ref], options[:force]) ||
|
51
|
+
repo.current_branch ||
|
52
|
+
raise(DeployArgumentError)
|
53
|
+
end
|
30
54
|
|
31
55
|
EY.ui.info "Connecting to the server..."
|
32
56
|
|
33
|
-
environment
|
34
|
-
case action
|
35
|
-
when :installing
|
36
|
-
EY.ui.warn "Instance does not have server-side component installed"
|
37
|
-
EY.ui.info "Installing server-side component..."
|
38
|
-
when :upgrading
|
39
|
-
EY.ui.info "Upgrading server-side component..."
|
40
|
-
else
|
41
|
-
# nothing slow is happening, so there's nothing to say
|
42
|
-
end
|
43
|
-
end
|
57
|
+
loudly_check_eysd(environment)
|
44
58
|
|
45
59
|
EY.ui.info "Running deploy for '#{environment.name}' on server..."
|
46
60
|
|
47
|
-
if environment.deploy
|
61
|
+
if environment.deploy(app, deploy_ref, options[:migrate])
|
48
62
|
EY.ui.info "Deploy complete"
|
49
63
|
else
|
50
64
|
raise EY::Error, "Deploy failed"
|
@@ -52,11 +66,17 @@ module EY
|
|
52
66
|
|
53
67
|
rescue NoEnvironmentError => e
|
54
68
|
# Give better feedback about why we couldn't find the environment.
|
55
|
-
exists = api.environments.named(
|
56
|
-
raise exists ? EnvironmentUnlinkedError.new(
|
69
|
+
exists = api.environments.named(options[:environment])
|
70
|
+
raise exists ? EnvironmentUnlinkedError.new(options[:environment]) : e
|
57
71
|
end
|
58
72
|
|
59
|
-
desc "environments [--all]",
|
73
|
+
desc "environments [--all]", <<-DESC
|
74
|
+
List environments.
|
75
|
+
|
76
|
+
By default, environments for this app are displayed. If the -all option is
|
77
|
+
used, all environments are displayed instead.
|
78
|
+
DESC
|
79
|
+
|
60
80
|
method_option :all, :type => :boolean, :aliases => %(-a)
|
61
81
|
def environments
|
62
82
|
apps = get_apps(options[:all])
|
@@ -65,33 +85,58 @@ module EY
|
|
65
85
|
end
|
66
86
|
map "envs" => :environments
|
67
87
|
|
68
|
-
desc "rebuild [ENVIRONMENT]",
|
69
|
-
|
70
|
-
|
88
|
+
desc "rebuild [--environment ENVIRONMENT]", <<-DESC
|
89
|
+
Rebuild specified environment.
|
90
|
+
|
91
|
+
Engine Yard's main configuration run occurs on all servers. Mainly used to fix
|
92
|
+
failed configuration of new or existing servers, or to update servers to latest
|
93
|
+
Engine Yard stack (e.g. to apply an Engine Yard supplied security
|
94
|
+
patch).
|
95
|
+
|
96
|
+
Note that uploaded recipes are also run after the main configuration run has
|
97
|
+
successfully completed.
|
98
|
+
DESC
|
99
|
+
|
100
|
+
method_option :environment, :type => :string, :aliases => %w(-e),
|
101
|
+
:desc => "Environment to rebuild"
|
102
|
+
def rebuild
|
103
|
+
env = fetch_environment(options[:environment])
|
71
104
|
EY.ui.debug("Rebuilding #{env.name}")
|
72
105
|
env.rebuild
|
73
106
|
end
|
74
107
|
|
75
|
-
desc "rollback [ENVIRONMENT]",
|
76
|
-
|
77
|
-
app = api.app_for_repo!(repo)
|
78
|
-
env = fetch_environment(name)
|
108
|
+
desc "rollback [--environment ENVIRONMENT]", <<-DESC
|
109
|
+
Rollback to the previous deploy.
|
79
110
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
111
|
+
Uses code from previous deploy in the "/data/APP_NAME/releases" directory on
|
112
|
+
remote server(s) to restart application servers.
|
113
|
+
DESC
|
114
|
+
method_option :environment, :type => :string, :aliases => %w(-e),
|
115
|
+
:desc => "Environment in which to roll back the current application"
|
116
|
+
def rollback
|
117
|
+
app = api.app_for_repo!(repo)
|
118
|
+
env = fetch_environment(options[:environment])
|
119
|
+
|
120
|
+
loudly_check_eysd(env)
|
121
|
+
|
122
|
+
EY.ui.info("Rolling back #{env.name}")
|
123
|
+
if env.rollback(app)
|
124
|
+
EY.ui.info "Rollback complete"
|
87
125
|
else
|
88
|
-
raise
|
126
|
+
raise EY::Error, "Rollback failed"
|
89
127
|
end
|
90
128
|
end
|
91
129
|
|
92
|
-
desc "ssh [ENVIRONMENT]",
|
93
|
-
|
94
|
-
|
130
|
+
desc "ssh [--environment ENVIRONMENT]", <<-DESC
|
131
|
+
Open an ssh session.
|
132
|
+
|
133
|
+
If the environment contains just one server, a session to it will be opened. For
|
134
|
+
environments with clusters, a session will be opened to the application master.
|
135
|
+
DESC
|
136
|
+
method_option :environment, :type => :string, :aliases => %w(-e),
|
137
|
+
:desc => "Environment to ssh into"
|
138
|
+
def ssh
|
139
|
+
env = fetch_environment(options[:environment])
|
95
140
|
|
96
141
|
if env.app_master
|
97
142
|
Kernel.exec "ssh", "#{env.username}@#{env.app_master.public_hostname}"
|
@@ -100,31 +145,54 @@ module EY
|
|
100
145
|
end
|
101
146
|
end
|
102
147
|
|
103
|
-
desc "logs [ENVIRONMENT]",
|
104
|
-
|
105
|
-
|
148
|
+
desc "logs [--environment ENVIRONMENT]", <<-DESC
|
149
|
+
Retrieve the latest logs for an environment.
|
150
|
+
|
151
|
+
Displays Engine Yard configuration logs for all servers in the environment. If
|
152
|
+
recipes were uploaded to the environment & run, their logs will also be
|
153
|
+
displayed beneath the main configuration logs.
|
154
|
+
DESC
|
155
|
+
method_option :environment, :type => :string, :aliases => %w(-e),
|
156
|
+
:desc => "Environment with the interesting logs"
|
157
|
+
def logs
|
158
|
+
env = fetch_environment(options[:environment])
|
159
|
+
env.logs.each do |log|
|
106
160
|
EY.ui.info log.instance_name
|
107
161
|
|
108
162
|
if log.main
|
109
|
-
EY.ui.info "Main logs:"
|
163
|
+
EY.ui.info "Main logs for #{env.name}:"
|
110
164
|
EY.ui.say log.main
|
111
165
|
end
|
112
166
|
|
113
167
|
if log.custom
|
114
|
-
EY.ui.info "Custom logs:"
|
168
|
+
EY.ui.info "Custom logs for #{env.name}:"
|
115
169
|
EY.ui.say log.custom
|
116
170
|
end
|
117
171
|
end
|
118
172
|
end
|
119
173
|
|
120
|
-
desc "recipes
|
174
|
+
desc "recipes", "Commands related to chef recipes."
|
121
175
|
subcommand "recipes", EY::CLI::Recipes
|
122
176
|
|
123
|
-
desc "
|
177
|
+
desc "web", "Commands related to maintenance pages."
|
178
|
+
subcommand "web", EY::CLI::Web
|
179
|
+
|
180
|
+
desc "version", "Print version number."
|
124
181
|
def version
|
125
182
|
EY.ui.say %{engineyard version #{EY::VERSION}}
|
126
183
|
end
|
127
184
|
map ["-v", "--version"] => :version
|
128
185
|
|
186
|
+
desc "help [COMMAND]", "Describe all commands or one specific command."
|
187
|
+
def help(*cmds)
|
188
|
+
if cmds.empty?
|
189
|
+
super
|
190
|
+
EY.ui.say "See '#{self.class.send(:banner_base)} help [COMMAND]' for more information on a specific command."
|
191
|
+
elsif klass = EY::Thor.subcommands[cmds.first]
|
192
|
+
klass.new.help(*cmds[1..-1])
|
193
|
+
else
|
194
|
+
super
|
195
|
+
end
|
196
|
+
end
|
129
197
|
end # CLI
|
130
198
|
end # EY
|
@@ -1,22 +1,52 @@
|
|
1
1
|
module EY
|
2
2
|
class CLI
|
3
3
|
class Recipes < EY::Thor
|
4
|
+
desc "recipes apply [ENVIRONMENT]", <<-DESC
|
5
|
+
Run uploaded chef recipes on specified environment.
|
4
6
|
|
5
|
-
|
6
|
-
|
7
|
-
|
7
|
+
This is similar to '#{banner_base} rebuild' except Engine Yard's main
|
8
|
+
configuration step is skipped.
|
9
|
+
DESC
|
10
|
+
|
11
|
+
method_option :environment, :type => :string, :aliases => %w(-e),
|
12
|
+
:desc => "Environment in which to apply recipes"
|
13
|
+
def apply
|
14
|
+
environment = fetch_environment(options[:environment])
|
8
15
|
environment.run_custom_recipes
|
9
16
|
EY.ui.say "Uploaded recipes started for #{environment.name}"
|
10
17
|
end
|
11
18
|
|
12
|
-
desc "recipes upload [
|
13
|
-
|
14
|
-
|
19
|
+
desc "recipes upload [ENVIRONMENT]", <<-DESC
|
20
|
+
Upload custom chef recipes to specified environment.
|
21
|
+
|
22
|
+
The current directory should contain a subdirectory named "cookbooks" to be
|
23
|
+
uploaded.
|
24
|
+
DESC
|
25
|
+
|
26
|
+
method_option :environment, :type => :string, :aliases => %w(-e),
|
27
|
+
:desc => "Environment that will receive the recipes"
|
28
|
+
def upload
|
29
|
+
environment = fetch_environment(options[:environment])
|
15
30
|
environment.upload_recipes
|
16
31
|
EY.ui.say "Recipes uploaded successfully for #{environment.name}"
|
17
32
|
end
|
18
33
|
|
34
|
+
desc "recipes download [--environment ENVIRONMENT]", <<-DESC
|
35
|
+
Download custom chef recipes from ENVIRONMENT into the current directory.
|
36
|
+
|
37
|
+
The recipes will be unpacked into a directory called "cookbooks" in the
|
38
|
+
current directory.
|
39
|
+
|
40
|
+
If the cookbooks directory already exists, an error will be raised.
|
41
|
+
DESC
|
42
|
+
method_option :environment, :type => :string, :aliases => %w(-e),
|
43
|
+
:desc => "Environment for which to download the recipes"
|
44
|
+
def download
|
45
|
+
environment = fetch_environment(options[:environment])
|
46
|
+
environment.download_recipes
|
47
|
+
EY.ui.say "Recipes downloaded successfully for #{environment.name}"
|
48
|
+
end
|
49
|
+
|
19
50
|
end
|
20
51
|
end
|
21
|
-
|
22
52
|
end
|
data/lib/engineyard/cli/ui.rb
CHANGED
@@ -0,0 +1,41 @@
|
|
1
|
+
module EY
|
2
|
+
class CLI
|
3
|
+
class Web < EY::Thor
|
4
|
+
desc "web enable [ENVIRONMENT]", <<-HELP
|
5
|
+
Take down the maintenance page for the current application in the specified environment.
|
6
|
+
HELP
|
7
|
+
method_option :environment, :type => :string, :aliases => %w(-e),
|
8
|
+
:desc => "Environment on which to put up the maintenance page"
|
9
|
+
def enable
|
10
|
+
app = api.app_for_repo!(repo)
|
11
|
+
environment = fetch_environment(options[:environment], app)
|
12
|
+
loudly_check_eysd(environment)
|
13
|
+
EY.ui.info "Taking down maintenance page for #{environment.name}"
|
14
|
+
environment.take_down_maintenance_page(app)
|
15
|
+
end
|
16
|
+
|
17
|
+
desc "web disable [ENVIRONMENT]", <<-HELP
|
18
|
+
Put up the maintenance page for the current application in the specified environment.
|
19
|
+
|
20
|
+
The maintenance page is taken from the app currently being deployed. This means
|
21
|
+
that you can customize maintenance pages to tell users the reason for downtime
|
22
|
+
on every particular deploy.
|
23
|
+
|
24
|
+
Maintenance pages searched for in order of decreasing priority:
|
25
|
+
* public/maintenance.html.custom
|
26
|
+
* public/maintenance.html.tmp
|
27
|
+
* public/maintenance.html
|
28
|
+
* public/system/maintenance.html.default
|
29
|
+
HELP
|
30
|
+
method_option :environment, :type => :string, :aliases => %w(-e),
|
31
|
+
:desc => "Environment on which to take down the maintenance page"
|
32
|
+
def disable
|
33
|
+
app = api.app_for_repo!(repo)
|
34
|
+
environment = fetch_environment(options[:environment], app)
|
35
|
+
loudly_check_eysd(environment)
|
36
|
+
EY.ui.info "Putting up maintenance page for #{environment.name}"
|
37
|
+
environment.put_up_maintenance_page(app)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/lib/engineyard/error.rb
CHANGED
@@ -16,6 +16,13 @@ module EY
|
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
19
|
+
class InvalidAppError < Error
|
20
|
+
def initialize(name)
|
21
|
+
error = %|There is no app configured with the name "#{name}"|
|
22
|
+
super error
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
19
26
|
class NoAppMaster < EY::Error
|
20
27
|
def initialize(env_name)
|
21
28
|
super "The environment '#{env_name}' does not have a master instance."
|
@@ -67,7 +74,7 @@ module EY
|
|
67
74
|
|
68
75
|
class DeployArgumentError < EY::Error
|
69
76
|
def initialize
|
70
|
-
super
|
77
|
+
super %("deploy" was called incorrectly. Call as "deploy [--environment <env>] [--ref <branch|tag|ref>]"\n) +
|
71
78
|
%|You can set default environments and branches in ey.yml|
|
72
79
|
end
|
73
80
|
end
|
@@ -31,16 +31,24 @@ module EY
|
|
31
31
|
master
|
32
32
|
end
|
33
33
|
|
34
|
-
def ensure_eysd_present
|
35
|
-
app_master!.ensure_eysd_present
|
34
|
+
def ensure_eysd_present(&blk)
|
35
|
+
app_master!.ensure_eysd_present(&blk)
|
36
36
|
end
|
37
37
|
|
38
|
-
def deploy
|
39
|
-
app_master!.deploy
|
38
|
+
def deploy(app, ref, migration_command=nil)
|
39
|
+
app_master!.deploy(app, ref, migration_command, config)
|
40
40
|
end
|
41
41
|
|
42
|
-
def rollback
|
43
|
-
app_master!.rollback
|
42
|
+
def rollback(app)
|
43
|
+
app_master!.rollback(app, config)
|
44
|
+
end
|
45
|
+
|
46
|
+
def take_down_maintenance_page(app)
|
47
|
+
app_master!.take_down_maintenance_page(app)
|
48
|
+
end
|
49
|
+
|
50
|
+
def put_up_maintenance_page(app)
|
51
|
+
app_master!.put_up_maintenance_page(app)
|
44
52
|
end
|
45
53
|
|
46
54
|
def rebuild
|
@@ -51,6 +59,23 @@ module EY
|
|
51
59
|
api.request("/environments/#{id}/run_custom_recipes", :method => :put)
|
52
60
|
end
|
53
61
|
|
62
|
+
def download_recipes
|
63
|
+
if File.exist?('cookbooks')
|
64
|
+
raise EY::Error, "Could not download, cookbooks already exists"
|
65
|
+
end
|
66
|
+
|
67
|
+
require 'tempfile'
|
68
|
+
tmp = Tempfile.new("recipes")
|
69
|
+
tmp.write(api.request("/environments/#{id}/recipes"))
|
70
|
+
tmp.flush
|
71
|
+
|
72
|
+
cmd = "tar xzf '#{tmp.path}' cookbooks"
|
73
|
+
|
74
|
+
unless system(cmd)
|
75
|
+
raise EY::Error, "Could not unarchive recipes.\nCommand `#{cmd}` exited with an error."
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
54
79
|
def upload_recipes(file_to_upload = recipe_file)
|
55
80
|
api.request("/environments/#{id}/recipes",
|
56
81
|
:method => :post,
|
@@ -95,4 +120,5 @@ module EY
|
|
95
120
|
end
|
96
121
|
end
|
97
122
|
end
|
123
|
+
|
98
124
|
end
|