capistrano 3.0.1 → 3.1.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.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.travis.yml +5 -2
  4. data/CHANGELOG.md +36 -3
  5. data/CONTRIBUTING.md +91 -0
  6. data/Gemfile +9 -0
  7. data/README.md +9 -11
  8. data/capistrano.gemspec +5 -8
  9. data/features/configuration.feature +15 -0
  10. data/features/deploy.feature +1 -0
  11. data/features/deploy_failure.feature +17 -0
  12. data/features/step_definitions/assertions.rb +19 -0
  13. data/features/step_definitions/cap_commands.rb +5 -1
  14. data/features/step_definitions/setup.rb +13 -0
  15. data/features/support/remote_command_helpers.rb +4 -0
  16. data/lib/capistrano/application.rb +41 -3
  17. data/lib/capistrano/configuration.rb +8 -0
  18. data/lib/capistrano/configuration/server.rb +28 -5
  19. data/lib/capistrano/configuration/servers.rb +3 -6
  20. data/lib/capistrano/configuration/servers/host_filter.rb +82 -0
  21. data/lib/capistrano/dsl.rb +16 -1
  22. data/lib/capistrano/dsl/env.rb +11 -1
  23. data/lib/capistrano/dsl/paths.rb +8 -0
  24. data/lib/capistrano/dsl/stages.rb +8 -1
  25. data/lib/capistrano/dsl/task_enhancements.rb +13 -2
  26. data/lib/capistrano/git.rb +35 -0
  27. data/lib/capistrano/hg.rb +32 -0
  28. data/lib/capistrano/i18n.rb +6 -2
  29. data/lib/capistrano/scm.rb +116 -0
  30. data/lib/capistrano/setup.rb +4 -3
  31. data/lib/capistrano/tasks/console.rake +9 -1
  32. data/lib/capistrano/tasks/deploy.rake +17 -15
  33. data/lib/capistrano/tasks/framework.rake +1 -0
  34. data/lib/capistrano/tasks/git.rake +16 -10
  35. data/lib/capistrano/tasks/hg.rake +13 -9
  36. data/lib/capistrano/templates/Capfile +1 -2
  37. data/lib/capistrano/templates/deploy.rb.erb +20 -2
  38. data/lib/capistrano/templates/stage.rb.erb +1 -4
  39. data/lib/capistrano/version.rb +1 -1
  40. data/spec/integration/dsl_spec.rb +147 -2
  41. data/spec/lib/capistrano/application_spec.rb +2 -5
  42. data/spec/lib/capistrano/configuration/server_spec.rb +40 -4
  43. data/spec/lib/capistrano/configuration/servers/host_filter_spec.rb +84 -0
  44. data/spec/lib/capistrano/configuration/servers_spec.rb +35 -0
  45. data/spec/lib/capistrano/configuration_spec.rb +8 -0
  46. data/spec/lib/capistrano/dsl_spec.rb +0 -11
  47. data/spec/lib/capistrano/git_spec.rb +70 -0
  48. data/spec/lib/capistrano/hg_spec.rb +70 -0
  49. data/spec/lib/capistrano/scm_spec.rb +104 -0
  50. data/spec/support/tasks/fail.cap +7 -0
  51. data/spec/support/tasks/failed.cap +5 -0
  52. data/spec/support/test_app.rb +33 -3
  53. metadata +29 -52
  54. data/spec/lib/capistrano/dsl/env_spec.rb +0 -10
@@ -9,6 +9,10 @@ module Capistrano
9
9
  def env
10
10
  @env ||= new
11
11
  end
12
+
13
+ def reset!
14
+ @env = new
15
+ end
12
16
  end
13
17
 
14
18
  def ask(key, default=nil)
@@ -34,6 +38,10 @@ module Capistrano
34
38
  end
35
39
 
36
40
  def role(name, hosts, options={})
41
+ if name == :all
42
+ raise ArgumentError.new("#{name} reserved name for role. Please choose another name")
43
+ end
44
+
37
45
  servers.add_role(name, hosts, options)
38
46
  end
39
47
 
@@ -5,6 +5,10 @@ module Capistrano
5
5
  extend Forwardable
6
6
  def_delegators :properties, :roles, :fetch, :set
7
7
 
8
+ def self.[](host)
9
+ host.is_a?(Server) ? host : new(host)
10
+ end
11
+
8
12
  def add_roles(roles)
9
13
  Array(roles).each { |role| add_role(role) }
10
14
  end
@@ -18,12 +22,8 @@ module Capistrano
18
22
  roles.include? role.to_sym
19
23
  end
20
24
 
21
- def matches?(host)
22
- hostname == Server.new(host).hostname
23
- end
24
-
25
25
  def select?(options)
26
- selector = Selector.new(options)
26
+ selector = Selector.for(options)
27
27
  selector.call(self)
28
28
  end
29
29
 
@@ -50,6 +50,10 @@ module Capistrano
50
50
  roles.to_a
51
51
  end
52
52
 
53
+ def matches?(other)
54
+ hostname == other.hostname && port == other.port
55
+ end
56
+
53
57
  private
54
58
 
55
59
  def add_property(key, value)
@@ -103,6 +107,14 @@ module Capistrano
103
107
  @options = options
104
108
  end
105
109
 
110
+ def self.for(options)
111
+ if options.has_key?(:exclude)
112
+ Exclusive
113
+ else
114
+ self
115
+ end.new(options)
116
+ end
117
+
106
118
  def callable
107
119
  if key.respond_to?(:call)
108
120
  key
@@ -126,6 +138,17 @@ module Capistrano
126
138
  ->(server) { :all }
127
139
  end
128
140
 
141
+ class Exclusive < Selector
142
+
143
+ def key
144
+ options[:exclude]
145
+ end
146
+
147
+ def call(server)
148
+ !callable.call(server)
149
+ end
150
+ end
151
+
129
152
  end
130
153
 
131
154
  end
@@ -1,5 +1,6 @@
1
1
  require 'set'
2
2
  require_relative 'servers/role_filter'
3
+ require_relative 'servers/host_filter'
3
4
  module Capistrano
4
5
  class Configuration
5
6
  class Servers
@@ -30,11 +31,7 @@ module Capistrano
30
31
  private
31
32
 
32
33
  def server(host)
33
- if host.is_a? Server
34
- host
35
- else
36
- servers.find { |server| server.matches?(host) } || Server.new(host)
37
- end
34
+ servers.find { |server| server.matches? Server[host] } || Server[host]
38
35
  end
39
36
 
40
37
  def fetch(role)
@@ -43,7 +40,7 @@ module Capistrano
43
40
 
44
41
  def fetch_roles(required, options)
45
42
  filter_roles = RoleFilter.for(required, available_roles)
46
- select(servers_with_roles(filter_roles), options)
43
+ HostFilter.for(select(servers_with_roles(filter_roles), options))
47
44
  end
48
45
 
49
46
  def servers_with_roles(roles)
@@ -0,0 +1,82 @@
1
+ module Capistrano
2
+ class Configuration
3
+ class Servers
4
+ class HostFilter
5
+
6
+ def initialize(available)
7
+ @available = available
8
+ end
9
+
10
+ def self.for(available)
11
+ new(available).hosts
12
+ end
13
+
14
+ def hosts
15
+ if host_filter.any?
16
+ @available.select { |server| host_filter.include? server.hostname }
17
+ else
18
+ @available
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def filter
25
+ if host_filter.any?
26
+ host_filter
27
+ else
28
+ @available
29
+ end
30
+ end
31
+
32
+ def host_filter
33
+ env_filter | configuration_filter
34
+ end
35
+
36
+ def configuration_filter
37
+ ConfigurationFilter.new.hosts
38
+ end
39
+
40
+ def env_filter
41
+ EnvFilter.new.hosts
42
+ end
43
+
44
+ class ConfigurationFilter
45
+
46
+ def hosts
47
+ if filter
48
+ Array(filter.fetch(:hosts, []))
49
+ else
50
+ []
51
+ end
52
+ end
53
+
54
+ def config
55
+ Configuration.env
56
+ end
57
+
58
+ def filter
59
+ config.fetch(:filter) || config.fetch(:select)
60
+ end
61
+ end
62
+
63
+
64
+ class EnvFilter
65
+
66
+ def hosts
67
+ if filter
68
+ filter.split(',')
69
+ else
70
+ []
71
+ end
72
+ end
73
+
74
+ def filter
75
+ ENV['HOSTS']
76
+ end
77
+ end
78
+
79
+ end
80
+ end
81
+ end
82
+ end
@@ -26,9 +26,20 @@ module Capistrano
26
26
  execute :sudo, *args
27
27
  end
28
28
 
29
+ def capturing_revisions(&block)
30
+ set :previous_revision, fetch_revision
31
+ block.call
32
+ set :current_revision, fetch_revision
33
+ end
34
+
29
35
  def revision_log_message
30
36
  fetch(:revision_log_message,
31
- t(:revision_log_message, branch: fetch(:branch), user: local_user, release: release_timestamp))
37
+ t(:revision_log_message,
38
+ branch: fetch(:branch),
39
+ user: local_user,
40
+ sha: fetch(:current_revision),
41
+ release: release_timestamp)
42
+ )
32
43
  end
33
44
 
34
45
  def rollback_log_message
@@ -43,6 +54,10 @@ module Capistrano
43
54
  VersionValidator.new(locked_version).verify
44
55
  end
45
56
 
57
+ private
58
+ def fetch_revision
59
+ capture("cd #{repo_path} && git rev-parse --short HEAD")
60
+ end
46
61
  end
47
62
  end
48
63
  self.extend Capistrano::DSL
@@ -40,7 +40,17 @@ module Capistrano
40
40
  end
41
41
 
42
42
  def roles(*names)
43
- env.roles_for(names)
43
+ env.roles_for(names.flatten)
44
+ end
45
+
46
+ def release_roles(*names)
47
+ options = { exclude: :no_release }
48
+ if names.last.is_a? Hash
49
+ names.last.merge(options)
50
+ else
51
+ names << options
52
+ end
53
+ roles(*names)
44
54
  end
45
55
 
46
56
  def primary(role)
@@ -27,6 +27,14 @@ module Capistrano
27
27
  set(:release_path, releases_path.join(timestamp))
28
28
  end
29
29
 
30
+ def stage_config_path
31
+ Pathname.new fetch(:stage_config_path, 'config/deploy')
32
+ end
33
+
34
+ def deploy_config_path
35
+ Pathname.new fetch(:deploy_config_path, 'config/deploy.rb')
36
+ end
37
+
30
38
  def repo_url
31
39
  require 'cgi'
32
40
  require 'uri'
@@ -3,7 +3,14 @@ module Capistrano
3
3
  module Stages
4
4
 
5
5
  def stages
6
- Dir['config/deploy/*.rb'].map { |f| File.basename(f, '.rb') }
6
+ Dir[stage_definitions].map { |f| File.basename(f, '.rb') }
7
+ end
8
+
9
+ def infer_stages_from_stage_files
10
+ end
11
+
12
+ def stage_definitions
13
+ stage_config_path.join('*.rb')
7
14
  end
8
15
 
9
16
  def stage_set?
@@ -6,9 +6,10 @@ module Capistrano
6
6
  end
7
7
 
8
8
  def after(task, post_task, *args, &block)
9
- post_task = Rake::Task.define_task(post_task, *args, &block) if block_given?
9
+ Rake::Task.define_task(post_task, *args, &block) if block_given?
10
+ post_task = Rake::Task[post_task]
10
11
  Rake::Task[task].enhance do
11
- invoke(post_task)
12
+ post_task.invoke
12
13
  end
13
14
  end
14
15
 
@@ -49,5 +50,15 @@ module Capistrano
49
50
  %w{install}
50
51
  end
51
52
 
53
+ def exit_deploy_because_of_exception(ex)
54
+ warn t(:deploy_failed, ex: ex.inspect)
55
+ invoke 'deploy:failed'
56
+ exit(false)
57
+ end
58
+
59
+ def deploying?
60
+ fetch(:deploying, false)
61
+ end
62
+
52
63
  end
53
64
  end
@@ -1 +1,36 @@
1
1
  load File.expand_path("../tasks/git.rake", __FILE__)
2
+
3
+ require 'capistrano/scm'
4
+
5
+ class Capistrano::Git < Capistrano::SCM
6
+
7
+ # execute git with argument in the context
8
+ #
9
+ def git(*args)
10
+ args.unshift :git
11
+ context.execute *args
12
+ end
13
+
14
+ # The Capistrano default strategy for git. You should want to use this.
15
+ module DefaultStrategy
16
+ def test
17
+ test! " [ -f #{repo_path}/HEAD ] "
18
+ end
19
+
20
+ def check
21
+ test! :git, :'ls-remote', repo_url
22
+ end
23
+
24
+ def clone
25
+ git :clone, '--mirror', repo_url, repo_path
26
+ end
27
+
28
+ def update
29
+ git :remote, :update
30
+ end
31
+
32
+ def release
33
+ git :archive, fetch(:branch), '| tar -x -C', release_path
34
+ end
35
+ end
36
+ end
@@ -1 +1,33 @@
1
1
  load File.expand_path("../tasks/hg.rake", __FILE__)
2
+
3
+ require 'capistrano/scm'
4
+
5
+ class Capistrano::Hg < Capistrano::SCM
6
+ # execute hg in context with arguments
7
+ def hg(*args)
8
+ args.unshift(:hg)
9
+ context.execute *args
10
+ end
11
+
12
+ module DefaultStrategy
13
+ def test
14
+ test! " [ -d #{repo_path}/.hg ] "
15
+ end
16
+
17
+ def check
18
+ hg "id", repo_url
19
+ end
20
+
21
+ def clone
22
+ hg "clone", "--noupdate", repo_url, repo_path
23
+ end
24
+
25
+ def update
26
+ hg "pull"
27
+ end
28
+
29
+ def release
30
+ hg "archive", release_path, "--rev", fetch(:branch)
31
+ end
32
+ end
33
+ end
@@ -7,7 +7,6 @@ en = {
7
7
  start: 'Start',
8
8
  update: 'Update',
9
9
  finalize: 'Finalise',
10
- restart: 'Restart',
11
10
  finishing: 'Finishing',
12
11
  finished: 'Finished',
13
12
  stage_not_set: 'Stage not set, please call something such as `cap production deploy`, where production is a stage you have defined.',
@@ -17,8 +16,9 @@ en = {
17
16
  no_old_releases: 'No old releases (keeping newest %{keep_releases}) on %{host}',
18
17
  linked_file_does_not_exist: 'linked file %{file} does not exist on %{host}',
19
18
  mirror_exists: "The repository mirror is at %{at}",
20
- revision_log_message: 'Branch %{branch} deployed as release %{release} by %{user}',
19
+ revision_log_message: 'Branch %{branch} (at %{sha}) deployed as release %{release} by %{user}',
21
20
  rollback_log_message: '%{user} rolled back to release %{release}',
21
+ deploy_failed: 'The deploy has failed with an error: %{ex}',
22
22
  console: {
23
23
  welcome: 'capistrano console - enter command to execute on %{stage}',
24
24
  bye: 'bye'
@@ -32,3 +32,7 @@ en = {
32
32
  }
33
33
 
34
34
  I18n.backend.store_translations(:en, { capistrano: en })
35
+
36
+ if I18n.respond_to?(:enforce_available_locales=)
37
+ I18n.enforce_available_locales = true
38
+ end
@@ -0,0 +1,116 @@
1
+ module Capistrano
2
+
3
+ # Base class for SCM strategy providers.
4
+ #
5
+ # @abstract
6
+ #
7
+ # @attr_reader [Rake] context
8
+ #
9
+ # @author Hartog de Mik
10
+ #
11
+ class SCM
12
+ attr_reader :context
13
+
14
+ # Provide a wrapper for the SCM that loads a strategy for the user.
15
+ #
16
+ # @param [Rake] context The context in which the strategy should run
17
+ # @param [Module] strategy A module to include into the SCM instance. The
18
+ # module should provide the abstract methods of Capistrano::SCM
19
+ #
20
+ def initialize(context, strategy)
21
+ @context = context
22
+ singleton = class << self; self; end
23
+ singleton.send(:include, strategy)
24
+ end
25
+
26
+ # Call test in context
27
+ def test!(*args)
28
+ context.test *args
29
+ end
30
+
31
+ # The repository URL accoriding to the context
32
+ def repo_url
33
+ context.repo_url
34
+ end
35
+
36
+ # The repository path accoriding to the context
37
+ def repo_path
38
+ context.repo_path
39
+ end
40
+
41
+ # The release path accoriding to the context
42
+ def release_path
43
+ context.release_path
44
+ end
45
+
46
+ # Fetch a var from the context
47
+ # @param [Symbol] variable The variable to fetch
48
+ # @param [Object] default The default value if not found
49
+ #
50
+ def fetch(*args)
51
+ context.fetch(*args)
52
+ end
53
+
54
+ # @abstract
55
+ #
56
+ # Your implementation should check the existance of a cache repository on
57
+ # the deployment target
58
+ #
59
+ # @return [Boolean]
60
+ #
61
+ def test
62
+ raise NotImplementedError.new(
63
+ "Your SCM strategy module should provide a #test method"
64
+ )
65
+ end
66
+
67
+ # @abstract
68
+ #
69
+ # Your implementation should check if the specified remote-repository is
70
+ # available.
71
+ #
72
+ # @return [Boolean]
73
+ #
74
+ def check
75
+ raise NotImplementedError.new(
76
+ "Your SCM strategy module should provide a #check method"
77
+ )
78
+ end
79
+
80
+ # @abstract
81
+ #
82
+ # Create a (new) clone of the remote-repository on the deployment target
83
+ #
84
+ # @return void
85
+ #
86
+ def clone
87
+ raise NotImplementedError.new(
88
+ "Your SCM strategy module should provide a #clone method"
89
+ )
90
+ end
91
+
92
+ # @abstract
93
+ #
94
+ # Update the clone on the deployment target
95
+ #
96
+ # @return void
97
+ #
98
+ def update
99
+ raise NotImplementedError.new(
100
+ "Your SCM strategy module should provide a #update method"
101
+ )
102
+ end
103
+
104
+ # @abstract
105
+ #
106
+ # Copy the contents of the cache-repository onto the release path
107
+ #
108
+ # @return void
109
+ #
110
+ def release
111
+ raise NotImplementedError.new(
112
+ "Your SCM strategy module should provide a #release method"
113
+ )
114
+ end
115
+ end
116
+ end