engineyard 1.3.17 → 1.3.18
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/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