engineyard-serverside 2.2.1 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- NzFmNmZjYjI5NjVmN2Q2NDRhMjA1NGFkM2YzYTZiMmE0YzhkZmVkNA==
4
+ ZTMyNDZmY2M5YmMyN2RjN2I3OWM1MGRlNjlhN2NjY2FkODExYjVmMg==
5
5
  data.tar.gz: !binary |-
6
- NjYyZGRmMmI0NTgxNTVmYzQyM2FjODY4ZjAwOGQyM2M5NDRjYTVjYg==
6
+ M2ZmMWQ0Y2E5MDE5NTkzNjQ0ODFmYTJkZGJlYjlhMDc0NDMyMWQxYg==
7
7
  !binary "U0hBNTEy":
8
8
  metadata.gz: !binary |-
9
- ZWJjNGI2ZWIwOWVmNDJlZjQ0NjAwOGQ5YTBjMzdhMTQwM2QwMmZiOTA1Zjcx
10
- NjFjYTE5ZjYwMTBhNGEyOTAzODIzZTE2OGRjNjI4ZDczMDJiMTk3YjJiMTIx
11
- NTgxODAxN2RkNDE1MTI1ZjUwY2IyNWExYzcwZjBjNjIxOTNlN2M=
9
+ MzA1YWVmMDUxMjhjNjdkNzhhN2FhOWU3NTJmZjYzNTJmNjk0MDUxYmJiYTY4
10
+ ZWQ0YTg0OGEyYTg5M2Y5MmRkMmM1YzIwNzhhZmY5NjhiYTMwY2RkMWFhMWFk
11
+ MmIzNmY3ZjU1YjQxNzU4NTE4MDg4YzUzMGQxYjY5MDk2MDAyYTM=
12
12
  data.tar.gz: !binary |-
13
- NmFhYjBjNDM3NmJlYWNlOGMxZDFjMDU1OWQ4NDI2MzI1MjNmZmE5MmM2ZmQw
14
- YWJjZjg1ZTZmMDE5MzhjMmIzYmU1Zjc3NWUwNTI5MTE2NjZkMmUzNDE4NjFi
15
- YjQ2ZWRiYTJkMTlmZTkxNWNlY2FjY2M5ZmYwZjIxYTdhODkxOWY=
13
+ YzQyZTQzZGEyNGQ0MmU2MWZkOTJmNTdjNzM0MmJhOWYxZjllMGE1NWE3OWRi
14
+ ZTFiNDMxZDJlNTJhZjU2OTdiNjU4MzIxOTIyNDYwZjNjMzVjODc4NzQzNDBh
15
+ YmExMzE0NzA0N2QxMDNjMDE3Yjk1NDc1NzI3ZWI0MTNjZjJhNTU=
@@ -19,10 +19,11 @@ $LOAD_PATH.unshift File.expand_path('vendor/multi_json/lib', File.dirname(__FILE
19
19
 
20
20
  require 'escape'
21
21
  require 'multi_json'
22
+ require 'uri'
22
23
 
23
24
  require 'engineyard-serverside/version'
24
25
  require 'engineyard-serverside/about'
25
- require 'engineyard-serverside/strategies/git'
26
+
26
27
  require 'engineyard-serverside/task'
27
28
  require 'engineyard-serverside/server'
28
29
  require 'engineyard-serverside/deploy'
@@ -23,6 +23,17 @@ module EY
23
23
  method_option :repo, :type => :string,
24
24
  :desc => "Remote repo to deploy",
25
25
  :aliases => ["-r"]
26
+
27
+
28
+ # Archive source strategy
29
+ method_option :archive, :type => :string,
30
+ :desc => "Remote URI for archive to download and unzip"
31
+
32
+ # Git source strategy
33
+ method_option :git, :type => :string,
34
+ :desc => "Remote git repo to deploy"
35
+
36
+
26
37
  account_app_env_options
27
38
  config_option
28
39
  framework_env_option
@@ -3,6 +3,9 @@ require 'thor'
3
3
  require 'pp'
4
4
  require 'yaml'
5
5
  require 'engineyard-serverside/paths'
6
+ require 'engineyard-serverside/source'
7
+ require 'engineyard-serverside/source/git'
8
+ require 'engineyard-serverside/source/archive'
6
9
 
7
10
  module EY
8
11
  module Serverside
@@ -47,6 +50,19 @@ module EY
47
50
  end
48
51
  end
49
52
 
53
+ def fetch_deprecated(attr, replacement, default)
54
+ called = false
55
+ result = fetch(attr) { called = true; default }
56
+ if !called # deprecated attr was found
57
+ @deprecation_warning ||= {}
58
+ @deprecation_warning[attr] ||= begin
59
+ EY::Serverside.deprecation_warning "The configuration key '#{attr}' is deprecated in favor of '#{replacement}'."
60
+ true
61
+ end
62
+ end
63
+ result
64
+ end
65
+
50
66
  def_required_option :app
51
67
  def_required_option :environment_name
52
68
  def_required_option :account_name
@@ -55,14 +71,15 @@ module EY
55
71
  def_required_option :instance_roles
56
72
  def_required_option :instance_names
57
73
 
58
- def_option :repo, nil
74
+ def_option(:git) { fetch(:repo, nil) } # repo is deprecated
75
+ def_option :archive, nil
59
76
  def_option :migrate, nil
60
77
  def_option :precompile_assets, 'detect'
61
78
  def_option :precompile_assets_task, 'assets:precompile'
62
79
  def_option :asset_strategy, 'shifting'
63
80
  def_option :asset_dependencies, %w[app/assets lib/assets vendor/assets Gemfile.lock config/routes.rb config/application.rb]
64
81
  def_option :stack, nil
65
- def_option :strategy, 'Git'
82
+ def_option(:source_class) { fetch_deprecated(:strategy, :source_class, nil) } # strategy is deprecated
66
83
  def_option :branch, 'master'
67
84
  def_option :current_roles, []
68
85
  def_option :current_name, nil
@@ -85,6 +102,7 @@ module EY
85
102
  alias app_name app
86
103
  alias environment framework_env # legacy because it would be nice to have less confusion around "environment"
87
104
  alias migration_command migrate
105
+ alias repo git
88
106
 
89
107
  def initialize(options)
90
108
  opts = string_keys(options)
@@ -180,8 +198,33 @@ module EY
180
198
  EY::Serverside.node
181
199
  end
182
200
 
183
- def strategy_class
184
- EY::Serverside::Strategies.const_get(strategy)
201
+ # Infer the deploy source strategy to use based on flag or
202
+ # default to specified strategy.
203
+ #
204
+ # Returns a Source object.
205
+ def source(shell)
206
+ if archive && git
207
+ shell.fatal "Both --git and --archive specified. Precedence is not defined. Aborting"
208
+ raise "Both --git and --archive specified. Precedence is not defined. Aborting"
209
+ end
210
+ if archive
211
+ load_source(EY::Serverside::Source::Archive, shell, archive)
212
+ elsif source_class
213
+ load_source(EY::Serverside::Source.const_get(source_class), shell, git)
214
+ else # git can be nil for integrate or rollback
215
+ load_source(EY::Serverside::Source::Git, shell, git)
216
+ end
217
+ end
218
+
219
+ def load_source(klass, shell, uri)
220
+ klass.new(
221
+ shell,
222
+ :verbose => verbose,
223
+ :repository_cache => paths.repository_cache,
224
+ :app => app,
225
+ :uri => uri,
226
+ :ref => branch
227
+ )
185
228
  end
186
229
 
187
230
  def paths
@@ -58,19 +58,23 @@ module EY
58
58
  end
59
59
 
60
60
  def update_repository_cache
61
- strategy.update_repository_cache
61
+ source.update_repository_cache
62
62
  end
63
63
 
64
64
  def gc_repository_cache
65
- strategy.gc_repository_cache
65
+ source.gc_repository_cache
66
66
  end
67
67
 
68
68
  def create_revision_file_command
69
- strategy.create_revision_file_command(paths.active_release)
69
+ source.create_revision_file_command(paths.active_revision)
70
70
  end
71
71
 
72
72
  def short_log_message(revision)
73
- strategy.short_log_message(revision)
73
+ source.short_log_message(revision)
74
+ end
75
+
76
+ def unchanged_diff_between_revisions?(previous_revision, active_revision, asset_dependencies)
77
+ source.same?(previous_revision, active_revision, asset_dependencies)
74
78
  end
75
79
 
76
80
  def check_repository
@@ -186,12 +190,27 @@ chmod 0700 #{path}
186
190
  @cleanup_failed = false
187
191
  end
188
192
 
193
+ def abort_on_bad_paths_in_release_directory
194
+ shell.substatus "Checking for disruptive files in #{paths.releases}"
195
+
196
+ bad_paths = paths.all_releases.reject do |path|
197
+ path.basename.to_s =~ /^[\d]+$/
198
+ end
199
+
200
+ if bad_paths.any?
201
+ shell.fatal "Bad paths found in #{paths.releases}:\n\t#{bad_paths.join("\n\t")}\nStoring files in this directory will disrupt latest_release, diff detection, rollback, and possibly other features."
202
+ raise
203
+ end
204
+ end
205
+
206
+
189
207
  # task
190
208
  def rollback
191
209
  if config.rollback_paths!
192
210
  begin
193
211
  rolled_back_release = paths.latest_release
194
212
  shell.status "Rolling back to previous release: #{short_log_message(config.active_revision)}"
213
+ abort_on_bad_paths_in_release_directory
195
214
  run_with_callbacks(:symlink)
196
215
  sudo "rm -rf #{rolled_back_release}"
197
216
  bundle
@@ -362,25 +381,17 @@ YML
362
381
  end
363
382
  end
364
383
 
365
- protected
366
-
367
384
  # Use [] to access attributes instead of calling methods so
368
385
  # that we get nils instead of NoMethodError.
369
386
  #
370
387
  # Rollback doesn't know about the repository location (nor
371
388
  # should it need to), but it would like to use #short_log_message.
372
- def strategy
389
+ def source
373
390
  ensure_git_ssh_wrapper
374
- @strategy ||= config.strategy_class.new(
375
- shell,
376
- :verbose => config.verbose,
377
- :repository_cache => paths.repository_cache.to_s,
378
- :app => config.app,
379
- :repo => config[:repo],
380
- :ref => config[:branch]
381
- )
382
- end
383
- public :strategy
391
+ @source ||= config.source(shell)
392
+ end
393
+
394
+ protected
384
395
 
385
396
  def base_callback_command_for(what)
386
397
  cmd = [About.binary, 'hook', what.to_s]
@@ -1,4 +1,6 @@
1
1
  require 'engineyard-serverside/shell/helpers'
2
+ require 'engineyard-serverside/dependency_manager/bundler'
3
+ require 'engineyard-serverside/source/git'
2
4
 
3
5
  module EY
4
6
  module Serverside
@@ -12,14 +14,15 @@ module EY
12
14
  end
13
15
  end
14
16
 
17
+ DEPRECATED_CLASSES = {
18
+ :LoggedOutput => EY::Serverside::Shell::Helpers,
19
+ :LockfileParser => EY::Serverside::DependencyManager::Bundler::Lockfile,
20
+ :Strategies => EY::Serverside::Source::Git
21
+ }
15
22
  def self.const_missing(const)
16
- case const
17
- when :LoggedOutput
18
- EY::Serverside.deprecation_warning("EY::Serverside::LoggedOutput has been deprecated. Use EY::Serverside::Shell::Helpers instead.")
19
- EY::Serverside::Shell::Helpers
20
- when :LockfileParser
21
- EY::Serverside.deprecation_warning("EY::Serverside::LockfileParser has been deprecated. Use EY::Serverside::DependencyManager::Bundler::Lockfile instead.")
22
- EY::Serverside::DependencyManager::Bundler::Lockfile
23
+ if klass = DEPRECATED_CLASSES[const]
24
+ deprecation_warning("EY::Serverside::#{const} has been deprecated. Please use: #{klass}")
25
+ klass
23
26
  else
24
27
  super
25
28
  end
@@ -75,7 +75,7 @@ module EY
75
75
  asset_strategy.reusable? &&
76
76
  previous_revision &&
77
77
  active_revision &&
78
- runner.strategy.same?(previous_revision, active_revision, asset_dependencies)
78
+ runner.unchanged_diff_between_revisions?(previous_revision, active_revision, asset_dependencies)
79
79
  end
80
80
 
81
81
  def precompile_detected_assets
@@ -0,0 +1,73 @@
1
+ require 'pathname'
2
+ require 'engineyard-serverside/spawner'
3
+
4
+ class EY::Serverside::Source
5
+ attr_reader :uri, :opts, :source_cache, :ref, :shell
6
+ alias repository_cache source_cache
7
+
8
+ class << self
9
+ attr_reader :required_opts
10
+ def require_opts(*names)
11
+ @required_opts ||= []
12
+ @required_opts += names
13
+ end
14
+ end
15
+
16
+ def initialize(shell, opts={})
17
+ @shell = shell
18
+ @opts = opts
19
+
20
+ missing = self.class.required_opts && self.class.required_opts.reject {|name| @opts[name] }
21
+ if missing and missing.any?
22
+ raise ArgumentError,
23
+ "Internal error: Missing keys #{missing.join(',')}. Required: #{self.class.required_opts.join(', ')}"
24
+ end
25
+
26
+ @ref = @opts[:ref]
27
+ @uri = @opts[:uri].to_s if @opts[:uri]
28
+ @source_cache = Pathname.new(@opts[:repository_cache]) if @opts[:repository_cache]
29
+ end
30
+
31
+ protected
32
+
33
+ def in_source_cache(&block)
34
+ raise ArgumentError, "Block required" unless block
35
+ source_cache.mkpath
36
+ Dir.chdir(source_cache, &block)
37
+ end
38
+
39
+ def escape(*shell_commands)
40
+ Escape.shell_command(shell_commands)
41
+ end
42
+
43
+ def runner
44
+ EY::Serverside::Spawner
45
+ end
46
+
47
+ # Internal: Run a command.
48
+ #
49
+ # cmd - A string command.
50
+ #
51
+ # Returns an instance of Spawner.
52
+ def run(cmd)
53
+ runner.run(cmd, shell, nil)
54
+ end
55
+
56
+ # Internal: Run a command and return the output.
57
+ #
58
+ # cmd - A string command.
59
+ #
60
+ # Returns the output of the command.
61
+ def run_and_output(cmd)
62
+ run(cmd).output
63
+ end
64
+
65
+ # Internal: Run a command and check if it was successful.
66
+ #
67
+ # cmd - A string command.
68
+ #
69
+ # Returns success.
70
+ def run_and_success?(cmd)
71
+ run(cmd).success?
72
+ end
73
+ end
@@ -0,0 +1,58 @@
1
+ require 'engineyard-serverside/source'
2
+
3
+ # Deploy source for archive sourced deploy.
4
+ class EY::Serverside::Source::Archive < EY::Serverside::Source
5
+ require_opts :uri, :repository_cache
6
+
7
+ def create_revision_file_command(revision_file_path)
8
+ "echo #{escape(@checksum || filename)} > #{escape(revision_file_path.to_s)}"
9
+ end
10
+
11
+ def gc_repository_cache
12
+ # If files are uploaded to the server, we should clean them up here probably.
13
+ end
14
+
15
+ def same?(previous_rev, current_rev, paths=nil)
16
+ previous_rev == current_rev
17
+ end
18
+
19
+ def short_log_message(rev)
20
+ rev
21
+ end
22
+
23
+ def update_repository_cache
24
+ clean_cache
25
+ in_source_cache do
26
+ fetch && checksum && unarchive
27
+ end
28
+ end
29
+
30
+ protected
31
+
32
+ def checksum
33
+ @checksum = run_and_output("shasum #{escape(File.join(source_cache, filename))}").strip
34
+ end
35
+
36
+ def clean_cache
37
+ run "rm -rf #{source_cache} && mkdir -p #{source_cache}"
38
+ end
39
+
40
+ def fetch_command
41
+ "curl --location --silent --show-error -O --user-agent #{escape("EngineYardDeploy/#{EY::Serverside::VERSION}")} #{escape(uri)}"
42
+ end
43
+
44
+ def fetch
45
+ run_and_success?(fetch_command)
46
+ end
47
+
48
+ def filename
49
+ @filename ||= File.basename(URI.parse(uri).path)
50
+ end
51
+
52
+ def unarchive
53
+ case File.extname(filename)
54
+ when '.zip', '.war'
55
+ run "unzip #{filename} && rm #{filename}"
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,104 @@
1
+ require 'engineyard-serverside/source'
2
+
3
+ # Deploy source for git repository sourced deploy.
4
+ class EY::Serverside::Source::Git < EY::Serverside::Source
5
+ require_opts :uri, :ref, :repository_cache
6
+
7
+ def create_revision_file_command(revision_file_path)
8
+ %Q{#{git} show --pretty=format:"%H" | head -1 > "#{revision_file_path}"}
9
+ end
10
+
11
+ def gc_repository_cache
12
+ shell.status "Garbage collecting cached git repository to reduce disk usage."
13
+ run("#{git} gc")
14
+ end
15
+
16
+ # Check if there have been changes.
17
+ # git diff --exit-code returns
18
+ # - 0 when nothing has changed
19
+ # - 1 when there are changes
20
+ #
21
+ # previous_revision - The previous ref string.
22
+ # active_revision - The current ref string.
23
+ #
24
+ #
25
+ # Returns a boolean whether there has been a change.
26
+ def same?(previous_revision, active_revision, paths=nil)
27
+ run_and_success?("#{git} diff '#{previous_revision}'..'#{active_revision}' --exit-code --name-only -- #{Array(paths).join(' ')} >/dev/null 2>&1")
28
+ end
29
+
30
+ # Get most recent commit message for revision.
31
+ def short_log_message(revision)
32
+ run_and_output("#{git} log --pretty=oneline --abbrev-commit -n 1 '#{revision}'").strip
33
+ end
34
+
35
+ def update_repository_cache
36
+ unless fetch && checkout
37
+ shell.fatal "git checkout #{to_checkout} failed."
38
+ raise "git checkout #{to_checkout} failed."
39
+ end
40
+ end
41
+
42
+ protected
43
+
44
+ # Internal:
45
+ # Returns .
46
+ def checkout
47
+ shell.status "Deploying revision #{short_log_message(to_checkout)}"
48
+ q = opts[:verbose] ? '' : '-q'
49
+ in_source_cache do
50
+ (run_and_success?("git checkout -f #{q} '#{to_checkout}'") ||
51
+ run_and_success?("git reset --hard #{q} '#{to_checkout}'")) &&
52
+ run_and_success?("git submodule sync") &&
53
+ run_and_success?("git submodule update --init") &&
54
+ run_and_success?("git clean -dfq")
55
+ end
56
+ end
57
+
58
+ # Internal:
59
+ #
60
+ # Returns .
61
+ def clean_local_branch
62
+ run_and_success?("#{git} show-branch #{ref} > /dev/null 2>&1 && #{git} branch -D #{ref} > /dev/null 2>&1")
63
+ end
64
+
65
+ # Internal:
66
+ def fetch
67
+ run_and_success?(fetch_command)
68
+ end
69
+
70
+ def fetch_command
71
+ if usable_repository?
72
+ "#{git} fetch -q origin 2>&1"
73
+ else
74
+ "rm -rf #{repository_cache} && git clone -q #{uri} #{repository_cache} 2>&1"
75
+ end
76
+ end
77
+
78
+ def git
79
+ @git ||= "git --git-dir #{repository_cache}/.git --work-tree #{repository_cache}"
80
+ end
81
+
82
+ # Internal: Check for valid git repository.
83
+ #
84
+ # Returns a boolean.
85
+ def usable_repository?
86
+ repository_cache.directory? &&
87
+ run_and_output("#{git} remote -v | grep origin").include?(uri)
88
+ end
89
+
90
+ # Internal: .
91
+ #
92
+ # Returns .
93
+ def to_checkout
94
+ @to_checkout ||= begin
95
+ clean_local_branch
96
+ remote_branch? ? "origin/#{ref}" : ref
97
+ end
98
+ end
99
+
100
+ def remote_branch?
101
+ run_and_success?("#{git} show-branch origin/#{ref} > /dev/null 2>&1")
102
+ end
103
+
104
+ end
@@ -0,0 +1,77 @@
1
+ require 'pathname'
2
+ require 'engineyard-serverside/spawner'
3
+ require 'escape'
4
+
5
+ class EY::Serverside::Source
6
+ attr_reader :uri, :opts, :source_cache, :ref, :shell
7
+ alias repository_cache source_cache
8
+
9
+ class << self
10
+ attr_reader :required_opts
11
+ def require_opts(*names)
12
+ @required_opts ||= []
13
+ @required_opts += names
14
+ end
15
+
16
+ def for(type)
17
+ const_get(type)
18
+ end
19
+ end
20
+
21
+ def initialize(shell, opts={})
22
+ @shell = shell
23
+ @opts = opts
24
+
25
+ if self.class.required_opts && !self.class.required_opts.all? {|name| @opts[name] }
26
+ raise ArgumentError,
27
+ "Missing required key(s) (#{self.class.required_opts.join(', ')} required)"
28
+ end
29
+
30
+ @ref = @opts[:ref]
31
+ @uri = @opts[:uri].to_s if @opts[:uri]
32
+ @source_cache = Pathname.new(@opts[:repository_cache]) if @opts[:repository_cache]
33
+ end
34
+
35
+ protected
36
+
37
+ def in_source_cache(&block)
38
+ raise ArgumentError, "Block required" unless block
39
+ source_cache.mkpath
40
+ Dir.chdir(source_cache, &block)
41
+ end
42
+
43
+ def escape(*shell_commands)
44
+ Escape.shell_command(shell_commands)
45
+ end
46
+
47
+ def runner
48
+ EY::Serverside::Spawner
49
+ end
50
+
51
+ # Internal: Run a command.
52
+ #
53
+ # cmd - A string command.
54
+ #
55
+ # Returns an instance of Spawner.
56
+ def run(cmd)
57
+ runner.run(cmd, shell, nil)
58
+ end
59
+
60
+ # Internal: Run a command and return the output.
61
+ #
62
+ # cmd - A string command.
63
+ #
64
+ # Returns the output of the command.
65
+ def run_and_output(cmd)
66
+ run(cmd).output
67
+ end
68
+
69
+ # Internal: Run a command and check if it was successful.
70
+ #
71
+ # cmd - A string command.
72
+ #
73
+ # Returns success.
74
+ def run_and_success?(cmd)
75
+ run(cmd).success?
76
+ end
77
+ end
@@ -1,5 +1,5 @@
1
1
  module EY
2
2
  module Serverside
3
- VERSION = '2.2.1'
3
+ VERSION = '2.3.0'
4
4
  end
5
5
  end
@@ -0,0 +1,59 @@
1
+ require 'spec_helper'
2
+
3
+ class EY::Serverside::Source::Archive
4
+ def fetch_command
5
+ "cp #{uri} #{source_cache}"
6
+ end
7
+ end
8
+
9
+ describe "Deploying a simple application" do
10
+ let(:adapter) {
11
+ EY::Serverside::Adapter.new do |args|
12
+ args.account_name = "account"
13
+ args.app = "application_name"
14
+ args.stack = "nginx_unicorn"
15
+ args.environment_name = "environment_name"
16
+ args.framework_env = "production"
17
+ args.archive = FIXTURES_DIR.join('retwisj.war')
18
+ args.verbose = true
19
+ args.instances = [{ :hostname => "localhost", :roles => ["solo"], :name => "single" }]
20
+ args.config = {
21
+ "deploy_to" => deploy_dir,
22
+ "release_path" => release_path.to_s,
23
+ "group" => GROUP
24
+ }
25
+ end
26
+ }
27
+
28
+ let(:binpath) {
29
+ File.expand_path(File.join(File.dirname(__FILE__), '..', 'bin', 'engineyard-serverside'))
30
+ }
31
+
32
+ before(:all) do
33
+ argv = adapter.deploy.commands.last.to_argv[2..-1]
34
+ with_mocked_commands do
35
+ capture do
36
+ EY::Serverside::CLI.start(argv)
37
+ end
38
+ end
39
+ end
40
+
41
+ it "exploded the war" do
42
+ %w(META-INF WEB-INF).each {|dir|
43
+ File.exists?(deploy_dir.join('current', dir))
44
+ }
45
+ end
46
+
47
+ it "creates a REVISION file" do
48
+ path = deploy_dir.join('current', 'REVISION')
49
+ expect(path).to exist
50
+ checksum = File.read(path).strip
51
+ expect(checksum).to match(/7400dc058376745c11a98f768b799c6651428857\s+.*retwisj.war$/)
52
+ end
53
+
54
+ it "restarts the app servers" do
55
+ restart = deploy_dir.join('current', 'restart')
56
+ restart.should exist
57
+ expect(restart.read.chomp).to eq(%|LANG="en_US.UTF-8" /engineyard/bin/app_application_name deploy|)
58
+ end
59
+ end
@@ -15,7 +15,6 @@ describe EY::Serverside::Deploy::Configuration do
15
15
  @config.migrate.should == nil
16
16
  @config.migrate?.should == false
17
17
  @config.branch.should == "master"
18
- @config.strategy_class.should == EY::Serverside::Strategies::Git
19
18
  @config.maintenance_on_migrate.should == true
20
19
  @config.maintenance_on_restart.should == true
21
20
  @config.required_downtime_stack?.should == true
@@ -43,6 +42,43 @@ describe EY::Serverside::Deploy::Configuration do
43
42
  end
44
43
  end
45
44
 
45
+ context "strategies" do
46
+ let(:options) {
47
+ { "app" => "serverside" }
48
+ }
49
+ it "uses strategy if set" do
50
+ @config = EY::Serverside::Deploy::Configuration.new(
51
+ options.merge({'strategy' => 'IntegrationSpec', 'git' => 'git@github.com:engineyard/todo.git'})
52
+ )
53
+ capture do # deprecation warning
54
+ expect(@config.source(test_shell)).to be_a_kind_of(EY::Serverside::Source::IntegrationSpec)
55
+ end
56
+ read_output.should include("DEPRECATION WARNING: The configuration key 'strategy' is deprecated in favor of 'source_class'.")
57
+ end
58
+
59
+ it "uses source_class if set" do
60
+ @config = EY::Serverside::Deploy::Configuration.new(
61
+ options.merge({'source_class' => 'IntegrationSpec', 'git' => 'git@github.com:engineyard/todo.git'})
62
+ )
63
+ expect(@config.source(test_shell)).to be_a_kind_of(EY::Serverside::Source::IntegrationSpec)
64
+ end
65
+
66
+ it "infers a git source" do
67
+ @config = EY::Serverside::Deploy::Configuration.new(
68
+ options.merge({ 'git' => 'git@github.com:engineyard/todo.git' })
69
+ )
70
+ expect(@config.source(test_shell)).to be_a_kind_of(EY::Serverside::Source::Git)
71
+ end
72
+
73
+ it "infers a archive source" do
74
+ @config = EY::Serverside::Deploy::Configuration.new(
75
+ options.merge({'archive' => 'https://github.com/engineyard/todo/archive/master.zip'})
76
+ )
77
+
78
+ expect(@config.source(test_shell)).to be_a_kind_of(EY::Serverside::Source::Archive)
79
+ end
80
+ end
81
+
46
82
  context "command line options" do
47
83
  before do
48
84
  @config = EY::Serverside::Deploy::Configuration.new({
Binary file
@@ -1,29 +1,34 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe "the git deploy strategy" do
4
- subject do
5
- fixtures_dir = Pathname.new(__FILE__).dirname.join("fixtures")
6
- gitrepo_dir = tmpdir.join("gitrepo-#{Time.now.utc.strftime("%Y%m%d%H%M%S")}#{Time.now.tv_usec}-#{$$}")
7
- gitrepo_dir.mkdir
8
- system "tar xzf #{fixtures_dir.join('gitrepo.tar.gz')} --strip-components 1 -C #{gitrepo_dir}"
3
+ class EY::Serverside::Source::Git
4
+ def fetch_command
5
+ "mkdir -p #{source_cache} && tar xzf #{FIXTURES_DIR.join('gitrepo.tar.gz')} --strip-components 1 -C #{source_cache}"
6
+ end
7
+ end
9
8
 
10
- EY::Serverside::Strategies::Git.new(
11
- test_shell,
12
- :repo => FIXTURES_DIR.join('repos','default'),
13
- :repository_cache => gitrepo_dir,
14
- :ref => "master"
15
- )
9
+ describe EY::Serverside::Source::Git do
10
+ before do
11
+ @source_cache = tmpdir.join("gitrepo-#{Time.now.utc.strftime("%Y%m%d%H%M%S")}#{Time.now.tv_usec}-#{$$}")
16
12
  end
17
13
 
18
- before { subject.checkout }
19
14
 
20
- it "#checkout returns true for branches that exist" do
21
- subject.opts[:ref] = "somebranch"
22
- subject.checkout.should be_true
15
+ it "#update_repository_cache returns true for branches that exist" do
16
+ git = EY::Serverside::Source::Git.new(
17
+ test_shell,
18
+ :uri => FIXTURES_DIR.join('repos','default'),
19
+ :repository_cache => @source_cache,
20
+ :ref => "somebranch"
21
+ )
22
+ git.update_repository_cache
23
23
  end
24
24
 
25
- it "#checkout returns false for branches that do not exist" do
26
- subject.opts[:ref] = "notabranch"
27
- subject.checkout.should be_false
25
+ it "#update_repository_cache returns false for branches that do not exist" do
26
+ git = EY::Serverside::Source::Git.new(
27
+ test_shell,
28
+ :uri => FIXTURES_DIR.join('repos','default'),
29
+ :repository_cache => @source_cache,
30
+ :ref => "notabranch"
31
+ )
32
+ expect { git.update_repository_cache }.to raise_error
28
33
  end
29
34
  end
@@ -30,6 +30,38 @@ describe "Rolling back" do
30
30
  end
31
31
  end
32
32
 
33
+ context "with a problematic file in the releases dir" do
34
+ before(:all) do
35
+ deploy_test_application('not_bundled', 'migrate' => nil)
36
+ @good_revision = deploy_dir.join('current', 'REVISION').read.strip
37
+ deploy_dir.join('current', 'REVISION').should exist
38
+ deploy_dir.join('current', 'restart').delete
39
+ deploy_test_application('not_bundled', 'migrate' => nil)
40
+ deploy_dir.join('current', 'REVISION').should exist
41
+ deploy_dir.join('current', 'restart').delete
42
+ end
43
+
44
+ it "rolls back to the older deploy" do
45
+ releases = @deployer.config.paths.all_releases
46
+ releases.size.should == 2
47
+ good_release = releases.first
48
+ bad_release = releases.last
49
+
50
+ @deployer.config.paths.releases.join('tmp').mkpath
51
+
52
+ expect { @deployer.rollback }.to raise_error
53
+ out = read_output
54
+ expect(out).to include("Bad paths found in #{@deployer.config.paths.releases}:")
55
+ expect(out).to include(@deployer.config.paths.releases.join('tmp').to_s)
56
+ expect(out).to include("Storing files in this directory will disrupt latest_release, diff detection, rollback, and possibly other features.")
57
+ expect(out).to_not include("Restarting with previous release.")
58
+
59
+ deploy_dir.join('current', 'restart').should_not exist
60
+ bad_release.should exist
61
+ good_release.should exist
62
+ end
63
+ end
64
+
33
65
  context "with complex config" do
34
66
  before(:all) do
35
67
  deploy_test_application('ey_yml', 'migrate' => nil)
@@ -0,0 +1,34 @@
1
+ require 'spec_helper'
2
+
3
+ describe EY::Serverside::Source::Archive do
4
+ before do
5
+ described_class.any_instance.stub(:runner) { RunnerDouble }
6
+ end
7
+
8
+ context "source" do
9
+ let(:shell) { ShellDouble.new }
10
+ subject {
11
+ described_class.new(shell,
12
+ :uri => "http://server.com/app.war",
13
+ :repository_cache => TMPDIR)
14
+ }
15
+
16
+ it "cleans cache" do
17
+ expect(subject).to respond_to(:gc_repository_cache)
18
+ end
19
+
20
+ it "compares revisions" do
21
+ expect(subject.same?("1", "1")).to be
22
+ end
23
+
24
+ it "understands short log message" do
25
+ expect(subject).to respond_to(:short_log_message)
26
+ end
27
+
28
+ it "updates the cache" do
29
+ last_output = subject.update_repository_cache.output
30
+ expect(last_output).to eq("unzip app.war && rm app.war")
31
+ end
32
+
33
+ end
34
+ end
@@ -0,0 +1,44 @@
1
+ require 'spec_helper'
2
+
3
+ describe EY::Serverside::Source::Git do
4
+ before do
5
+ described_class.any_instance.stub(:runner) { RunnerDouble }
6
+ end
7
+
8
+ it "errors when required options are not used" do
9
+ expect { described_class.new(nil, {}) }.to raise_error(ArgumentError)
10
+ end
11
+
12
+ context "source" do
13
+ let(:shell) { ShellDouble.new }
14
+ subject {
15
+ described_class.new(shell,
16
+ :uri => "engineyard/engineyard-serverside.git",
17
+ :ref => "",
18
+ :repository_cache => "cache_dir")
19
+ }
20
+
21
+ it "creates the correct reivison file command" do
22
+ expect(subject.create_revision_file_command("directory/REVISION")).to eq(
23
+ "git --git-dir cache_dir/.git --work-tree cache_dir show --pretty=format:\"%H\" | head -1 > \"directory/REVISION\""
24
+ )
25
+ end
26
+
27
+ it "runs gc" do
28
+ expect(subject.gc_repository_cache.output).to eq("git --git-dir cache_dir/.git --work-tree cache_dir gc")
29
+ expect(shell.messages.last).to eq("Garbage collecting cached git repository to reduce disk usage.")
30
+ end
31
+
32
+ it "checks if it is the same revision" do
33
+ expect(subject.same?("", "")).to be
34
+ end
35
+
36
+ it "runs a short log message" do
37
+ expect(subject.short_log_message("rev")).to eq(
38
+ "git --git-dir cache_dir/.git --work-tree cache_dir log --pretty=oneline --abbrev-commit -n 1 'rev'"
39
+ )
40
+ end
41
+
42
+ end
43
+
44
+ end
@@ -15,7 +15,8 @@ end
15
15
  require 'pp'
16
16
  require 'engineyard-serverside'
17
17
  require 'engineyard-serverside-adapter'
18
- require File.expand_path('../support/integration', __FILE__)
18
+ require 'support/integration'
19
+ require 'support/source_doubles'
19
20
 
20
21
  FIXTURES_DIR = Pathname.new(__FILE__).dirname.join("fixtures")
21
22
  TMPDIR = Pathname.new(__FILE__).dirname.parent.join('tmp')
@@ -28,11 +29,6 @@ module EY
28
29
  @dna_json = j
29
30
  @node = nil
30
31
  end
31
-
32
- class Strategies::Git
33
- def short_log_message(_) "" end
34
- end
35
-
36
32
  end
37
33
  end
38
34
 
@@ -232,7 +228,7 @@ exec "$@"
232
228
  # spec/fixtures/repos dir are copied into the test github repository.
233
229
  def deploy_test_application(repo_fixture_name = 'default', extra_config = {}, &block)
234
230
  options = {
235
- "strategy" => "IntegrationSpec",
231
+ "source_class" => "IntegrationSpec",
236
232
  "deploy_to" => deploy_dir.to_s,
237
233
  "release_path" => release_path.to_s,
238
234
  "group" => GROUP,
@@ -244,7 +240,7 @@ exec "$@"
244
240
  "framework_env" => 'staging',
245
241
  "branch" => 'somebranch',
246
242
  "verbose" => true,
247
- "repo" => FIXTURES_DIR.join('repos', repo_fixture_name),
243
+ "git" => FIXTURES_DIR.join('repos', repo_fixture_name),
248
244
  }.merge(extra_config)
249
245
 
250
246
  # pretend there is a shared bundled_gems directory
@@ -260,11 +256,11 @@ exec "$@"
260
256
  args.account_name = options['account_name']
261
257
  args.migrate = options['migrate']
262
258
  args.ref = options['branch']
263
- args.repo = options['repo']
259
+ args.git = options['git']
264
260
  args.config = {
265
261
  "services_check_command" => "which echo",
266
262
  "services_setup_command" => "echo 'services setup command'",
267
- "strategy" => options["strategy"],
263
+ "source_class" => options["source_class"],
268
264
  "deploy_to" => options["deploy_to"],
269
265
  "release_path" => options["release_path"],
270
266
  "group" => options["group"]
@@ -51,18 +51,12 @@ class EY::Serverside::Deploy
51
51
  end
52
52
  end
53
53
 
54
- class EY::Serverside::Strategies::IntegrationSpec
55
- attr_reader :shell, :source_repo, :repository_cache
54
+ class EY::Serverside::Source::IntegrationSpec < EY::Serverside::Source
55
+ attr_reader :source_repo
56
56
 
57
- def initialize(shell, opts)
58
- unless opts[:repository_cache] && opts[:repo]
59
- raise ArgumentError, "Option :repository_cache and :repo are required"
60
- end
61
-
62
- @shell = shell
63
- @ref = opts[:ref]
64
- @source_repo = Pathname.new(opts[:repo])
65
- @repository_cache = Pathname.new(opts[:repository_cache])
57
+ def initialize(*a)
58
+ super
59
+ @source_repo = Pathname.new(uri)
66
60
  end
67
61
 
68
62
  def update_repository_cache
@@ -71,8 +65,8 @@ class EY::Serverside::Strategies::IntegrationSpec
71
65
  copy_fixture_repo_files
72
66
  end
73
67
 
74
- def create_revision_file_command(dir)
75
- "echo '#{@ref}' > #{dir}/REVISION"
68
+ def create_revision_file_command(revision_path)
69
+ "echo '#{@ref}' > #{revision_path}"
76
70
  end
77
71
 
78
72
  def short_log_message(revision)
@@ -101,7 +95,7 @@ class EY::Serverside::Strategies::IntegrationSpec
101
95
  shell.substatus "Test helpers copying repo fixture from #{source_repo}/ to #{repository_cache}"
102
96
  # This uses a ruby method instead of shelling out because I was having
103
97
  # trouble getting cp -R to behave consistently between distros.
104
- FileUtils.cp_r Dir.glob("#{source_repo}/*"), repository_cache
98
+ system "rsync -aq #{source_repo}/ #{repository_cache}"
105
99
  else
106
100
  raise "Mock repo #{source_repo.inspect} does not exist. Path should be absolute. e.g. FIXTURES_DIR.join('repos','example')"
107
101
  end
@@ -0,0 +1,28 @@
1
+ class ShellDouble
2
+ attr_reader :messages
3
+ def initialize
4
+ @messages = []
5
+ end
6
+
7
+ def status(message)
8
+ @messages << message
9
+ end
10
+ end
11
+
12
+ class RunnerDouble
13
+ def self.run(cmd, shell, server=nil)
14
+ new(cmd)
15
+ end
16
+
17
+ def initialize(cmd)
18
+ @cmd = cmd
19
+ end
20
+
21
+ def output
22
+ @cmd
23
+ end
24
+
25
+ def success?
26
+ true
27
+ end
28
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: engineyard-serverside
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.2.1
4
+ version: 2.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - EY Cloud Team
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-07-29 00:00:00.000000000 Z
11
+ date: 2013-08-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -100,14 +100,14 @@ dependencies:
100
100
  requirements:
101
101
  - - ~>
102
102
  - !ruby/object:Gem::Version
103
- version: 2.0.6
103
+ version: 2.1.0
104
104
  type: :development
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
108
  - - ~>
109
109
  - !ruby/object:Gem::Version
110
- version: 2.0.6
110
+ version: 2.1.0
111
111
  - !ruby/object:Gem::Dependency
112
112
  name: sqlite3
113
113
  requirement: !ruby/object:Gem::Requirement
@@ -154,8 +154,11 @@ files:
154
154
  - lib/engineyard-serverside/shell/helpers.rb
155
155
  - lib/engineyard-serverside/shell/yieldio.rb
156
156
  - lib/engineyard-serverside/shell.rb
157
+ - lib/engineyard-serverside/source/archive.rb
158
+ - lib/engineyard-serverside/source/git.rb
159
+ - lib/engineyard-serverside/source.rb
160
+ - lib/engineyard-serverside/source_strategy.rb
157
161
  - lib/engineyard-serverside/spawner.rb
158
- - lib/engineyard-serverside/strategies/git.rb
159
162
  - lib/engineyard-serverside/task.rb
160
163
  - lib/engineyard-serverside/version.rb
161
164
  - lib/engineyard-serverside.rb
@@ -220,6 +223,7 @@ files:
220
223
  - lib/vendor/thor/README.md
221
224
  - lib/vendor/thor/thor.gemspec
222
225
  - LICENSE
226
+ - spec/archive_deploy_spec.rb
223
227
  - spec/basic_deploy_spec.rb
224
228
  - spec/bundler_deploy_spec.rb
225
229
  - spec/configuration_spec.rb
@@ -355,6 +359,7 @@ files:
355
359
  - spec/fixtures/repos/sqlite3/Gemfile
356
360
  - spec/fixtures/repos/sqlite3/Gemfile.lock
357
361
  - spec/fixtures/repos/sqlite3/README
362
+ - spec/fixtures/retwisj.war
358
363
  - spec/fixtures/valid_hook.rb
359
364
  - spec/git_strategy_spec.rb
360
365
  - spec/lockfile_parser_spec.rb
@@ -367,9 +372,12 @@ files:
367
372
  - spec/server_spec.rb
368
373
  - spec/services_deploy_spec.rb
369
374
  - spec/shell_spec.rb
375
+ - spec/source/archive_spec.rb
376
+ - spec/source/git_spec.rb
370
377
  - spec/spec_helper.rb
371
378
  - spec/sqlite3_deploy_spec.rb
372
379
  - spec/support/integration.rb
380
+ - spec/support/source_doubles.rb
373
381
  homepage: http://github.com/engineyard/engineyard-serverside
374
382
  licenses:
375
383
  - MIT
@@ -395,6 +403,7 @@ signing_key:
395
403
  specification_version: 4
396
404
  summary: A gem that deploys ruby applications on EY Cloud instances
397
405
  test_files:
406
+ - spec/archive_deploy_spec.rb
398
407
  - spec/basic_deploy_spec.rb
399
408
  - spec/bundler_deploy_spec.rb
400
409
  - spec/configuration_spec.rb
@@ -530,6 +539,7 @@ test_files:
530
539
  - spec/fixtures/repos/sqlite3/Gemfile
531
540
  - spec/fixtures/repos/sqlite3/Gemfile.lock
532
541
  - spec/fixtures/repos/sqlite3/README
542
+ - spec/fixtures/retwisj.war
533
543
  - spec/fixtures/valid_hook.rb
534
544
  - spec/git_strategy_spec.rb
535
545
  - spec/lockfile_parser_spec.rb
@@ -542,6 +552,9 @@ test_files:
542
552
  - spec/server_spec.rb
543
553
  - spec/services_deploy_spec.rb
544
554
  - spec/shell_spec.rb
555
+ - spec/source/archive_spec.rb
556
+ - spec/source/git_spec.rb
545
557
  - spec/spec_helper.rb
546
558
  - spec/sqlite3_deploy_spec.rb
547
559
  - spec/support/integration.rb
560
+ - spec/support/source_doubles.rb
@@ -1,104 +0,0 @@
1
- require 'pathname'
2
-
3
- module EY
4
- module Serverside
5
- module Strategies
6
- class Git
7
- attr_reader :shell, :opts
8
-
9
- def initialize(shell, opts)
10
- @shell = shell
11
- @opts = opts
12
- end
13
-
14
- def update_repository_cache
15
- unless fetch && checkout
16
- abort "*** [Error] Git could not checkout (#{to_checkout}) ***"
17
- end
18
- end
19
-
20
- def usable_repository?
21
- repository_cache.directory? && `#{git} remote -v | grep origin`[remote_uri]
22
- end
23
-
24
- def fetch
25
- if usable_repository?
26
- run("#{git} fetch -q origin 2>&1")
27
- else
28
- run("rm -rf #{repository_cache} && git clone -q #{remote_uri} #{repository_cache} 2>&1")
29
- end
30
- end
31
-
32
- def checkout
33
- shell.status "Deploying revision #{short_log_message(to_checkout)}"
34
- q = opts[:verbose] ? '' : '-q'
35
- in_repository_cache do
36
- (run("git checkout -f #{q} '#{to_checkout}'") ||
37
- run("git reset --hard #{q} '#{to_checkout}'")) &&
38
- run("git submodule sync") &&
39
- run("git submodule update --init") &&
40
- run("git clean -dfq")
41
- end
42
- end
43
-
44
- def to_checkout
45
- return @to_checkout if @opts_ref == opts[:ref]
46
- @opts_ref = opts[:ref]
47
- clean_local_branch(@opts_ref)
48
- @to_checkout = remote_branch?(@opts_ref) ? "origin/#{@opts_ref}" : @opts_ref
49
- end
50
-
51
- def clean_local_branch(ref)
52
- system("#{git} show-branch #{ref} > /dev/null 2>&1 && #{git} branch -D #{ref} > /dev/null 2>&1")
53
- end
54
-
55
- def gc_repository_cache
56
- shell.status "Garbage collecting cached git repository to reduce disk usage."
57
- run("#{git} gc")
58
- end
59
-
60
- def create_revision_file_command(dir)
61
- %Q{#{git} show --pretty=format:"%H" | head -1 > "#{dir}/REVISION"}
62
- end
63
-
64
- def short_log_message(rev)
65
- `#{git} log --pretty=oneline --abbrev-commit -n 1 '#{rev}'`.strip
66
- end
67
-
68
- # git diff --exit-code returns
69
- # 0 when nothing has changed
70
- # 1 when there are changes
71
- #
72
- # Thes method returns true when nothing has changed, fales otherwise
73
- def same?(previous_revision, active_revision, paths = nil)
74
- run("#{git} diff '#{previous_revision}'..'#{active_revision}' --exit-code --name-only -- #{Array(paths).join(' ')} >/dev/null 2>&1")
75
- end
76
-
77
- private
78
- def run(cmd)
79
- EY::Serverside::Spawner.run(cmd, shell, nil).success?
80
- end
81
-
82
- def in_repository_cache
83
- Dir.chdir(repository_cache) { yield }
84
- end
85
-
86
- def remote_uri
87
- opts[:repo]
88
- end
89
-
90
- def repository_cache
91
- Pathname.new(opts[:repository_cache])
92
- end
93
-
94
- def git
95
- "git --git-dir #{repository_cache}/.git --work-tree #{repository_cache}"
96
- end
97
-
98
- def remote_branch?(ref)
99
- system("#{git} show-branch origin/#{ref} > /dev/null 2>&1")
100
- end
101
- end
102
- end
103
- end
104
- end