engineyard 0.4.0 → 0.5.0

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