engineyard 0.2.11 → 0.2.12

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. data/lib/engineyard.rb +4 -3
  2. data/lib/engineyard/#repo.rb# +24 -0
  3. data/lib/engineyard/account.rb +32 -11
  4. data/lib/engineyard/account/api_struct.rb +25 -0
  5. data/lib/engineyard/account/app.rb +11 -10
  6. data/lib/engineyard/account/app_master.rb +1 -7
  7. data/lib/engineyard/account/environment.rb +31 -16
  8. data/lib/engineyard/account/instance.rb +6 -0
  9. data/lib/engineyard/account/log.rb +7 -16
  10. data/lib/engineyard/action/deploy.rb +138 -0
  11. data/lib/engineyard/action/list_environments.rb +22 -0
  12. data/lib/engineyard/action/rebuild.rb +31 -0
  13. data/lib/engineyard/action/show_logs.rb +26 -0
  14. data/lib/engineyard/action/ssh.rb +19 -0
  15. data/lib/engineyard/action/upload_recipes.rb +17 -0
  16. data/lib/engineyard/action/util.rb +47 -0
  17. data/lib/engineyard/cli.rb +24 -150
  18. data/lib/engineyard/cli/thor_fixes.rb +26 -0
  19. data/lib/engineyard/error.rb +48 -0
  20. data/lib/engineyard/repo.rb +1 -1
  21. data/lib/engineyard/ruby_ext.rb +9 -0
  22. data/spec/engineyard/account/api_struct_spec.rb +37 -0
  23. data/spec/engineyard/account/environment_spec.rb +20 -0
  24. data/spec/engineyard/account_spec.rb +18 -0
  25. data/spec/engineyard/cli_spec.rb +3 -3
  26. data/spec/ey/deploy_spec.rb +166 -28
  27. data/spec/ey/ey_spec.rb +3 -3
  28. data/spec/ey/list_environments_spec.rb +16 -0
  29. data/spec/ey/logs_spec.rb +2 -17
  30. data/spec/ey/rebuild_spec.rb +25 -0
  31. data/spec/ey/ssh_spec.rb +24 -0
  32. data/spec/ey/upload_recipes_spec.rb +21 -0
  33. data/spec/spec_helper.rb +32 -0
  34. data/spec/support/fake_awsm.ru +25 -1
  35. data/spec/support/helpers.rb +72 -7
  36. metadata +44 -56
  37. data/lib/engineyard/cli/error.rb +0 -44
  38. 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
@@ -1,11 +1,10 @@
1
1
  require 'thor'
2
2
  require 'engineyard'
3
- require 'engineyard/cli/error'
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
- app = account.app_for_repo(repo)
29
- raise NoAppError.new(repo) unless app
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
- app, envs = app_and_envs(options[:all])
105
- if app
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
- env = account.environment_named(name)
120
- if env
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 environment", "Retrieve the latest logs for an enviornment"
128
- def logs(environment)
129
- env = account.environment_named(environment)
130
-
131
- if env.nil?
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
- if log.custom
144
- EY.ui.info "Custom logs:"
145
- EY.ui.say log.custom
146
- end
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
@@ -21,4 +21,4 @@ module EY
21
21
  end
22
22
 
23
23
  end # Repo
24
- end # EY
24
+ end # EY
@@ -0,0 +1,9 @@
1
+ class Object
2
+ unless respond_to?(:tap)
3
+ # Ruby 1.9 has it, 1.8 doesn't
4
+ def tap
5
+ yield self
6
+ self
7
+ end
8
+ end
9
+ end
@@ -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