capistrano 3.5.0 → 3.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +5 -1
  3. data/CHANGELOG.md +55 -10
  4. data/README.md +3 -3
  5. data/RELEASING.md +1 -0
  6. data/UPGRADING-3.7.md +97 -0
  7. data/capistrano.gemspec +1 -1
  8. data/features/deploy.feature +1 -0
  9. data/features/stage_failure.feature +9 -0
  10. data/features/step_definitions/assertions.rb +5 -0
  11. data/features/step_definitions/setup.rb +4 -0
  12. data/lib/capistrano/application.rb +5 -10
  13. data/lib/capistrano/configuration.rb +8 -7
  14. data/lib/capistrano/configuration/filter.rb +4 -5
  15. data/lib/capistrano/configuration/host_filter.rb +1 -1
  16. data/lib/capistrano/configuration/plugin_installer.rb +1 -1
  17. data/lib/capistrano/configuration/server.rb +8 -2
  18. data/lib/capistrano/configuration/validated_variables.rb +75 -0
  19. data/lib/capistrano/configuration/variables.rb +7 -23
  20. data/lib/capistrano/defaults.rb +10 -0
  21. data/lib/capistrano/doctor.rb +1 -0
  22. data/lib/capistrano/doctor/gems_doctor.rb +1 -1
  23. data/lib/capistrano/doctor/servers_doctor.rb +105 -0
  24. data/lib/capistrano/doctor/variables_doctor.rb +6 -7
  25. data/lib/capistrano/dsl.rb +28 -4
  26. data/lib/capistrano/dsl/paths.rb +1 -1
  27. data/lib/capistrano/dsl/stages.rb +15 -1
  28. data/lib/capistrano/dsl/task_enhancements.rb +6 -1
  29. data/lib/capistrano/i18n.rb +2 -0
  30. data/lib/capistrano/proc_helpers.rb +13 -0
  31. data/lib/capistrano/tasks/deploy.rake +9 -1
  32. data/lib/capistrano/tasks/doctor.rake +6 -1
  33. data/lib/capistrano/tasks/git.rake +11 -4
  34. data/lib/capistrano/templates/deploy.rb.erb +2 -15
  35. data/lib/capistrano/version.rb +1 -1
  36. data/spec/lib/capistrano/configuration/empty_filter_spec.rb +1 -1
  37. data/spec/lib/capistrano/configuration/filter_spec.rb +8 -8
  38. data/spec/lib/capistrano/configuration/host_filter_spec.rb +1 -1
  39. data/spec/lib/capistrano/configuration/null_filter_spec.rb +1 -1
  40. data/spec/lib/capistrano/configuration/question_spec.rb +1 -1
  41. data/spec/lib/capistrano/configuration/role_filter_spec.rb +1 -1
  42. data/spec/lib/capistrano/configuration/server_spec.rb +3 -2
  43. data/spec/lib/capistrano/configuration_spec.rb +16 -3
  44. data/spec/lib/capistrano/doctor/gems_doctor_spec.rb +6 -0
  45. data/spec/lib/capistrano/doctor/servers_doctor_spec.rb +86 -0
  46. data/spec/lib/capistrano/doctor/variables_doctor_spec.rb +9 -0
  47. data/spec/lib/capistrano/dsl/paths_spec.rb +9 -9
  48. data/spec/lib/capistrano/dsl/task_enhancements_spec.rb +5 -0
  49. data/spec/lib/capistrano/dsl_spec.rb +37 -3
  50. data/spec/lib/capistrano/version_validator_spec.rb +2 -2
  51. data/spec/support/test_app.rb +10 -0
  52. metadata +12 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 997bdc47eec05054270bbd80c9d5e44fb142c7ad
4
- data.tar.gz: 82ae85408f7e67427b2cffccc95c2fe33c834288
3
+ metadata.gz: 1dffc85194d6ad3db0812f96e216fe5d95a508e9
4
+ data.tar.gz: f93a202158ed81afecc72d6d19db46b9c965fdad
5
5
  SHA512:
6
- metadata.gz: 3a3314345dc42f3eb9c26c9eb249bf20281d4e57944c19b7e04f97597daac149a858c5eddda45958b9304323d3fa36722d845699c704345724993d100efd3117
7
- data.tar.gz: b16b26b8b29ea8a82bbd61850595807f9a045f7d3a3fb66330ce7fb4b00fd6775747a482a1eb3cec3c1ecdb9604487fe7b509f1884d23d39fbe1464a2399d0ad
6
+ metadata.gz: 07cb65b0395968f71258ab6e0f6886d658f5c5f6a87e1780537474855f19453c2c09e77fc7b6d7d11ce062133aca191dc1be4e19ad9f21b22c37d82a86925e55
7
+ data.tar.gz: 65d563a1ae4fce777c70c225f2f5d711d4c5e68493b3254215a372ba72f06fb4cf410637ab82758af6d2ac91d91ec8c11c8648da4c5a57deec7800838b30a6c4
@@ -4,7 +4,11 @@ rvm:
4
4
  - 2.2
5
5
  - 2.1
6
6
  - 2.0
7
- - rbx-2
7
+ matrix:
8
+ include:
9
+ # Run specs on Rubinius, but not RuboCop, since it seg faults
10
+ - rvm: rbx-2
11
+ script: bundle exec rake spec
8
12
  script: bundle exec rake spec rubocop
9
13
  install: bundle install --jobs=1
10
14
  cache: bundler
@@ -4,7 +4,49 @@ Reverse Chronological Order:
4
4
 
5
5
  ## master
6
6
 
7
- https://github.com/capistrano/capistrano/compare/v3.5.0...HEAD
7
+ https://github.com/capistrano/capistrano/compare/v3.6.0...HEAD
8
+
9
+ * Your contribution here!
10
+
11
+ ## `3.6.0` (2016-07-26)
12
+
13
+ https://github.com/capistrano/capistrano/compare/v3.5.0...v3.6.0
14
+
15
+ Thank you to the many first-time contributors from the Capistrano community who
16
+ helped with this release!
17
+
18
+ ### Deprecations:
19
+
20
+ * Deprecate `remote_file` feature (will be removed in Capistrano 3.7.0) (@lebedev-yury)
21
+ * Deprecate `:git_strategy`, `:hg_strategy`, and `:svn_strategy` variables.
22
+ These will be completely removed in 3.7.0.
23
+ * Added warning about future deprecation of reinvocation behaviour (@troelskn)
24
+
25
+ Refer to the [Capistrano 3.7.0 upgrade document](UPGRADING-3.7.md) if you are
26
+ affected by these deprecations.
27
+
28
+ ### New features:
29
+
30
+ * Added a `doctor:servers` subtask that outputs a summary of servers, roles & properties (@irvingwashington)
31
+ * Make path to git wrapper script configurable (@thickpaddy)
32
+ * Make name of current directory configurable via configuration variable `:current_directory` (@websi)
33
+ * It is now possible to rollback to a specific release using the
34
+ `ROLLBACK_RELEASE` environment variable.
35
+ [#1155](https://github.com/capistrano/capistrano/issues/1155) (@lanrion)
36
+
37
+ ### Fixes:
38
+
39
+ * `doctor` no longer erroneously warns that `:git_strategy` and other SCM options are "unrecognized" (@shanesaww)
40
+ * Fix `NoMethodError: undefined method gsub` when setting `:application` to a
41
+ Proc. [#1681](https://github.com/capistrano/capistrano/issues/1681)
42
+ (@mattbrictson)
43
+
44
+ ### Other changes:
45
+
46
+ * Raise a better error when an ‘after’ hook isn’t found (@jdelStrother)
47
+ * Change git wrapper path to work better with multiple users (@thickpaddy)
48
+ * Restrict the uploaded git wrapper script permissions to 700 (@irvingwashington)
49
+ * Add `net-ssh` gem version to `doctor:gems` output (@lebedev-yury)
8
50
 
9
51
  ## `3.5.0`
10
52
 
@@ -26,12 +68,18 @@ and how to configure it, visit the
26
68
 
27
69
  ### Potentially breaking changes:
28
70
 
29
- * Drop support for Ruby 1.9.3 (Capistrano may still work with 1.9.3, but it is
30
- no longer officially supported)
71
+ * Drop support for Ruby 1.9.3 (Capistrano does no longer work with 1.9.3)
31
72
  * Git version 1.6.3 or greater is now required
32
73
  * Remove 'vendor/bundle' from default :linked_dirs (@ojab)
33
74
  * Old versions of SSHKit (before 1.9.0) are no longer supported
34
75
  * SHA1 hash of current git revision written to REVISION file is no longer abbreviated
76
+ * Ensure task invocation within after hooks is namespace aware, which may require
77
+ you to change how your `after` hooks are declared in some cases; see
78
+ [#1652](https://github.com/capistrano/capistrano/issues/1652) for an example
79
+ and how to correct it (@thickpaddy)
80
+ * Validation of the `:application` variable forbids special characters such as slash,
81
+ this may be a breaking change in case that you rely on using a `/` in your application
82
+ name to deploy from a sub directory.
35
83
 
36
84
  ### New features:
37
85
 
@@ -53,6 +101,10 @@ and how to configure it, visit the
53
101
  * Added option to set specific revision when using Subversion as SCM (@marcovtwout)
54
102
  * Deduplicate list of linked directories
55
103
  * Integration with Harrow.io (See http://capistranorb.com/documentation/harrow/) when running `cap install`
104
+ * Added validate method to DSL to allow validation of certain values (@Kriechi)
105
+ * validate values before assignment inside of `set(:key, value)`
106
+ * should raise a `Capistrano::ValidationError` if invalid
107
+ * Added default validation for Capistrano-specific variables (@Kriechi)
56
108
 
57
109
  ### Fixes:
58
110
 
@@ -68,7 +120,6 @@ and how to configure it, visit the
68
120
  * Refactor `Configuration::Filter` to use filtering strategies instead
69
121
  of case statements (@cshaffer)
70
122
  * Clean up rubocop lint warnings (@cshaffer)
71
- * Ensure task invocation within after hooks is namespace aware (@thickpaddy)
72
123
 
73
124
  ## `3.4.0`
74
125
 
@@ -187,12 +238,6 @@ https://github.com/capistrano/capistrano/compare/v3.2.1...v3.3.3
187
238
  This allows roles to specify properties common to all servers and
188
239
  then for individual servers to modify them, keeping things DRY
189
240
 
190
- * Enhancements (@Kriechi)
191
- * Added validate method to DSL to allow validation of certain values
192
- - validate values before assignment inside of `set(:key, value)`
193
- - should raise a `Capistrano::ValidationError` if invalid
194
- * Added default validation for Capistrano-specific variables
195
-
196
241
  Breaking Changes:
197
242
  * By using Ruby's noecho method introduced in Ruby version 1.9.3, we dropped support for Ruby versions prior to 1.9.3. See [issue #878](https://github.com/capistrano/capistrano/issues/878) and [PR #1112](https://github.com/capistrano/capistrano/pull/1112) for more information. (@kaikuchn)
198
243
  * Track (anonymous) statistics, see https://github.com/capistrano/stats. This breaks automated deployment on continuous integration servers until the `.capistrano/metrics` file is created (with content `full` to simulate a "yes") via the interactive prompt or manually.
data/README.md CHANGED
@@ -1,9 +1,9 @@
1
1
 
2
2
  # Capistrano: A deployment automation tool built on Ruby, Rake, and SSH.
3
3
 
4
- [![Gem Version](https://badge.fury.io/rb/capistrano.svg)](http://badge.fury.io/rb/capistrano) [![Build Status](https://travis-ci.org/capistrano/capistrano.svg?branch=master)](https://travis-ci.org/capistrano/capistrano) [![Code Climate](https://img.shields.io/codeclimate/github/capistrano/capistrano.svg)](https://codeclimate.com/github/capistrano/capistrano) <a href="http://codersclan.net/?repo_id=325&source=small"><img src="https://img.shields.io/badge/get-support-blue.svg"></a>
4
+ [![Gem Version](https://badge.fury.io/rb/capistrano.svg)](http://badge.fury.io/rb/capistrano) [![Build Status](https://travis-ci.org/capistrano/capistrano.svg?branch=master)](https://travis-ci.org/capistrano/capistrano) [![Code Climate](https://codeclimate.com/github/capistrano/capistrano/badges/gpa.svg)](https://codeclimate.com/github/capistrano/capistrano) [![CodersClan](https://img.shields.io/badge/get-support-blue.svg)](http://codersclan.net/?repo_id=325&source=small)
5
5
 
6
- Capistrano is framework for building automated deployment scripts. Although Capistrano itself is written in Ruby, it can easily be used to deploy projects of any language or framework, be it Rails, Java, or PHP.
6
+ Capistrano is a framework for building automated deployment scripts. Although Capistrano itself is written in Ruby, it can easily be used to deploy projects of any language or framework, be it Rails, Java, or PHP.
7
7
 
8
8
  Once installed, Capistrano gives you a `cap` tool to perform your deployments from the comfort of your command line.
9
9
 
@@ -103,7 +103,7 @@ Add Capistrano to your project's Gemfile:
103
103
 
104
104
  ``` ruby
105
105
  group :development do
106
- gem "capistrano", "~> 3.4"
106
+ gem "capistrano", "~> 3.6"
107
107
  end
108
108
  ```
109
109
 
@@ -11,6 +11,7 @@
11
11
  2. **Ensure all tests are passing by running `rake spec` and `rake features`.**
12
12
  3. Determine which would be the correct next version number according to [semver](http://semver.org/).
13
13
  4. Update the version in `./lib/capistrano/version.rb`.
14
+ 4. Update the version in the `./README.md` Gemfile example (`gem "capistrano", "~> X.Y"`).
14
15
  5. Update the `CHANGELOG`.
15
16
  6. Commit the changelog and version in a single commit, the message should be "Preparing vX.Y.Z"
16
17
  7. Run `rake release`; this will tag, push to GitHub, and publish to rubygems.org.
@@ -0,0 +1,97 @@
1
+ # Capistrano 3.7.0 upgrade guide
2
+
3
+ Capistrano 3.7.0 has not yet been released. This guide serves as a preview of
4
+ what is *planned* for 3.7.0, so that you can be prepared to update your
5
+ Capistrano deployment if necessary once it becomes available.
6
+
7
+ If you wish to try the new 3.7.0 behavior today, you can do so by using the
8
+ `master` branch in your Gemfile:
9
+
10
+ ```ruby
11
+ gem "capistrano", :github => "capistrano/capistrano"
12
+ ```
13
+
14
+ ## The :scm variable is deprecated
15
+
16
+ Up until now, Capistrano's SCM was configured using the `:scm` variable:
17
+
18
+ ```ruby
19
+ # This is now deprecated
20
+ set :scm, :svn
21
+ ```
22
+
23
+ To avoid deprecation warnings:
24
+
25
+ 1. Remove `set :scm, ...` from your Capistrano configuration.
26
+ 2. Add *one* of the following SCM declarations to your `Capfile`:
27
+
28
+ ```ruby
29
+ # To use Git
30
+ require "capistrano/scm/git"
31
+ install_plugin Capistrano::SCM::Git
32
+
33
+ # To use Mercurial
34
+ require "capistrano/scm/hg"
35
+ install_plugin Capistrano::SCM::Hg
36
+
37
+ # To use Subversion
38
+ require "capistrano/scm/svn"
39
+ install_plugin Capistrano::SCM::Svn
40
+ ```
41
+
42
+ ## This is the last release where Git is the automatic default
43
+
44
+ If you do not specify an SCM, Capistrano assumes Git. However this behavior is
45
+ now deprecated. Add this to your Capfile to avoid deprecation warnings:
46
+
47
+ ```ruby
48
+ require "capistrano/scm/git"
49
+ install_plugin Capistrano::SCM::Git
50
+ ```
51
+
52
+ ## :git_strategy, :hg_strategy, and :svn_strategy are removed
53
+
54
+ Capistrano 3.7.0 has a rewritten SCM system that relies on "plugins". This
55
+ system is more flexible than the old "strategy" system that only allowed certain
56
+ parts of SCM tasks to be customized.
57
+
58
+ If your deployment relies on a custom SCM strategy, you will need to rewrite
59
+ that strategy to be a full-fledged SCM plugin instead. There is a fairly
60
+ straightforward migration path: write your plugin to be a subclass of the
61
+ built-in SCM that you want to customize. For example:
62
+
63
+ ```ruby
64
+ require "capistrano/scm/git"
65
+
66
+ class MyCustomGit < Capistrano::SCM::Git
67
+ # Override the methods you wish to customize, e.g.:
68
+ def clone_repo
69
+ # ...
70
+ end
71
+ end
72
+ ```
73
+
74
+ Then use your plugin in by loading it in the Capfile:
75
+
76
+ ```ruby
77
+ require_relative "path/to/my_custom_git.rb"
78
+ install_plugin MyCustomGit
79
+ ```
80
+
81
+ ## Existing third-party SCMs are deprecated
82
+
83
+ If you are using a third-party SCM, you can continue using it without
84
+ changes, but you will see deprecation warnings. Contact the maintainer of the
85
+ third-party SCM gem and ask them about modifying the gem to work with the new
86
+ Capistrano 3.7.0 SCM plugin system.
87
+
88
+ ## remote_file is removed
89
+
90
+ The `remote_file` method is no longer in Capistrano 3.7.0. You can read the
91
+ discussion that led to its removal here:
92
+ [issue 762](https://github.com/capistrano/capistrano/issues/762).
93
+
94
+ There is no direct replacement. To migrate to 3.7.0, you will need to rewrite
95
+ any parts of your deployment that use `remote_file` to use a different
96
+ mechanism for uploading files. Consider using the `upload!` method directly in
97
+ a procedural fashion instead.
@@ -19,7 +19,7 @@ Gem::Specification.new do |gem|
19
19
 
20
20
  gem.licenses = ["MIT"]
21
21
 
22
- gem.required_ruby_version = ">= 1.9.3"
22
+ gem.required_ruby_version = ">= 2.0"
23
23
  gem.add_dependency "airbrussh", ">= 1.0.0"
24
24
  gem.add_dependency "i18n"
25
25
  gem.add_dependency "rake", ">= 10.0.0"
@@ -8,6 +8,7 @@ Feature: Deploy
8
8
  When I run cap "git:check"
9
9
  Then the task is successful
10
10
  And references in the remote repo are listed
11
+ And git wrapper permissions are 0700
11
12
 
12
13
  Scenario: Creating the directory structure
13
14
  When I run cap "deploy:check:directories"
@@ -0,0 +1,9 @@
1
+ Feature: Stage failure
2
+
3
+ Background:
4
+ Given a test app with the default configuration
5
+ And a stage file named deploy.rb
6
+
7
+ Scenario: Running a task
8
+ When I run cap "doctor"
9
+ Then the task fails
@@ -2,6 +2,11 @@ Then(/^references in the remote repo are listed$/) do
2
2
  expect(@output).to include("refs/heads/master")
3
3
  end
4
4
 
5
+ Then(/^git wrapper permissions are 0700$/) do
6
+ permissions_test = %Q([ $(stat -c "%a" #{TestApp.git_wrapper_path}) == "700" ])
7
+ expect(vagrant_cli_command("ssh -c '#{permissions_test}'")).to be_success
8
+ end
9
+
5
10
  Then(/^the shared path is created$/) do
6
11
  run_vagrant_command(test_dir_exists(TestApp.shared_path))
7
12
  end
@@ -52,3 +52,7 @@ Given(/^a custom task to run in the event of a failure$/) do
52
52
  safely_remove_file(TestApp.shared_path.join("failed"))
53
53
  TestApp.copy_task_to_test_app("spec/support/tasks/failed.rake")
54
54
  end
55
+
56
+ Given(/^a stage file named (.+)$/) do |filename|
57
+ TestApp.write_local_stage_file(filename)
58
+ end
@@ -107,8 +107,7 @@ module Capistrano
107
107
  lambda do |_value|
108
108
  puts "Capistrano Version: #{Capistrano::VERSION} (Rake Version: #{Rake::VERSION})"
109
109
  exit
110
- end
111
- ]
110
+ end]
112
111
  end
113
112
 
114
113
  def dry_run
@@ -116,8 +115,7 @@ module Capistrano
116
115
  "Do a dry run without executing actions",
117
116
  lambda do |_value|
118
117
  Configuration.env.set(:sshkit_backend, SSHKit::Backend::Printer)
119
- end
120
- ]
118
+ end]
121
119
  end
122
120
 
123
121
  def roles
@@ -125,8 +123,7 @@ module Capistrano
125
123
  "Run SSH commands only on hosts matching these roles",
126
124
  lambda do |value|
127
125
  Configuration.env.add_cmdline_filter(:role, value)
128
- end
129
- ]
126
+ end]
130
127
  end
131
128
 
132
129
  def hostfilter
@@ -134,8 +131,7 @@ module Capistrano
134
131
  "Run SSH commands only on matching hosts",
135
132
  lambda do |value|
136
133
  Configuration.env.add_cmdline_filter(:host, value)
137
- end
138
- ]
134
+ end]
139
135
  end
140
136
 
141
137
  def print_config_variables
@@ -143,8 +139,7 @@ module Capistrano
143
139
  "Display the defined config variables before starting the deployment tasks.",
144
140
  lambda do |_value|
145
141
  Configuration.env.set(:print_config_variables, true)
146
- end
147
- ]
142
+ end]
148
143
  end
149
144
  end
150
145
  end
@@ -3,10 +3,11 @@ require_relative "configuration/question"
3
3
  require_relative "configuration/plugin_installer"
4
4
  require_relative "configuration/server"
5
5
  require_relative "configuration/servers"
6
+ require_relative "configuration/validated_variables"
6
7
  require_relative "configuration/variables"
7
8
 
8
9
  module Capistrano
9
- class ValidationError < Exception; end
10
+ class ValidationError < RuntimeError; end
10
11
 
11
12
  class Configuration
12
13
  def self.env
@@ -23,7 +24,7 @@ module Capistrano
23
24
  :set, :fetch, :fetch_for, :delete, :keys, :validate
24
25
 
25
26
  def initialize(values={})
26
- @variables = Variables.new(values)
27
+ @variables = ValidatedVariables.new(Variables.new(values))
27
28
  end
28
29
 
29
30
  def ask(key, default=nil, options={})
@@ -129,20 +130,20 @@ module Capistrano
129
130
  fetch(:sshkit_backend) == SSHKit::Backend::Printer
130
131
  end
131
132
 
132
- def install_plugin(plugin, load_hooks:true)
133
+ def install_plugin(plugin, load_hooks: true)
133
134
  installer.install(plugin, load_hooks: load_hooks)
134
135
  end
135
136
 
137
+ def servers
138
+ @servers ||= Servers.new
139
+ end
140
+
136
141
  private
137
142
 
138
143
  def cmdline_filters
139
144
  @cmdline_filters ||= []
140
145
  end
141
146
 
142
- def servers
143
- @servers ||= Servers.new
144
- end
145
-
146
147
  def installer
147
148
  @installer ||= PluginInstaller.new
148
149
  end
@@ -10,11 +10,10 @@ module Capistrano
10
10
  def initialize(type, values=nil)
11
11
  raise "Invalid filter type #{type}" unless [:host, :role].include? type
12
12
  av = Array(values)
13
- @strategy = case
14
- when av.empty? then EmptyFilter.new
15
- when av.include?(:all), av.include?("all") then NullFilter.new
16
- when type == :host then HostFilter.new(values)
17
- when type == :role then RoleFilter.new(values)
13
+ @strategy = if av.empty? then EmptyFilter.new
14
+ elsif av.include?(:all) || av.include?("all") then NullFilter.new
15
+ elsif type == :host then HostFilter.new(values)
16
+ elsif type == :role then RoleFilter.new(values)
18
17
  else NullFilter.new
19
18
  end
20
19
  end
@@ -3,7 +3,7 @@ module Capistrano
3
3
  class HostFilter
4
4
  def initialize(values)
5
5
  av = Array(values).dup
6
- av.map! { |v| (v.is_a?(String) && v =~ /^(?<name>[-A-Za-z0-9.]+)(,\g<name>)*$/) ? v.split(",") : v }
6
+ av.map! { |v| v.is_a?(String) && v =~ /^(?<name>[-A-Za-z0-9.]+)(,\g<name>)*$/ ? v.split(",") : v }
7
7
  av.flatten!
8
8
  @rex = regex_matcher(av)
9
9
  end
@@ -18,7 +18,7 @@ module Capistrano
18
18
  # install(Capistrano::SCM::Git)
19
19
  # install(Capistrano::SCM::Git.new)
20
20
  #
21
- def install(plugin, load_hooks:true)
21
+ def install(plugin, load_hooks: true)
22
22
  plugin = plugin.is_a?(Class) ? plugin.new : plugin
23
23
 
24
24
  plugin.define_tasks
@@ -99,8 +99,8 @@ module Capistrano
99
99
  @properties[key]
100
100
  end
101
101
 
102
- def respond_to?(method, _include_all=false)
103
- @properties.key?(method)
102
+ def respond_to_missing?(method, _include_all=false)
103
+ @properties.key?(method) || super
104
104
  end
105
105
 
106
106
  def roles
@@ -111,6 +111,7 @@ module Capistrano
111
111
  @properties.keys
112
112
  end
113
113
 
114
+ # rubocop:disable Style/MethodMissing
114
115
  def method_missing(key, value=nil)
115
116
  if value
116
117
  set(lvalue(key), value)
@@ -118,6 +119,11 @@ module Capistrano
118
119
  fetch(key)
119
120
  end
120
121
  end
122
+ # rubocop:enable Style/MethodMissing
123
+
124
+ def to_h
125
+ @properties
126
+ end
121
127
 
122
128
  private
123
129