engineyard 0.2.11 → 0.2.12
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/engineyard.rb +4 -3
- data/lib/engineyard/#repo.rb# +24 -0
- data/lib/engineyard/account.rb +32 -11
- data/lib/engineyard/account/api_struct.rb +25 -0
- data/lib/engineyard/account/app.rb +11 -10
- data/lib/engineyard/account/app_master.rb +1 -7
- data/lib/engineyard/account/environment.rb +31 -16
- data/lib/engineyard/account/instance.rb +6 -0
- data/lib/engineyard/account/log.rb +7 -16
- data/lib/engineyard/action/deploy.rb +138 -0
- data/lib/engineyard/action/list_environments.rb +22 -0
- data/lib/engineyard/action/rebuild.rb +31 -0
- data/lib/engineyard/action/show_logs.rb +26 -0
- data/lib/engineyard/action/ssh.rb +19 -0
- data/lib/engineyard/action/upload_recipes.rb +17 -0
- data/lib/engineyard/action/util.rb +47 -0
- data/lib/engineyard/cli.rb +24 -150
- data/lib/engineyard/cli/thor_fixes.rb +26 -0
- data/lib/engineyard/error.rb +48 -0
- data/lib/engineyard/repo.rb +1 -1
- data/lib/engineyard/ruby_ext.rb +9 -0
- data/spec/engineyard/account/api_struct_spec.rb +37 -0
- data/spec/engineyard/account/environment_spec.rb +20 -0
- data/spec/engineyard/account_spec.rb +18 -0
- data/spec/engineyard/cli_spec.rb +3 -3
- data/spec/ey/deploy_spec.rb +166 -28
- data/spec/ey/ey_spec.rb +3 -3
- data/spec/ey/list_environments_spec.rb +16 -0
- data/spec/ey/logs_spec.rb +2 -17
- data/spec/ey/rebuild_spec.rb +25 -0
- data/spec/ey/ssh_spec.rb +24 -0
- data/spec/ey/upload_recipes_spec.rb +21 -0
- data/spec/spec_helper.rb +32 -0
- data/spec/support/fake_awsm.ru +25 -1
- data/spec/support/helpers.rb +72 -7
- metadata +44 -56
- data/lib/engineyard/cli/error.rb +0 -44
- data/spec/spec.opts +0 -2
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'engineyard/action/util'
|
2
|
+
|
3
|
+
module EY
|
4
|
+
module Action
|
5
|
+
class ShowLogs
|
6
|
+
extend Util
|
7
|
+
|
8
|
+
def self.call(name)
|
9
|
+
env_named(name).logs.each do |log|
|
10
|
+
EY.ui.info log.instance_name
|
11
|
+
|
12
|
+
if log.main
|
13
|
+
EY.ui.info "Main logs:"
|
14
|
+
EY.ui.say log.main
|
15
|
+
end
|
16
|
+
|
17
|
+
if log.custom
|
18
|
+
EY.ui.info "Custom logs:"
|
19
|
+
EY.ui.say log.custom
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'engineyard/action/util'
|
2
|
+
|
3
|
+
module EY
|
4
|
+
module Action
|
5
|
+
class SSH
|
6
|
+
extend Util
|
7
|
+
|
8
|
+
def self.call(name)
|
9
|
+
|
10
|
+
env = account.environment_named(name)
|
11
|
+
if env
|
12
|
+
Kernel.exec "ssh", "#{env.username}@#{env.app_master.public_hostname}", *ARGV[2..-1]
|
13
|
+
else
|
14
|
+
EY.ui.warn %|Could not find an environment named "#{name}"|
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'engineyard/action/util'
|
2
|
+
|
3
|
+
module EY
|
4
|
+
module Action
|
5
|
+
class UploadRecipes
|
6
|
+
extend Util
|
7
|
+
|
8
|
+
def self.call(name)
|
9
|
+
if account.upload_recipes_for(env_named(name))
|
10
|
+
EY.ui.say "Recipes uploaded successfully"
|
11
|
+
else
|
12
|
+
EY.ui.error "Recipes upload failed"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module EY
|
2
|
+
module Action
|
3
|
+
module Util
|
4
|
+
|
5
|
+
protected
|
6
|
+
|
7
|
+
def account
|
8
|
+
# XXX it stinks that we have to use EY::CLI::API explicitly
|
9
|
+
# here; I don't want to have this lateral Action --> CLI reference
|
10
|
+
@account ||= EY::Account.new(EY::CLI::API.new)
|
11
|
+
end
|
12
|
+
|
13
|
+
def repo
|
14
|
+
@repo ||= EY::Repo.new
|
15
|
+
end
|
16
|
+
|
17
|
+
def app_and_envs(all_envs = false)
|
18
|
+
app = account.app_for_repo(repo)
|
19
|
+
|
20
|
+
if all_envs || !app
|
21
|
+
envs = account.environments
|
22
|
+
EY.ui.warn(NoAppError.new(repo).message) unless app || all_envs
|
23
|
+
[nil, envs]
|
24
|
+
else
|
25
|
+
envs = app.environments
|
26
|
+
if envs.empty?
|
27
|
+
EY.ui.warn %|You have no environments set up for the application "#{app.name}"|
|
28
|
+
EY.ui.warn %|You can make one at #{EY.config.endpoint}|
|
29
|
+
end
|
30
|
+
envs.empty? ? [app, nil] : [app, envs]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def env_named(name)
|
35
|
+
env = account.environment_named(name)
|
36
|
+
|
37
|
+
if env.nil?
|
38
|
+
raise EnvironmentError, "Environment '#{env_name}' can't be found\n" +
|
39
|
+
"You can create it at #{EY.config.endpoint}"
|
40
|
+
else
|
41
|
+
env
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/lib/engineyard/cli.rb
CHANGED
@@ -1,11 +1,10 @@
|
|
1
1
|
require 'thor'
|
2
2
|
require 'engineyard'
|
3
|
-
require 'engineyard/
|
3
|
+
require 'engineyard/error'
|
4
|
+
require 'engineyard/cli/thor_fixes'
|
4
5
|
|
5
6
|
module EY
|
6
7
|
class CLI < Thor
|
7
|
-
EYSD_VERSION = "~>0.2.6"
|
8
|
-
|
9
8
|
autoload :API, 'engineyard/cli/api'
|
10
9
|
autoload :UI, 'engineyard/cli/ui'
|
11
10
|
|
@@ -21,131 +20,45 @@ module EY
|
|
21
20
|
:desc => "Force a deploy of the specified branch"
|
22
21
|
method_option :migrate, :type => :string, :aliases => %w(-m),
|
23
22
|
:default => 'rake db:migrate',
|
24
|
-
:desc => "Run migrations via [MIGRATE], defaults to 'rake db:migrate'"
|
23
|
+
:desc => "Run migrations via [MIGRATE], defaults to 'rake db:migrate'; use --no-migrate to avoid running migrations"
|
25
24
|
method_option :install_eysd, :type => :boolean, :aliases => %(-s),
|
26
25
|
:desc => "Force remote install of eysd"
|
27
26
|
def deploy(env_name = nil, branch = nil)
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
env_name ||= EY.config.default_environment
|
32
|
-
raise DeployArgumentError if !env_name && app.environments.size != 1
|
33
|
-
|
34
|
-
default_branch = EY.config.default_branch(env_name)
|
35
|
-
branch ||= (default_branch || repo.current_branch)
|
36
|
-
raise DeployArgumentError unless branch
|
37
|
-
|
38
|
-
invalid_branch = default_branch && (branch != default_branch) && !options[:force]
|
39
|
-
raise BranchMismatch.new(default_branch, branch) if invalid_branch
|
40
|
-
|
41
|
-
if env_name && app.environments
|
42
|
-
env = app.environments.find{|e| e.name == env_name }
|
43
|
-
else
|
44
|
-
env = app.environments.first
|
45
|
-
end
|
46
|
-
|
47
|
-
if !env && account.environment_named(env_name)
|
48
|
-
raise EnvironmentError, "Environment '#{env_name}' doesn't run this application\nYou can add it at #{EY.config.endpoint}"
|
49
|
-
elsif !env
|
50
|
-
raise NoEnvironmentError
|
51
|
-
end
|
52
|
-
|
53
|
-
running = env.app_master && env.app_master.status == "running"
|
54
|
-
raise EnvironmentError, "No running instances for environment #{env.name}\nStart one at #{EY.config.endpoint}" unless running
|
55
|
-
|
56
|
-
hostname = env.app_master.public_hostname
|
57
|
-
username = env.username
|
58
|
-
|
59
|
-
EY.ui.info "Connecting to the server..."
|
60
|
-
ssh_to(hostname, "eysd check '#{EY::VERSION}' '#{EYSD_VERSION}'", username, false)
|
61
|
-
case $?.exitstatus
|
62
|
-
when 255
|
63
|
-
raise EnvironmentError, "SSH connection to #{hostname} failed"
|
64
|
-
when 127
|
65
|
-
EY.ui.warn "Server does not have ey-deploy gem installed"
|
66
|
-
eysd_installed = false
|
67
|
-
when 0
|
68
|
-
eysd_installed = true
|
69
|
-
else
|
70
|
-
raise EnvironmentError, "ey-deploy version not compatible"
|
71
|
-
end
|
72
|
-
|
73
|
-
if !eysd_installed || options[:install_eysd]
|
74
|
-
EY.ui.info "Installing ey-deploy gem..."
|
75
|
-
ssh_to(hostname,
|
76
|
-
"sudo gem install ey-deploy -v '#{EYSD_VERSION}'",
|
77
|
-
username)
|
78
|
-
end
|
79
|
-
|
80
|
-
deploy_cmd = "eysd deploy --app #{app.name} --branch #{branch}"
|
81
|
-
if env.config
|
82
|
-
escaped_config_option = env.config.to_json.gsub(/"/, "\\\"")
|
83
|
-
deploy_cmd << " --config '#{escaped_config_option}'"
|
84
|
-
end
|
85
|
-
|
86
|
-
if options['migrate']
|
87
|
-
deploy_cmd << " --migrate='#{options[:migrate]}'"
|
88
|
-
end
|
89
|
-
|
90
|
-
EY.ui.info "Running deploy on server..."
|
91
|
-
deployed = ssh_to(hostname, deploy_cmd, username)
|
92
|
-
|
93
|
-
if deployed
|
94
|
-
EY.ui.info "Deploy complete"
|
95
|
-
else
|
96
|
-
raise EY::Error, "Deploy failed"
|
97
|
-
end
|
27
|
+
require 'engineyard/action/deploy'
|
28
|
+
EY::Action::Deploy.call(env_name, branch, options)
|
98
29
|
end
|
99
30
|
|
100
31
|
|
101
32
|
desc "environments [--all]", "List cloud environments for this app, or all environments"
|
102
33
|
method_option :all, :type => :boolean, :aliases => %(-a)
|
103
34
|
def environments
|
104
|
-
|
105
|
-
|
106
|
-
EY.ui.say %|Cloud environments for #{app.name}:|
|
107
|
-
EY.ui.print_envs(envs, EY.config.default_environment)
|
108
|
-
elsif envs
|
109
|
-
EY.ui.say %|Cloud environments:|
|
110
|
-
EY.ui.print_envs(envs, EY.config.default_environment)
|
111
|
-
else
|
112
|
-
EY.ui.say %|You do not have any cloud environments.|
|
113
|
-
end
|
35
|
+
require 'engineyard/action/list_environments'
|
36
|
+
EY::Action::ListEnvironments.call(options[:all])
|
114
37
|
end
|
115
38
|
map "envs" => :environments
|
116
39
|
|
40
|
+
desc "rebuild [ENV]", "Rebuild environment (ensure configuration is up-to-date)"
|
41
|
+
def rebuild(name = nil)
|
42
|
+
require 'engineyard/action/rebuild'
|
43
|
+
EY::Action::Rebuild.call(name)
|
44
|
+
end
|
45
|
+
|
117
46
|
desc "ssh ENV", "Open an ssh session to the environment's application server"
|
118
47
|
def ssh(name)
|
119
|
-
|
120
|
-
|
121
|
-
Kernel.exec "ssh", "#{env.username}@#{env.app_master.public_hostname}", *ARGV[2..-1]
|
122
|
-
else
|
123
|
-
EY.ui.warn %|Could not find an environment named "#{name}"|
|
124
|
-
end
|
48
|
+
require 'engineyard/action/ssh'
|
49
|
+
EY::Action::SSH.call(name)
|
125
50
|
end
|
126
51
|
|
127
|
-
desc "logs
|
128
|
-
def logs(
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
raise EnvironmentError, "Environment '#{env_name}' can't be found\n" +
|
133
|
-
"You can create it at #{EY.config.endpoint}"
|
134
|
-
else
|
135
|
-
env.logs.each do |log|
|
136
|
-
EY.ui.info log.instance_name
|
137
|
-
|
138
|
-
if log.main
|
139
|
-
EY.ui.info "Main logs:"
|
140
|
-
EY.ui.say log.main
|
141
|
-
end
|
52
|
+
desc "logs ENV", "Retrieve the latest logs for an enviornment"
|
53
|
+
def logs(name)
|
54
|
+
require 'engineyard/action/show_logs'
|
55
|
+
EY::Action::ShowLogs.call(name)
|
56
|
+
end
|
142
57
|
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
end # logs_for_environment(env).each
|
148
|
-
end # env.nil?
|
58
|
+
desc "upload_recipes ENV", "Upload custom chef recipes from the current directory to ENV"
|
59
|
+
def upload_recipes(name)
|
60
|
+
require 'engineyard/action/upload_recipes'
|
61
|
+
EY::Action::UploadRecipes.call(name)
|
149
62
|
end
|
150
63
|
|
151
64
|
desc "version", "Print the version of the engineyard gem"
|
@@ -154,44 +67,5 @@ module EY
|
|
154
67
|
end
|
155
68
|
map "-v" => :version
|
156
69
|
|
157
|
-
private
|
158
|
-
|
159
|
-
def app_and_envs(all_envs = false)
|
160
|
-
app = account.app_for_repo(repo)
|
161
|
-
|
162
|
-
if all_envs || !app
|
163
|
-
envs = account.environments
|
164
|
-
EY.ui.warn(NoAppError.new(repo).message) unless app || all_envs
|
165
|
-
[nil, envs]
|
166
|
-
else
|
167
|
-
envs = app.environments
|
168
|
-
if envs.empty?
|
169
|
-
EY.ui.warn %|You have no environments set up for the application "#{app.name}"|
|
170
|
-
EY.ui.warn %|You can make one at #{EY.config.endpoint}|
|
171
|
-
end
|
172
|
-
envs.empty? ? [app, nil] : [app, envs]
|
173
|
-
end
|
174
|
-
end
|
175
|
-
|
176
|
-
def account
|
177
|
-
@account ||= EY::Account.new(API.new)
|
178
|
-
end
|
179
|
-
|
180
|
-
def repo
|
181
|
-
@repo ||= EY::Repo.new
|
182
|
-
end
|
183
|
-
|
184
|
-
def ssh_to(hostname, remote_cmd, user, output = true)
|
185
|
-
cmd = %{ssh -o StrictHostKeyChecking=no -q #{user}@#{hostname} "#{remote_cmd}"}
|
186
|
-
cmd << %{ &> /dev/null} unless output
|
187
|
-
EY.ui.debug(cmd)
|
188
|
-
puts cmd if output
|
189
|
-
unless ENV["NO_SSH"]
|
190
|
-
system cmd
|
191
|
-
else
|
192
|
-
true
|
193
|
-
end
|
194
|
-
end
|
195
|
-
|
196
70
|
end # CLI
|
197
71
|
end # EY
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# This particular pile of monkeypatch can be removed once we have a
|
2
|
+
# thor that doesn't consume an argument for --no-migrate.
|
3
|
+
#
|
4
|
+
# A fix has been written and a pull request sent.
|
5
|
+
# The fix is
|
6
|
+
# http://github.com/smerritt/thor/commit/421b2e97684e67ee393a13b3873e1a784bb83f68
|
7
|
+
|
8
|
+
class Thor
|
9
|
+
class Arguments
|
10
|
+
private
|
11
|
+
|
12
|
+
def no_or_skip?(arg)
|
13
|
+
arg =~ /^--(no|skip)-([-\w]+)$/
|
14
|
+
$2
|
15
|
+
end
|
16
|
+
|
17
|
+
def parse_string(name)
|
18
|
+
if no_or_skip?(name)
|
19
|
+
nil
|
20
|
+
else
|
21
|
+
shift
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module EY
|
2
|
+
class Error < RuntimeError; end
|
3
|
+
|
4
|
+
class NoAppError < Error
|
5
|
+
def initialize(repo)
|
6
|
+
@repo = repo
|
7
|
+
end
|
8
|
+
|
9
|
+
def message
|
10
|
+
error = [%|There is no application configured for any of the following remotes:|]
|
11
|
+
@repo.urls.each{|url| error << %|\t#{url}| }
|
12
|
+
error << %|You can add this application at #{EY.config.endpoint}|
|
13
|
+
error.join("\n")
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class EnvironmentError < EY::Error
|
18
|
+
end
|
19
|
+
|
20
|
+
class NoEnvironmentError < EY::Error
|
21
|
+
def initialize(env_name=nil)
|
22
|
+
@env_name = env_name
|
23
|
+
end
|
24
|
+
|
25
|
+
def message
|
26
|
+
"No environment named '#{@env_name}'\nYou can create one at #{EY.config.endpoint}"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class BranchMismatch < EY::Error
|
31
|
+
def initialize(default_branch, branch)
|
32
|
+
super(nil)
|
33
|
+
@default_branch, @branch = default_branch, branch
|
34
|
+
end
|
35
|
+
|
36
|
+
def message
|
37
|
+
%|Your deploy branch is set to "#{@default_branch}".\n| +
|
38
|
+
%|If you want to deploy branch "#{@branch}", use --force.|
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class DeployArgumentError < EY::Error
|
43
|
+
def message
|
44
|
+
%|"deploy" was called incorrectly. Call as "deploy [ENVIRONMENT] [BRANCH]"\n| +
|
45
|
+
%|You can set default environments and branches in ey.yml|
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/lib/engineyard/repo.rb
CHANGED
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe EY::Account::ApiStruct do
|
4
|
+
class Foo < EY::Account::ApiStruct.new(:fruit); end
|
5
|
+
class FooWithAccount < EY::Account::ApiStruct.new(:fruit, :account); end
|
6
|
+
|
7
|
+
it "acts like a normal struct" do
|
8
|
+
f = Foo.new("banana")
|
9
|
+
|
10
|
+
f.fruit.should == "banana"
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "from_hash initializer" do
|
14
|
+
it "assigns values from string keys" do
|
15
|
+
f = Foo.from_hash("fruit" => "banana")
|
16
|
+
f.should == Foo.new("banana")
|
17
|
+
end
|
18
|
+
|
19
|
+
it "assigns values from symbol keys" do
|
20
|
+
f = Foo.from_hash(:fruit => "banana")
|
21
|
+
f.should == Foo.new("banana")
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe "from_array initializer" do
|
26
|
+
it "provides a from_array initializer" do
|
27
|
+
f = Foo.from_array([:fruit => "banana"])
|
28
|
+
f.should == [Foo.new("banana")]
|
29
|
+
end
|
30
|
+
|
31
|
+
it "handles an account as the second argument" do
|
32
|
+
f = FooWithAccount.from_array([:fruit => "banana"], "account")
|
33
|
+
f.should == [FooWithAccount.new("banana", "account")]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|