engineyard 1.3.17 → 1.3.18
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +19 -7
- data/bin/ey +1 -0
- data/lib/engineyard/api.rb +6 -1
- data/lib/engineyard/cli.rb +2 -1
- data/lib/engineyard/cli/recipes.rb +37 -10
- data/lib/engineyard/cli/ui.rb +15 -25
- data/lib/engineyard/model/environment.rb +19 -9
- data/lib/engineyard/version.rb +1 -1
- data/spec/engineyard/api_spec.rb +7 -0
- data/spec/ey/recipes/upload_spec.rb +52 -3
- data/spec/spec_helper.rb +1 -0
- data/spec/support/fake_awsm.ru +20 -388
- data/spec/support/fixture_recipes.tgz +0 -0
- data/spec/support/helpers.rb +10 -0
- data/spec/support/scenarios.rb +361 -0
- data/spec/support/shared_behavior.rb +2 -0
- metadata +57 -53
data/README.rdoc
CHANGED
@@ -5,13 +5,16 @@ Command:
|
|
5
5
|
ey deploy
|
6
6
|
|
7
7
|
Options:
|
8
|
-
-r, --branch, --tag, [--ref=REF]
|
9
|
-
|
10
|
-
-v, [--verbose]
|
11
|
-
-a, [--app=APP]
|
12
|
-
-m, [--migrate=MIGRATE]
|
13
|
-
|
14
|
-
-e, [--environment=ENVIRONMENT]
|
8
|
+
-r, --branch, --tag, [--ref=REF] # Git ref to deploy. May be a branch, a tag, or a SHA.
|
9
|
+
[--ignore-bad-master] # Force a deploy even if the master is in a bad state
|
10
|
+
-v, [--verbose] # Be verbose
|
11
|
+
-a, [--app=APP] # Name of the application to deploy
|
12
|
+
-m, [--migrate=MIGRATE] # Run migrations via [MIGRATE], defaults to 'rake db:migrate'; use --no-migrate to avoid running migrations
|
13
|
+
[--ignore-default-branch] # Force a deploy of the specified branch even if a default is set
|
14
|
+
-e, [--environment=ENVIRONMENT] # Environment in which to deploy this application
|
15
|
+
-c, [--account=ACCOUNT] # Name of the account in which the environment can be found
|
16
|
+
[--extra-deploy-hook-options key:val] # Additional options to be made available in deploy hooks (in the 'config' hash)
|
17
|
+
|
15
18
|
|
16
19
|
Description:
|
17
20
|
This command must be run with the current directory containing the app to be deployed. If ey.yml specifies a default branch then the ref parameter can be omitted. Furthermore,
|
@@ -35,6 +38,7 @@ Command:
|
|
35
38
|
|
36
39
|
Options:
|
37
40
|
-e, [--environment=ENVIRONMENT] # Environment with the interesting logs
|
41
|
+
-c, [--account=ACCOUNT] # Name of the account in which the environment can be found
|
38
42
|
|
39
43
|
Description:
|
40
44
|
Displays Engine Yard configuration logs for all servers in the environment. If recipes were uploaded to the environment & run, their logs will also be displayed beneath the
|
@@ -45,6 +49,7 @@ Command:
|
|
45
49
|
|
46
50
|
Options:
|
47
51
|
-e, [--environment=ENVIRONMENT] # Environment to rebuild
|
52
|
+
-c, [--account=ACCOUNT] # Name of the account in which the environment can be found
|
48
53
|
|
49
54
|
Description:
|
50
55
|
Engine Yard's main configuration run occurs on all servers. Mainly used to fix failed configuration of new or existing servers, or to update servers to latest Engine Yard stack
|
@@ -59,6 +64,7 @@ Command:
|
|
59
64
|
-v, [--verbose] # Be verbose
|
60
65
|
-a, [--app=APP] # Name of the application to roll back
|
61
66
|
-e, [--environment=ENVIRONMENT] # Environment in which to roll back the application
|
67
|
+
-c, [--account=ACCOUNT] # Name of the account in which the environment can be found
|
62
68
|
|
63
69
|
Description:
|
64
70
|
Uses code from previous deploy in the "/data/APP_NAME/releases" directory on remote server(s) to restart application servers.
|
@@ -68,6 +74,7 @@ Command:
|
|
68
74
|
|
69
75
|
Options:
|
70
76
|
-e, [--environment=ENVIRONMENT] # Environment in which to apply recipes
|
77
|
+
-c, [--account=ACCOUNT] # Name of the account in which the environment can be found
|
71
78
|
|
72
79
|
Description:
|
73
80
|
This is similar to 'ey rebuild' except Engine Yard's main configuration step is skipped.
|
@@ -77,6 +84,7 @@ Command:
|
|
77
84
|
|
78
85
|
Options:
|
79
86
|
-e, [--environment=ENVIRONMENT] # Environment that will receive the recipes
|
87
|
+
-c, [--account=ACCOUNT] # Name of the account in which the environment can be found
|
80
88
|
|
81
89
|
Description:
|
82
90
|
The current directory should contain a subdirectory named "cookbooks" to be uploaded.
|
@@ -86,6 +94,7 @@ Command:
|
|
86
94
|
|
87
95
|
Options:
|
88
96
|
-e, [--environment=ENVIRONMENT] # Environment for which to download the recipes
|
97
|
+
-c, [--account=ACCOUNT] # Name of the account in which the environment can be found
|
89
98
|
|
90
99
|
Description:
|
91
100
|
The recipes will be unpacked into a directory called "cookbooks" in the current directory.
|
@@ -99,6 +108,7 @@ Command:
|
|
99
108
|
-v, [--verbose] # Be verbose
|
100
109
|
-a, [--app=APP] # Name of the application whose maintenance page will be removed
|
101
110
|
-e, [--environment=ENVIRONMENT] # Environment on which to take down the maintenance page
|
111
|
+
-c, [--account=ACCOUNT] # Name of the account in which the environment can be found
|
102
112
|
|
103
113
|
Remove the maintenance page for this application in the given environment.
|
104
114
|
|
@@ -109,6 +119,7 @@ Command:
|
|
109
119
|
-v, [--verbose] # Be verbose
|
110
120
|
-a, [--app=APP] # Name of the application whose maintenance page will be put up
|
111
121
|
-e, [--environment=ENVIRONMENT] # Environment on which to put up the maintenance page
|
122
|
+
-c, [--account=ACCOUNT] # Name of the account in which the environment can be found
|
112
123
|
|
113
124
|
Description:
|
114
125
|
The maintenance page is taken from the app currently being deployed. This means that you can customize maintenance pages to tell users the reason for downtime on every
|
@@ -128,6 +139,7 @@ Command:
|
|
128
139
|
-a, [--all] # Run command on all servers
|
129
140
|
[--db-slaves] # Run command on the slave database servers
|
130
141
|
-e, [--environment=ENVIRONMENT] # Environment to ssh into
|
142
|
+
-c, [--account=ACCOUNT] # Name of the account in which the environment can be found
|
131
143
|
|
132
144
|
Description:
|
133
145
|
If a command is supplied, it will be run, otherwise a session will be opened. The application master is used for environments with clusters. Option --all requires a command to
|
data/bin/ey
CHANGED
data/lib/engineyard/api.rb
CHANGED
@@ -4,6 +4,8 @@ module EY
|
|
4
4
|
class API
|
5
5
|
attr_reader :token
|
6
6
|
|
7
|
+
USER_AGENT_STRING = "EngineYardCLI/#{VERSION}"
|
8
|
+
|
7
9
|
def initialize(token = nil)
|
8
10
|
@token ||= token
|
9
11
|
@token ||= self.class.read_token
|
@@ -47,10 +49,11 @@ module EY
|
|
47
49
|
require 'json'
|
48
50
|
|
49
51
|
url = EY.config.endpoint + "api/v2#{path}"
|
50
|
-
method = (
|
52
|
+
method = (opts.delete(:method) || 'get').to_s.downcase.to_sym
|
51
53
|
params = opts.delete(:params) || {}
|
52
54
|
headers = opts.delete(:headers) || {}
|
53
55
|
headers["Accept"] ||= "application/json"
|
56
|
+
headers["User-Agent"] = USER_AGENT_STRING
|
54
57
|
|
55
58
|
begin
|
56
59
|
EY.ui.debug("Request", "#{method.to_s.upcase} #{url}")
|
@@ -67,6 +70,8 @@ module EY
|
|
67
70
|
raise RequestFailed, "Could not reach the cloud API"
|
68
71
|
rescue RestClient::ResourceNotFound
|
69
72
|
raise RequestFailed, "The requested resource could not be found"
|
73
|
+
rescue RestClient::BadGateway
|
74
|
+
raise RequestFailed, "AppCloud API is temporarily unavailable. Please try again soon."
|
70
75
|
rescue RestClient::RequestFailed => e
|
71
76
|
raise RequestFailed, "#{e.message} #{e.response}"
|
72
77
|
rescue OpenSSL::SSL::SSLError
|
data/lib/engineyard/cli.rb
CHANGED
@@ -126,6 +126,7 @@ module EY
|
|
126
126
|
EY.ui.debug("Rebuilding #{environment.name}")
|
127
127
|
environment.rebuild
|
128
128
|
end
|
129
|
+
map "update" => :rebuild
|
129
130
|
|
130
131
|
desc "rollback [--environment ENVIRONMENT]", "Rollback to the previous deploy."
|
131
132
|
long_desc <<-DESC
|
@@ -186,7 +187,7 @@ module EY
|
|
186
187
|
environment = fetch_environment(options[:environment], options[:account])
|
187
188
|
hosts = ssh_hosts(options, environment)
|
188
189
|
|
189
|
-
raise NoCommandError.new if cmd.nil? and hosts.
|
190
|
+
raise NoCommandError.new if cmd.nil? and hosts.size != 1
|
190
191
|
|
191
192
|
hosts.each do |host|
|
192
193
|
system Escape.shell_command(['ssh', "#{environment.username}@#{host}", cmd].compact)
|
@@ -2,13 +2,13 @@ module EY
|
|
2
2
|
class CLI
|
3
3
|
class Recipes < EY::Thor
|
4
4
|
desc "apply [--environment ENVIRONMENT]",
|
5
|
-
"Run chef recipes uploaded by
|
5
|
+
"Run chef recipes uploaded by '#{banner_base} recipes upload' on the specified environment."
|
6
6
|
long_desc <<-DESC
|
7
7
|
This is similar to '#{banner_base} rebuild' except Engine Yard's main
|
8
8
|
configuration step is skipped.
|
9
9
|
|
10
|
-
The cookbook uploaded by the 'recipes upload' command will be run when
|
11
|
-
you run 'recipes apply'.
|
10
|
+
The cookbook uploaded by the '#{banner_base} recipes upload' command will be run when
|
11
|
+
you run '#{banner_base} recipes apply'.
|
12
12
|
DESC
|
13
13
|
|
14
14
|
method_option :environment, :type => :string, :aliases => %w(-e),
|
@@ -17,27 +17,54 @@ module EY
|
|
17
17
|
:desc => "Name of the account in which the environment can be found"
|
18
18
|
def apply
|
19
19
|
environment = fetch_environment(options[:environment], options[:account])
|
20
|
-
environment
|
21
|
-
EY.ui.say "Uploaded recipes started for #{environment.name}"
|
20
|
+
apply_recipes(environment)
|
22
21
|
end
|
23
22
|
|
24
23
|
desc "upload [--environment ENVIRONMENT]",
|
25
24
|
"Upload custom chef recipes to specified environment so they can be applied."
|
26
25
|
long_desc <<-DESC
|
27
|
-
|
28
|
-
|
26
|
+
Make an archive of the "cookbooks/" subdirectory in your current working
|
27
|
+
directory and upload it to AppCloud's recipe storage.
|
29
28
|
|
30
|
-
|
29
|
+
Alternatively, specify a .tgz of a cookbooks/ directory yourself as follows:
|
30
|
+
|
31
|
+
$ #{banner_base} recipes upload -f path/to/recipes.tgz
|
32
|
+
|
33
|
+
The uploaded cookbooks will be run when executing '#{banner_base} recipes apply'
|
34
|
+
and also automatically each time you update/rebuild your instances.
|
31
35
|
DESC
|
32
36
|
|
33
37
|
method_option :environment, :type => :string, :aliases => %w(-e),
|
34
38
|
:desc => "Environment that will receive the recipes"
|
35
39
|
method_option :account, :type => :string, :aliases => %w(-c),
|
36
40
|
:desc => "Name of the account in which the environment can be found"
|
41
|
+
method_option :apply, :type => :boolean,
|
42
|
+
:desc => "Apply the recipes immediately after they are uploaded"
|
43
|
+
method_option :file, :type => :string, :aliases => %w(-f),
|
44
|
+
:desc => "Specify a gzipped tar file (.tgz) for upload instead of cookbooks/ directory"
|
37
45
|
def upload
|
38
46
|
environment = fetch_environment(options[:environment], options[:account])
|
39
|
-
environment
|
40
|
-
|
47
|
+
upload_recipes(environment, options[:file])
|
48
|
+
if options[:apply]
|
49
|
+
apply_recipes(environment)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
no_tasks do
|
54
|
+
def apply_recipes(environment)
|
55
|
+
environment.run_custom_recipes
|
56
|
+
EY.ui.say "Uploaded recipes started for #{environment.name}"
|
57
|
+
end
|
58
|
+
|
59
|
+
def upload_recipes(environment, filename)
|
60
|
+
if options[:file]
|
61
|
+
environment.upload_recipes_at_path(options[:file])
|
62
|
+
EY.ui.say "Recipes file #{options[:file]} uploaded successfully for #{environment.name}"
|
63
|
+
else
|
64
|
+
environment.tar_and_upload_recipes_in_cookbooks_dir
|
65
|
+
EY.ui.say "Recipes in cookbooks/ uploaded successfully for #{environment.name}"
|
66
|
+
end
|
67
|
+
end
|
41
68
|
end
|
42
69
|
|
43
70
|
desc "download [--environment ENVIRONMENT]",
|
data/lib/engineyard/cli/ui.rb
CHANGED
@@ -28,43 +28,33 @@ module EY
|
|
28
28
|
end
|
29
29
|
|
30
30
|
def error(name, message = nil)
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
elsif name
|
36
|
-
say name, :red
|
37
|
-
end
|
38
|
-
ensure
|
39
|
-
$stdout = orig_out
|
40
|
-
end
|
31
|
+
$stdout = $stderr
|
32
|
+
say_with_status(name, message, :red)
|
33
|
+
ensure
|
34
|
+
$stdout = STDOUT
|
41
35
|
end
|
42
36
|
|
43
37
|
def warn(name, message = nil)
|
44
|
-
|
45
|
-
say_status name, message, :yellow
|
46
|
-
elsif name
|
47
|
-
say name, :yellow
|
48
|
-
end
|
38
|
+
say_with_status(name, message, :yellow)
|
49
39
|
end
|
50
40
|
|
51
41
|
def info(name, message = nil)
|
52
|
-
|
53
|
-
say_status name, message, :green
|
54
|
-
elsif name
|
55
|
-
say name, :green
|
56
|
-
end
|
42
|
+
say_with_status(name, message, :green)
|
57
43
|
end
|
58
44
|
|
59
45
|
def debug(name, message = nil)
|
60
|
-
|
46
|
+
if ENV["DEBUG"]
|
47
|
+
name = name.inspect unless name.nil? or name.is_a?(String)
|
48
|
+
message = message.inspect unless message.nil? or message.is_a?(String)
|
49
|
+
say_with_status(name, message, :blue)
|
50
|
+
end
|
51
|
+
end
|
61
52
|
|
53
|
+
def say_with_status(name, message=nil, color=nil)
|
62
54
|
if message
|
63
|
-
|
64
|
-
say_status name, message, :blue
|
55
|
+
say_status name, message, color
|
65
56
|
elsif name
|
66
|
-
|
67
|
-
say name, :cyan
|
57
|
+
say name, color
|
68
58
|
end
|
69
59
|
end
|
70
60
|
|
@@ -71,6 +71,7 @@ module EY
|
|
71
71
|
tmp = Tempfile.new("recipes")
|
72
72
|
tmp.write(api.request("/environments/#{id}/recipes"))
|
73
73
|
tmp.flush
|
74
|
+
tmp.close
|
74
75
|
|
75
76
|
cmd = "tar xzf '#{tmp.path}' cookbooks"
|
76
77
|
|
@@ -79,27 +80,36 @@ module EY
|
|
79
80
|
end
|
80
81
|
end
|
81
82
|
|
82
|
-
def
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
83
|
+
def upload_recipes_at_path(recipes_path)
|
84
|
+
recipes_path = Pathname.new(recipes_path)
|
85
|
+
if recipes_path.exist?
|
86
|
+
upload_recipes recipes_path.open('r')
|
87
|
+
else
|
88
|
+
raise EY::Error, "Recipes file not found: #{recipes_path}"
|
89
|
+
end
|
87
90
|
end
|
88
91
|
|
89
|
-
def
|
92
|
+
def tar_and_upload_recipes_in_cookbooks_dir
|
90
93
|
require 'tempfile'
|
91
94
|
unless File.exist?("cookbooks")
|
92
95
|
raise EY::Error, "Could not find chef recipes. Please run from the root of your recipes repo."
|
93
96
|
end
|
94
97
|
|
95
|
-
|
96
|
-
cmd = "tar czf '#{
|
98
|
+
recipes_file = Tempfile.new("recipes")
|
99
|
+
cmd = "tar czf '#{recipes_file.path}' cookbooks/"
|
97
100
|
|
98
101
|
unless system(cmd)
|
99
102
|
raise EY::Error, "Could not archive recipes.\nCommand `#{cmd}` exited with an error."
|
100
103
|
end
|
101
104
|
|
102
|
-
|
105
|
+
upload_recipes(recipes_file)
|
106
|
+
end
|
107
|
+
|
108
|
+
def upload_recipes(file_to_upload)
|
109
|
+
api.request("/environments/#{id}/recipes", {
|
110
|
+
:method => :post,
|
111
|
+
:params => {:file => file_to_upload}
|
112
|
+
})
|
103
113
|
end
|
104
114
|
|
105
115
|
def resolve_branch(branch, allow_non_default_branch=false)
|
data/lib/engineyard/version.rb
CHANGED
data/spec/engineyard/api_spec.rb
CHANGED
@@ -53,4 +53,11 @@ describe EY::API do
|
|
53
53
|
}.should raise_error(EY::Error)
|
54
54
|
end
|
55
55
|
|
56
|
+
it "raises RequestFailed with a friendly error when cloud is under maintenance" do
|
57
|
+
FakeWeb.register_uri(:post, "https://cloud.engineyard.com/api/v2/authenticate", :status => 502, :content_type => 'text/html')
|
58
|
+
|
59
|
+
lambda {
|
60
|
+
EY::API.fetch_token("a@b.com", "foo")
|
61
|
+
}.should raise_error(EY::API::RequestFailed, /AppCloud API is temporarily unavailable/)
|
62
|
+
end
|
56
63
|
end
|
@@ -17,12 +17,52 @@ describe "ey recipes upload" do
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def verify_ran(scenario)
|
20
|
-
@out.should =~
|
20
|
+
@out.should =~ %r|Recipes in cookbooks/ uploaded successfully for #{scenario[:environment]}|
|
21
21
|
end
|
22
22
|
|
23
23
|
it_should_behave_like "it takes an environment name and an account name"
|
24
24
|
end
|
25
25
|
|
26
|
+
describe "ey recipes upload -f recipes.tgz" do
|
27
|
+
given "integration"
|
28
|
+
|
29
|
+
define_git_repo('+recipes') do |git_dir|
|
30
|
+
link_recipes_tgz(git_dir)
|
31
|
+
end
|
32
|
+
use_git_repo('+recipes')
|
33
|
+
|
34
|
+
def command_to_run(opts)
|
35
|
+
cmd = %w[recipes upload]
|
36
|
+
cmd << "--environment" << opts[:environment] if opts[:environment]
|
37
|
+
cmd << "--account" << opts[:account] if opts[:account]
|
38
|
+
cmd << "-f" << "recipes.tgz"
|
39
|
+
cmd
|
40
|
+
end
|
41
|
+
|
42
|
+
def verify_ran(scenario)
|
43
|
+
@out.should =~ %r|Recipes file recipes.tgz uploaded successfully for #{scenario[:environment]}|
|
44
|
+
end
|
45
|
+
|
46
|
+
it_should_behave_like "it takes an environment name and an account name"
|
47
|
+
end
|
48
|
+
|
49
|
+
describe "ey recipes upload -f with a missing filenamen" do
|
50
|
+
given "integration"
|
51
|
+
def command_to_run(opts)
|
52
|
+
cmd = %w[recipes upload]
|
53
|
+
cmd << "--environment" << opts[:environment] if opts[:environment]
|
54
|
+
cmd << "--account" << opts[:account] if opts[:account]
|
55
|
+
cmd << "-f" << "recipes.tgz"
|
56
|
+
cmd
|
57
|
+
end
|
58
|
+
|
59
|
+
it "errors with file not found" do
|
60
|
+
api_scenario "one app, one environment"
|
61
|
+
ey(%w[recipes upload --environment giblets -f recipes.tgz], :expect_failure => true)
|
62
|
+
@err.should match(/Recipes file not found: recipes.tgz/i)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
26
66
|
describe "ey recipes upload with an ambiguous git repo" do
|
27
67
|
given "integration"
|
28
68
|
def command_to_run(_) %w[recipes upload] end
|
@@ -48,7 +88,16 @@ describe "ey recipes upload from a separate cookbooks directory" do
|
|
48
88
|
api_scenario "one app, one environment"
|
49
89
|
|
50
90
|
ey %w[recipes upload -e giblets]
|
51
|
-
@out.should =~
|
91
|
+
@out.should =~ %r|Recipes in cookbooks/ uploaded successfully|
|
92
|
+
@out.should_not =~ /Uploaded recipes started for giblets/
|
93
|
+
end
|
94
|
+
|
95
|
+
it "applies the recipes with --apply" do
|
96
|
+
api_scenario "one app, one environment"
|
97
|
+
|
98
|
+
ey %w[recipes upload -e giblets --apply]
|
99
|
+
@out.should =~ %r|Recipes in cookbooks/ uploaded successfully|
|
100
|
+
@out.should =~ /Uploaded recipes started for giblets/
|
52
101
|
end
|
53
102
|
end
|
54
103
|
|
@@ -70,7 +119,7 @@ describe "ey recipes upload from a separate cookbooks directory" do
|
|
70
119
|
api_scenario "one app, one environment"
|
71
120
|
|
72
121
|
ey %w[recipes upload -e giblets]
|
73
|
-
@out.should =~
|
122
|
+
@out.should =~ %r|Recipes in cookbooks/ uploaded successfully|
|
74
123
|
end
|
75
124
|
|
76
125
|
end
|
data/spec/spec_helper.rb
CHANGED