engineyard 0.2.11 → 0.2.12

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.
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