engineyard 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -3,7 +3,7 @@ require 'escape'
3
3
  module EY
4
4
  module Model
5
5
  class Instance < ApiStruct.new(:id, :role, :status, :amazon_id, :public_hostname, :environment)
6
- EYSD_VERSION = "~>0.4.0"
6
+ EYSD_VERSION = "~>0.4.1"
7
7
  CHECK_SCRIPT = <<-SCRIPT
8
8
  require "rubygems"
9
9
  requirement = Gem::Requirement.new("#{EYSD_VERSION}")
@@ -29,7 +29,8 @@ exit(17) # required_version < current_version
29
29
 
30
30
  alias :hostname :public_hostname
31
31
 
32
- def deploy!(app, ref, migration_command=nil, extra_configuration=nil)
32
+
33
+ def deploy(app, ref, migration_command=nil, extra_configuration=nil)
33
34
  deploy_cmd = [eysd_path, 'deploy', '--app', app.name, '--branch', ref]
34
35
 
35
36
  if extra_configuration
@@ -43,18 +44,42 @@ exit(17) # required_version < current_version
43
44
  ssh Escape.shell_command(deploy_cmd)
44
45
  end
45
46
 
46
- def ensure_eysd_present!
47
+ def rollback(app, extra_configuration=nil)
48
+ deploy_cmd = [eysd_path, 'deploy', 'rollback', '--app', app.name]
49
+
50
+ if extra_configuration
51
+ deploy_cmd << '--config' << extra_configuration.to_json
52
+ end
53
+
54
+ ssh Escape.shell_command(deploy_cmd)
55
+ end
56
+
57
+
58
+ def put_up_maintenance_page(app)
59
+ ssh Escape.shell_command([
60
+ eysd_path, 'deploy', 'enable_maintenance_page', '--app', app.name
61
+ ])
62
+ end
63
+
64
+ def take_down_maintenance_page(app)
65
+ ssh Escape.shell_command([
66
+ eysd_path, 'deploy', 'disable_maintenance_page', '--app', app.name
67
+ ])
68
+ end
69
+
70
+
71
+ def ensure_eysd_present
47
72
  case ey_deploy_check
48
73
  when :ssh_failed
49
74
  raise EnvironmentError, "SSH connection to #{hostname} failed"
50
75
  when :eysd_missing
51
76
  yield :installing if block_given?
52
- install_ey_deploy!
77
+ install_ey_deploy
53
78
  when :too_new
54
79
  raise EnvironmentError, "server-side component too new; please upgrade your copy of the engineyard gem."
55
80
  when :too_old
56
81
  yield :upgrading if block_given?
57
- upgrade_ey_deploy!
82
+ upgrade_ey_deploy
58
83
  when :ok
59
84
  # no action needed
60
85
  else
@@ -66,26 +91,20 @@ exit(17) # required_version < current_version
66
91
  require 'base64'
67
92
  encoded_script = Base64.encode64(CHECK_SCRIPT).gsub(/\n/, '')
68
93
  ssh "#{ruby_path} -r base64 -e \"eval Base64.decode64(ARGV[0])\" #{encoded_script}", false
69
- EXIT_STATUS[$?.exitstatus]
70
- end
71
-
72
- def install_ey_deploy!
73
- ssh(Escape.shell_command(['sudo', gem_path, 'install', 'ey-deploy', '-v', EYSD_VERSION]))
74
- end
75
-
76
- def rollback!(app, extra_configuration=nil)
77
- deploy_cmd = [eysd_path, 'deploy', 'rollback', '--app', app.name]
78
-
79
- if extra_configuration
80
- deploy_cmd << '--config' << extra_configuration.to_json
94
+ if ENV["NO_SSH"]
95
+ :ok
96
+ else
97
+ EXIT_STATUS[$?.exitstatus]
81
98
  end
99
+ end
82
100
 
83
- ssh Escape.shell_command(deploy_cmd)
101
+ def install_ey_deploy
102
+ ssh(Escape.shell_command(['sudo', gem_path, 'install', 'ey-deploy', '--no-rdoc', '--no-ri', '-v', EYSD_VERSION]))
84
103
  end
85
104
 
86
- def upgrade_ey_deploy!
105
+ def upgrade_ey_deploy
87
106
  ssh "sudo #{gem_path} uninstall -a -x ey-deploy"
88
- install_ey_deploy!
107
+ install_ey_deploy
89
108
  end
90
109
 
91
110
  private
@@ -6,9 +6,13 @@ module EY
6
6
  end
7
7
 
8
8
  def current_branch
9
- head = File.read(File.join(@path, ".git/HEAD")).chomp
10
- if head.gsub!("ref: refs/heads/", "")
11
- head
9
+ if File.directory?(File.join(@path, ".git"))
10
+ head = File.read(File.join(@path, ".git/HEAD")).chomp
11
+ if head.gsub!("ref: refs/heads/", "")
12
+ head
13
+ else
14
+ nil
15
+ end
12
16
  else
13
17
  nil
14
18
  end
@@ -9,17 +9,56 @@ module EY
9
9
  end
10
10
 
11
11
  no_tasks do
12
- def subcommand_args
13
- @@original_args[1..-1]
12
+ def self.subcommands
13
+ @@subcommands ||= {}
14
14
  end
15
15
 
16
16
  def self.subcommand(subcommand, subcommand_class)
17
+ subcommand = subcommand.to_s
18
+ subcommands[subcommand] = subcommand_class
19
+ subcommand_class.subcommand_help subcommand
17
20
  define_method(subcommand) { |*_| subcommand_class.start(subcommand_args) }
18
21
  end
22
+
23
+ def self.subcommand_help(cmd)
24
+ desc "help #{cmd} [SUBCOMMAND]", "Describe all subcommands or one specific subcommand."
25
+
26
+ class_eval <<-RUBY
27
+ def help(*args)
28
+ super
29
+ if args.empty?
30
+ banner = "See '" + self.class.send(:banner_base) + " help #{cmd} [SUBCOMMAND]' "
31
+ text = "for more information on a specific subcommand."
32
+ EY.ui.say banner + text
33
+ end
34
+ end
35
+ RUBY
36
+ end
37
+
38
+ def subcommand_args
39
+ @@original_args[1..-1]
40
+ end
41
+
42
+ def self.printable_tasks(all=true)
43
+ (all ? all_tasks : tasks).map do |_, task|
44
+ item = []
45
+ item << banner(task)
46
+ item << (task.description ? "# #{task.description.gsub(/\n.*/,'')}" : "")
47
+ item
48
+ end
49
+ end
19
50
  end
20
51
 
21
52
  protected
22
53
 
54
+ def self.handle_no_task_error(task)
55
+ if self.banner_base == "thor"
56
+ raise UndefinedTaskError, "Could not find command #{task.inspect} in #{namespace.inspect} namespace."
57
+ else
58
+ raise UndefinedTaskError, "Could not find command #{task.inspect}."
59
+ end
60
+ end
61
+
23
62
  def self.exit_on_failure?
24
63
  true
25
64
  end
@@ -32,6 +71,20 @@ module EY
32
71
  @repo ||= EY::Repo.new
33
72
  end
34
73
 
74
+ def loudly_check_eysd(environment)
75
+ environment.ensure_eysd_present do |action|
76
+ case action
77
+ when :installing
78
+ EY.ui.warn "Instance does not have server-side component installed"
79
+ EY.ui.info "Installing server-side component..."
80
+ when :upgrading
81
+ EY.ui.info "Upgrading server-side component..."
82
+ else
83
+ # nothing slow is happening, so there's nothing to say
84
+ end
85
+ end
86
+ end
87
+
35
88
  # if an app is supplied, it is used as a constraint for implied environment lookup
36
89
  def fetch_environment(env_name, app = nil)
37
90
  env_name ||= EY.config.default_environment
@@ -8,7 +8,7 @@ describe EY::API do
8
8
 
9
9
  context "fetching the token from EY cloud" do
10
10
  before(:each) do
11
- FakeWeb.register_uri(:post, "https://cloud.engineyard.com/api/v2/authenticate", :body => %|{"api_token": "asdf"}|)
11
+ FakeWeb.register_uri(:post, "https://cloud.engineyard.com/api/v2/authenticate", :body => %|{"api_token": "asdf"}|, :content_type => 'application/json')
12
12
  @token = EY::API.fetch_token("a@b.com", "foo")
13
13
  end
14
14
 
@@ -46,7 +46,7 @@ describe EY::API do
46
46
  end
47
47
 
48
48
  it "raises InvalidCredentials when the credentials are invalid" do
49
- FakeWeb.register_uri(:post, "https://cloud.engineyard.com/api/v2/authenticate", :status => 401)
49
+ FakeWeb.register_uri(:post, "https://cloud.engineyard.com/api/v2/authenticate", :status => 401, :content_type => 'application/json')
50
50
 
51
51
  lambda {
52
52
  EY::API.fetch_token("a@b.com", "foo")
@@ -20,7 +20,7 @@ describe EY::CLI::API do
20
20
 
21
21
  context "without saved api token" do
22
22
  before(:each) do
23
- FakeWeb.register_uri(:post, "https://cloud.engineyard.com/api/v2/authenticate", :body => %|{"api_token": "asdf"}|)
23
+ FakeWeb.register_uri(:post, "https://cloud.engineyard.com/api/v2/authenticate", :body => %|{"api_token": "asdf"}|, :content_type => 'application/json')
24
24
 
25
25
  capture_stdio("\n\n") do
26
26
  @token = EY::CLI::API.new
@@ -1,7 +1,7 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe "EY::Model::Environment#rebuild" do
4
- it_should_behave_like "it has an api"
4
+ given "it has an api"
5
5
 
6
6
  it "hits the rebuild action in the API" do
7
7
  env = EY::Model::Environment.from_hash({
@@ -22,7 +22,7 @@ describe "EY::Model::Environment#rebuild" do
22
22
  end
23
23
 
24
24
  describe "EY::Model::Environment#run_custom_recipes" do
25
- it_should_behave_like "it has an api"
25
+ given "it has an api"
26
26
 
27
27
  it "hits the rebuild action in the API" do
28
28
  env = EY::Model::Environment.from_hash({
@@ -33,7 +33,8 @@ describe "EY::Model::Environment#run_custom_recipes" do
33
33
  FakeWeb.register_uri(
34
34
  :put,
35
35
  "https://cloud.engineyard.com/api/v2/environments/#{env.id}/run_custom_recipes",
36
- :body => ''
36
+ :body => '',
37
+ :content_type => 'application/json'
37
38
  )
38
39
 
39
40
  env.run_custom_recipes
@@ -56,7 +57,7 @@ describe "EY::Model::Environment.from_array" do
56
57
  end
57
58
 
58
59
  describe "EY::Model::Environment#instances" do
59
- it_should_behave_like "it has an api"
60
+ given "it has an api"
60
61
 
61
62
  it "returns instances" do
62
63
  env = EY::Model::Environment.from_hash({
@@ -72,8 +73,9 @@ describe "EY::Model::Environment#instances" do
72
73
  }
73
74
  FakeWeb.register_uri(:get,
74
75
  "https://cloud.engineyard.com/api/v2/environments/#{env.id}/instances",
75
- :body => {"instances" => [instance_data]}.to_json)
76
-
76
+ :body => {"instances" => [instance_data]}.to_json,
77
+ :content_type => 'application/json'
78
+ )
77
79
 
78
80
  env.should have(1).instances
79
81
  env.instances.first.should == EY::Model::Instance.from_hash(instance_data.merge(:environment => env))
@@ -2,7 +2,7 @@ require 'spec_helper'
2
2
 
3
3
  describe "ey deploy without an eyrc file" do
4
4
 
5
- it_should_behave_like "an integration test without an eyrc file"
5
+ given "integration without an eyrc file"
6
6
 
7
7
  before(:each) do
8
8
  FileUtils.rm_rf(ENV['EYRC'])
@@ -22,8 +22,27 @@ describe "ey deploy without an eyrc file" do
22
22
  end
23
23
  end
24
24
 
25
+
25
26
  describe "ey deploy" do
26
- it_should_behave_like "an integration test"
27
+ given "integration"
28
+
29
+ def command_to_run(options)
30
+ cmd = "deploy"
31
+ cmd << " -e #{options[:env]}" if options[:env]
32
+ cmd
33
+ end
34
+
35
+ def verify_ran(scenario)
36
+ @out.should match(/Running deploy for '#{scenario[:environment]}'/)
37
+ end
38
+
39
+ # common behavior
40
+ it_should_behave_like "it takes an environment name"
41
+ it_should_behave_like "it invokes eysd"
42
+ end
43
+
44
+ describe "ey deploy" do
45
+ given "integration"
27
46
 
28
47
  context "with invalid input" do
29
48
  it "complains when there is no app" do
@@ -32,15 +51,9 @@ describe "ey deploy" do
32
51
  @err.should include(%|no application configured|)
33
52
  end
34
53
 
35
- it "complains when you specify a nonexistent environment" do
36
- api_scenario "one app, one environment"
37
- ey "deploy typo-happens-here master", :expect_failure => true
38
- @err.should match(/no environment named 'typo-happens-here'/i)
39
- end
40
-
41
54
  it "complains when the specified environment does not contain the app" do
42
55
  api_scenario "one app, one environment, not linked"
43
- ey "deploy giblets master", :expect_failure => true
56
+ ey "deploy -e giblets -r master", :expect_failure => true
44
57
  @err.should match(/does not run this application/i)
45
58
  end
46
59
 
@@ -52,19 +65,12 @@ describe "ey deploy" do
52
65
 
53
66
  it "complains when the app master is in a non-running state" do
54
67
  api_scenario "one app, one environment, app master red"
55
- ey "deploy giblets master", :expect_failure => true
68
+ ey "deploy --environment giblets --ref master", :expect_failure => true
56
69
  @err.should_not match(/No running instances/i)
57
70
  @err.should match(/running.*\(green\)/)
58
71
  end
59
72
  end
60
73
 
61
- it "runs when environment is known" do
62
- api_scenario "one app, one environment"
63
- ey "deploy", :hide_err => true
64
- @out.should match(/running deploy/i)
65
- @err.should be_empty
66
- end
67
-
68
74
  context "migration command" do
69
75
  before(:each) do
70
76
  api_scenario "one app, one environment"
@@ -86,11 +92,6 @@ describe "ey deploy" do
86
92
  @ssh_commands.last.should =~ /eysd deploy/
87
93
  @ssh_commands.last.should_not =~ /--migrate/
88
94
  end
89
-
90
- it "can be disabled with --no-migrate in the middle of the command line" do
91
- ey "deploy --no-migrate giblets master"
92
- @ssh_commands.last.should_not =~ /--migrate/
93
- end
94
95
  end
95
96
 
96
97
  context "choosing something to deploy" do
@@ -120,14 +121,24 @@ describe "ey deploy" do
120
121
  end
121
122
 
122
123
  it "deploys another branch if given" do
123
- ey "deploy giblets master"
124
+ ey "deploy --ref master"
124
125
  @ssh_commands.last.should =~ /--branch master/
125
126
  end
126
127
 
127
128
  it "deploys a tag if given" do
128
- ey "deploy giblets v1"
129
+ ey "deploy --ref v1"
129
130
  @ssh_commands.last.should =~ /--branch v1/
130
131
  end
132
+
133
+ it "allows using --branch to specify a branch" do
134
+ ey "deploy --branch master"
135
+ @ssh_commands.last.should match(/--branch master/)
136
+ end
137
+
138
+ it "allows using --tag to specify the tag" do
139
+ ey "deploy --tag v1"
140
+ @ssh_commands.last.should match(/--branch v1/)
141
+ end
131
142
  end
132
143
 
133
144
  context "when there is extra configuration" do
@@ -160,12 +171,12 @@ describe "ey deploy" do
160
171
  end
161
172
 
162
173
  it "complains about a non-default branch without --force" do
163
- ey "deploy giblets current-branch", :expect_failure => true
174
+ ey "deploy -r current-branch", :expect_failure => true
164
175
  @err.should =~ /deploy branch is set to "master"/
165
176
  end
166
177
 
167
178
  it "deploys a non-default branch with --force" do
168
- ey "deploy giblets current-branch --force"
179
+ ey "deploy -r current-branch --force"
169
180
  @ssh_commands.last.should =~ /--branch current-branch/
170
181
  end
171
182
  end
@@ -176,72 +187,26 @@ describe "ey deploy" do
176
187
  api_scenario "one app, many similarly-named environments"
177
188
  end
178
189
 
179
- it "lets you choose by unambiguous substring" do
180
- ey "deploy prod"
181
- @out.should match(/Running deploy for 'railsapp_production'/)
182
- end
183
-
184
190
  it "lets you choose by complete name even if the complete name is ambiguous" do
185
- ey "deploy railsapp_staging"
191
+ ey "deploy --environment railsapp_staging"
186
192
  @out.should match(/Running deploy for 'railsapp_staging'/)
187
193
  end
188
-
189
- it "complains when given an ambiguous substring" do
190
- # NB: there's railsapp_staging and railsapp_staging_2
191
- ey "deploy staging", :hide_err => true, :expect_failure => true
192
- @err.should match(/'staging' is ambiguous/)
193
- end
194
194
  end
195
195
 
196
- context "eysd install" do
196
+ context "specifying the application" do
197
197
  before(:all) do
198
198
  api_scenario "one app, one environment"
199
+ Dir.chdir(File.expand_path("~"))
199
200
  end
200
201
 
201
- before(:each) do
202
- ENV.delete "NO_SSH"
203
- end
204
-
205
- after(:each) do
206
- ENV['NO_SSH'] = "true"
207
- end
208
-
209
- def exiting_ssh(exit_code)
210
- "#!/usr/bin/env ruby\n exit!(#{exit_code}) if ARGV.to_s =~ /Base64.decode64/"
211
- end
212
-
213
- it "raises an error if SSH fails" do
214
- ey "deploy", :prepend_to_path => {'ssh' => exiting_ssh(255)}, :expect_failure => true
215
- @err.should =~ /SSH connection to \S+ failed/
216
- end
217
-
218
- it "installs ey-deploy if it's missing" do
219
- ey "deploy", :prepend_to_path => {'ssh' => exiting_ssh(104)}
220
-
221
- gem_install_command = @ssh_commands.find do |command|
222
- command =~ /gem install ey-deploy/
223
- end
224
- gem_install_command.should =~ %r{/usr/local/ey_resin/ruby/bin/gem install}
225
- end
226
-
227
- it "upgrades ey-deploy if it's too old" do
228
- ey "deploy", :prepend_to_path => {'ssh' => exiting_ssh(70)}
229
- @ssh_commands.should have_command_like(/gem uninstall -a -x ey-deploy/)
230
- @ssh_commands.should have_command_like(/gem install ey-deploy/)
231
- end
232
-
233
- it "raises an error if ey-deploy is too new" do
234
- ey "deploy", :prepend_to_path => {'ssh' => exiting_ssh(17)}, :expect_failure => true
235
- @ssh_commands.should_not have_command_like(/gem install ey-deploy/)
236
- @ssh_commands.should_not have_command_like(/eysd deploy/)
237
- @err.should match(/too new/i)
202
+ it "allows you to specify an app when not in a directory" do
203
+ ey "deploy --app rails232app --ref master"
204
+ @ssh_commands.last.should match(/deploy --app rails232app --branch master --migrate 'rake db:migrate'/)
238
205
  end
239
206
 
240
- it "does not change ey-deploy if its version is satisfactory" do
241
- ey "deploy", :prepend_to_path => {'ssh' => exiting_ssh(0)}
242
- @ssh_commands.should_not have_command_like(/gem install ey-deploy/)
243
- @ssh_commands.should_not have_command_like(/gem uninstall.* ey-deploy/)
244
- @ssh_commands.should have_command_like(/eysd deploy/)
207
+ it "requires that you specify a ref when specifying the application" do
208
+ ey "deploy --app rails232app", :expect_failure => true
209
+ @err.should match(/you must also specify the ref to deploy/)
245
210
  end
246
211
  end
247
212
  end