engineyard-serverside 2.2.1 → 2.3.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.
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