chefspec 2.0.1 → 3.0.0.beta.1

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 (110) hide show
  1. checksums.yaml +4 -4
  2. data/lib/chefspec.rb +30 -44
  3. data/lib/chefspec/api.rb +74 -0
  4. data/lib/chefspec/api/apt_package.rb +192 -0
  5. data/lib/chefspec/api/batch.rb +43 -0
  6. data/lib/chefspec/api/chef_gem.rb +191 -0
  7. data/lib/chefspec/api/cookbook_file.rb +166 -0
  8. data/lib/chefspec/api/cron.rb +80 -0
  9. data/lib/chefspec/api/deploy.rb +117 -0
  10. data/lib/chefspec/api/directory.rb +80 -0
  11. data/lib/chefspec/api/dpkg_package.rb +117 -0
  12. data/lib/chefspec/api/easy_install_package.rb +154 -0
  13. data/lib/chefspec/api/env.rb +117 -0
  14. data/lib/chefspec/api/erl_call.rb +43 -0
  15. data/lib/chefspec/api/execute.rb +43 -0
  16. data/lib/chefspec/api/file.rb +166 -0
  17. data/lib/chefspec/api/freebsd_package.rb +80 -0
  18. data/lib/chefspec/api/gem_package.rb +191 -0
  19. data/lib/chefspec/api/git.rb +117 -0
  20. data/lib/chefspec/api/group.rb +154 -0
  21. data/lib/chefspec/api/http_request.rb +228 -0
  22. data/lib/chefspec/api/ifconfig.rb +154 -0
  23. data/lib/chefspec/api/include_recipe.rb +26 -0
  24. data/lib/chefspec/api/ips_package.rb +117 -0
  25. data/lib/chefspec/api/link.rb +102 -0
  26. data/lib/chefspec/api/log.rb +43 -0
  27. data/lib/chefspec/api/macports_package.rb +154 -0
  28. data/lib/chefspec/api/mdadm.rb +117 -0
  29. data/lib/chefspec/api/mount.rb +192 -0
  30. data/lib/chefspec/api/notifications.rb +38 -0
  31. data/lib/chefspec/api/ohai.rb +43 -0
  32. data/lib/chefspec/api/package.rb +192 -0
  33. data/lib/chefspec/api/pacman_package.rb +155 -0
  34. data/lib/chefspec/api/portage_package.rb +155 -0
  35. data/lib/chefspec/api/powershell_script.rb +43 -0
  36. data/lib/chefspec/api/registry_key.rb +166 -0
  37. data/lib/chefspec/api/remote_directory.rb +120 -0
  38. data/lib/chefspec/api/remote_file.rb +166 -0
  39. data/lib/chefspec/api/render_file.rb +32 -0
  40. data/lib/chefspec/api/route.rb +80 -0
  41. data/lib/chefspec/api/rpm_package.rb +117 -0
  42. data/lib/chefspec/api/ruby_block.rb +37 -0
  43. data/lib/chefspec/api/script.rb +228 -0
  44. data/lib/chefspec/api/service.rb +246 -0
  45. data/lib/chefspec/api/smartos_package.rb +117 -0
  46. data/lib/chefspec/api/solaris_package.rb +80 -0
  47. data/lib/chefspec/api/subversion.rb +154 -0
  48. data/lib/chefspec/api/template.rb +166 -0
  49. data/lib/chefspec/api/user.rb +228 -0
  50. data/lib/chefspec/api/yum_package.rb +154 -0
  51. data/lib/chefspec/berkshelf.rb +37 -0
  52. data/lib/chefspec/deprecations.rb +151 -0
  53. data/lib/chefspec/errors.rb +99 -0
  54. data/lib/chefspec/expect_exception.rb +45 -0
  55. data/lib/chefspec/extensions/chef/client.rb +15 -0
  56. data/lib/chefspec/extensions/chef/conditional.rb +11 -0
  57. data/lib/chefspec/extensions/chef/data_query.rb +29 -0
  58. data/lib/chefspec/extensions/chef/lwrp_base.rb +44 -0
  59. data/lib/chefspec/extensions/chef/resource.rb +27 -0
  60. data/lib/chefspec/extensions/chef/securable.rb +19 -0
  61. data/lib/chefspec/formatter.rb +270 -0
  62. data/lib/chefspec/macros.rb +217 -0
  63. data/lib/chefspec/matchers.rb +9 -0
  64. data/lib/chefspec/matchers/include_recipe_matcher.rb +45 -0
  65. data/lib/chefspec/matchers/link_to_matcher.rb +28 -0
  66. data/lib/chefspec/matchers/notifications_matcher.rb +92 -0
  67. data/lib/chefspec/matchers/render_file_matcher.rb +72 -0
  68. data/lib/chefspec/matchers/resource_matcher.rb +143 -0
  69. data/lib/chefspec/renderer.rb +137 -0
  70. data/lib/chefspec/rspec.rb +17 -0
  71. data/lib/chefspec/runner.rb +274 -0
  72. data/lib/chefspec/stubs/command_registry.rb +11 -0
  73. data/lib/chefspec/stubs/command_stub.rb +37 -0
  74. data/lib/chefspec/stubs/data_bag_item_registry.rb +13 -0
  75. data/lib/chefspec/stubs/data_bag_item_stub.rb +25 -0
  76. data/lib/chefspec/stubs/data_bag_registry.rb +13 -0
  77. data/lib/chefspec/stubs/data_bag_stub.rb +23 -0
  78. data/lib/chefspec/stubs/registry.rb +32 -0
  79. data/lib/chefspec/stubs/search_registry.rb +13 -0
  80. data/lib/chefspec/stubs/search_stub.rb +25 -0
  81. data/lib/chefspec/stubs/stub.rb +37 -0
  82. data/lib/chefspec/version.rb +1 -2
  83. metadata +100 -103
  84. data/lib/chef/expect_exception.rb +0 -34
  85. data/lib/chef/formatters/chefspec.rb +0 -233
  86. data/lib/chef/knife/cookbook_create_specs.rb +0 -107
  87. data/lib/chefspec/chef_runner.rb +0 -275
  88. data/lib/chefspec/helpers/describe.rb +0 -17
  89. data/lib/chefspec/matchers/cron.rb +0 -7
  90. data/lib/chefspec/matchers/env.rb +0 -8
  91. data/lib/chefspec/matchers/execute.rb +0 -33
  92. data/lib/chefspec/matchers/file.rb +0 -83
  93. data/lib/chefspec/matchers/file_content.rb +0 -32
  94. data/lib/chefspec/matchers/group.rb +0 -8
  95. data/lib/chefspec/matchers/include_recipe.rb +0 -20
  96. data/lib/chefspec/matchers/link.rb +0 -14
  97. data/lib/chefspec/matchers/log.rb +0 -21
  98. data/lib/chefspec/matchers/notifications.rb +0 -43
  99. data/lib/chefspec/matchers/package.rb +0 -39
  100. data/lib/chefspec/matchers/python.rb +0 -7
  101. data/lib/chefspec/matchers/ruby_block.rb +0 -13
  102. data/lib/chefspec/matchers/script.rb +0 -34
  103. data/lib/chefspec/matchers/service.rb +0 -25
  104. data/lib/chefspec/matchers/shared.rb +0 -132
  105. data/lib/chefspec/matchers/user.rb +0 -8
  106. data/lib/chefspec/minitest.rb +0 -195
  107. data/lib/chefspec/monkey_patches/conditional.rb +0 -19
  108. data/lib/chefspec/monkey_patches/hash.rb +0 -23
  109. data/lib/chefspec/monkey_patches/lwrp_base.rb +0 -45
  110. data/lib/chefspec/monkey_patches/provider.rb +0 -43
@@ -0,0 +1,37 @@
1
+ begin
2
+ require 'berkshelf'
3
+ rescue LoadError
4
+ raise RuntimeError, "Berkshelf not found! You must have the berkshelf" \
5
+ " installed on your system before requiring chefspec/berkshelf. Install" \
6
+ " berkshelf by running:\n\n gem install berkshelf\n\nor add Berkshelf" \
7
+ " to your Gemfile:\n\n gem 'berkshelf'\n\n"
8
+ end
9
+
10
+ module ChefSpec
11
+ class Berkshelf
12
+ class << self
13
+ extend Forwardable
14
+ def_delegators :instance, :setup!
15
+ end
16
+
17
+ include Singleton
18
+
19
+ def initialize
20
+ setup!
21
+ end
22
+
23
+ def setup!
24
+ tmpdir = Dir.mktmpdir
25
+
26
+ ::Berkshelf.ui.mute do
27
+ ::Berkshelf::Berksfile.from_file('Berksfile').install(path: tmpdir)
28
+ end
29
+
30
+ ::RSpec.configure do |config|
31
+ config.cookbook_path = tmpdir
32
+ end
33
+ end
34
+ end
35
+ end
36
+
37
+ ChefSpec::Berkshelf.setup!
@@ -0,0 +1,151 @@
1
+ module Kernel
2
+ # Kernel extension to print deprecation notices.
3
+ #
4
+ # @example printing a deprecation warning
5
+ # deprecated 'no longer in use' #=> "[DEPRECATION] no longer in use"
6
+ #
7
+ # @param [Array<String>] messages
8
+ def deprecated(*messages)
9
+ messages.each do |message|
10
+ calling_spec = caller.find { |line| line =~ /(\/spec)|(_spec\.rb)/ }
11
+ calling_spec = 'spec/' + calling_spec.split('/spec/').last
12
+ warn "[DEPRECATION] #{message} (called from #{calling_spec})"
13
+ end
14
+ end
15
+ end
16
+
17
+ module ChefSpec
18
+ # @deprecated {ChefSpec::ChefRunner} is deprecated. Please use
19
+ # {ChefSpec::Runner} instead.
20
+ class ChefRunner
21
+ def self.new(*args, &block)
22
+ deprecated '`ChefSpec::ChefRunner` is deprecated. Please use' \
23
+ ' `ChefSpec::Runner` instead.'
24
+
25
+ ChefSpec::Runner.new(*args, &block)
26
+ end
27
+ end
28
+ end
29
+
30
+ module ChefSpec
31
+ class Runner
32
+ alias_method :existing_initialize, :initialize
33
+ def initialize(*args, &block)
34
+ if args.first.is_a?(Hash)
35
+ if args.first.has_key?(:evaluate_guards)
36
+ deprecated 'The `:evaluate_guards` option is deprecated. Guards are' \
37
+ ' automatically evaluated by default. Please use `stub_command` to' \
38
+ ' stub shell guards.'
39
+ end
40
+
41
+ if args.first.has_key?(:actually_run_shell_guards)
42
+ deprecated 'The `:actually_run_shell_guards` option is deprecated.' \
43
+ ' Shell commands must be stubbed using `stub_command`.'
44
+ end
45
+ end
46
+
47
+ existing_initialize(*args, &block)
48
+ end
49
+ end
50
+ end
51
+
52
+ module ChefSpec::API
53
+ module DeprecatedMatchers
54
+ def be_owned_by(user, group)
55
+ deprecated "The `be_owned_by` matcher is deprecated. Please use:" \
56
+ "\n\n" \
57
+ " expect(resource.owner).to eq('#{user}')\n" \
58
+ " expect(resource.group).to eq('#{group}')" \
59
+ "\n\n" \
60
+ "instead"
61
+ raise ChefSpec::NoConversionError.new('be_owned_by')
62
+ end
63
+
64
+ def create_file_with_content(path, content)
65
+ deprecated "The `create_file_with_content` matcher is deprecated." \
66
+ " Please use `render_file(#{path.inspect})" \
67
+ ".with_content(#{content.inspect})` instead."
68
+ ChefSpec::Matchers::RenderFileMatcher.new(path).with_content(content)
69
+ end
70
+
71
+ [:package, :yum_package, :gem_package, :chef_gem].each do |type|
72
+ matcher_name = "install_#{type}_at_version".to_sym
73
+ define_method(matcher_name) do |name, version|
74
+ deprecated "The `#{matcher_name}` matcher is deprecated." \
75
+ " Please use `install_package(#{name.inspect})" \
76
+ ".with(version: #{version.inspect})` instead."
77
+ ChefSpec::Matchers::ResourceMatcher.new(type, :install, package)
78
+ .with(version: version)
79
+ end
80
+ end
81
+
82
+ [:bash, :csh, :perl, :python, :ruby, :script].each do |type|
83
+ matcher_name = "execute_#{type}_script".to_sym
84
+ define_method(matcher_name) do |name|
85
+ deprecated "The `#{matcher_name}` matcher is deprecated." \
86
+ " Please use `run_#{type}(#{name.inspect})` instead."
87
+ ChefSpec::Matchers::ResourceMatcher.new(type, :run, name)
88
+ end
89
+ end
90
+
91
+ def log(message)
92
+ deprecated "The `log` matcher is deprcated. Please use" \
93
+ " `write_log(#{message.inspect}) instead."
94
+ ChefSpec::Matchers::ResourceMatcher.new(:log, :write, message)
95
+ end
96
+
97
+ def set_service_to_start_on_boot(service)
98
+ deprecated "The `set_service_to_start_on_boot` matcher is" \
99
+ " deprecated. Please use `enable_service(#{service.inspect})`" \
100
+ " instead."
101
+ ChefSpec::Matchers::ResourceMatcher.new(:service, :enable, service)
102
+ end
103
+
104
+ def set_service_to_not_start_on_boot(service)
105
+ deprecated "The `set_service_to_not_start_on_boot` matcher is" \
106
+ " deprecated. Please use `enable_service(#{service.inspect})`" \
107
+ " with a negating argument instead."
108
+ raise ChefSpec::NoConversionError.new('set_service_to_start_on_boot')
109
+ end
110
+
111
+ def execute_ruby_block(name)
112
+ deprecated "The `execute_ruby_block` matcher is deprecated. Please" \
113
+ " use `run_ruby_block(#{name.inspect})` instead."
114
+ ChefSpec::Matchers::ResourceMatcher.new(:ruby_block, :run, name)
115
+ end
116
+
117
+ def execute_command(command)
118
+ deprecated "The `execute_command` matcher is deprecated. Please" \
119
+ " use `run_execute(#{command.inspect})` instead."
120
+ ChefSpec::Matchers::ResourceMatcher.new(:execute, :run, command)
121
+ end
122
+ end
123
+ end
124
+
125
+ module ChefSpec::API
126
+ module NotificationsMatchers
127
+ alias_method :new_notify, :notify
128
+ def notify(resource, action = nil)
129
+ if action
130
+ deprecated "The `notify` matcher arity has changed. Please use" \
131
+ " `notify(#{resource.inspect}).to(#{action.to_sym.inspect})`" \
132
+ " instead."
133
+ new_notify(resource).to(action.to_sym)
134
+ else
135
+ new_notify(resource)
136
+ end
137
+ end
138
+ end
139
+ end
140
+
141
+ module ChefSpec
142
+ class NoConversionError < Error
143
+ def initialize(matcher)
144
+ message = "I cannot convert `#{matcher}` to use a new matcher format!" \
145
+ " Please see the ChefSpec documentation and CHANGELOG for details" \
146
+ " on converting this matcher. Sorry :("
147
+
148
+ super(message)
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,99 @@
1
+ module ChefSpec
2
+ class Error < StandardError; end
3
+
4
+ class CommandNotStubbedError < Error
5
+ def initialize(command)
6
+ message = "Real commands are disabled. Unregistered command: `#{command}`"
7
+ message << "\n\n"
8
+ message << "You can stub this command with:"
9
+ message << "\n\n"
10
+ message << " #{Stubs::CommandStub.new(command).and_return(true).signature}"
11
+
12
+ unless Stubs::CommandRegistry.stubs.empty?
13
+ message << "\n\n"
14
+ message << "registered command stubs:"
15
+ message << "\n"
16
+
17
+ Stubs::CommandRegistry.stubs.each do |command|
18
+ message << "\n #{command.signature}"
19
+ end
20
+ end
21
+
22
+ message << "\n\n"
23
+ message << "="*60
24
+ super(message)
25
+ end
26
+ end
27
+
28
+ class SearchNotStubbedError < Error
29
+ def initialize(type, query)
30
+ message = "Real searches are disabled. Unregistered search: search(#{type.inspect}, #{query.inspect})"
31
+ message << "\n\n"
32
+ message << "You can stub this search with:"
33
+ message << "\n\n"
34
+ message << " #{Stubs::SearchStub.new(type, query).and_return({}).signature}"
35
+
36
+ unless Stubs::SearchRegistry.stubs.empty?
37
+ message << "\n\n"
38
+ message << "registered search stubs:"
39
+ message << "\n"
40
+
41
+ Stubs::SearchRegistry.stubs.each do |search|
42
+ message << "\n #{search.signature}"
43
+ end
44
+ end
45
+
46
+ message << "\n\n"
47
+ message << "="*60
48
+ super(message)
49
+ end
50
+ end
51
+
52
+ class DataBagNotStubbedError < Error
53
+ def initialize(bag)
54
+ message = "Real data_bags are disabled. Unregistered data_bag: data_bag(#{bag.inspect})"
55
+ message << "\n\n"
56
+ message << "You can stub this data_bag with:"
57
+ message << "\n\n"
58
+ message << " #{Stubs::DataBagStub.new(bag).and_return({}).signature}"
59
+
60
+ unless Stubs::DataBagRegistry.stubs.empty?
61
+ message << "\n\n"
62
+ message << "registered data_bag stubs:"
63
+ message << "\n"
64
+
65
+ Stubs::DataBagRegistry.stubs.each do |data_bag|
66
+ message << "\n #{data_bag.signature}"
67
+ end
68
+ end
69
+
70
+ message << "\n\n"
71
+ message << "="*60
72
+ super(message)
73
+ end
74
+ end
75
+
76
+ class DataBagItemNotStubbedError < Error
77
+ def initialize(bag, id)
78
+ message = "Real data_bag_items are disabled. Unregistered search: data_bag_item(#{bag.inspect}, #{id.inspect})"
79
+ message << "\n\n"
80
+ message << "You can stub this data_bag_item with:"
81
+ message << "\n\n"
82
+ message << " #{Stubs::DataBagItemStub.new(bag, id).and_return({}).signature}"
83
+
84
+ unless Stubs::DataBagItemRegistry.stubs.empty?
85
+ message << "\n\n"
86
+ message << "registered data_bag_item stubs:"
87
+ message << "\n"
88
+
89
+ Stubs::DataBagItemRegistry.stubs.each do |data_bag_item|
90
+ message << "\n #{data_bag_item.signature}"
91
+ end
92
+ end
93
+
94
+ message << "\n\n"
95
+ message << "="*60
96
+ super(message)
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,45 @@
1
+ class RSpec::Matchers::BuiltIn::RaiseError
2
+ class << self
3
+ attr_accessor :last_run
4
+ end
5
+
6
+ attr_reader :expected_error, :expected_message
7
+
8
+ alias_method :old_matches?, :matches?
9
+ def matches?(*args)
10
+ self.class.last_run = self
11
+ old_matches?(*args)
12
+ end
13
+ end
14
+
15
+ module ChefSpec
16
+ class ExpectException
17
+ def initialize(formatter_exception, formatter_message = nil)
18
+ @formatter_exception = formatter_exception
19
+ @formatter_message = formatter_message
20
+ @matcher = RSpec::Matchers::BuiltIn::RaiseError.last_run
21
+ end
22
+
23
+ def expected?
24
+ return false if @matcher.nil?
25
+ exception_matched? && message_matched?
26
+ end
27
+
28
+ private
29
+ def exception_matched?
30
+ @formatter_exception == @matcher.expected_error ||
31
+ @matcher.expected_error === @formatter_exception
32
+ end
33
+
34
+ def message_matched?
35
+ case @formatter_message
36
+ when nil
37
+ true
38
+ when Regexp
39
+ @matcher.expected_message =~ @formatter_message
40
+ else
41
+ @matcher.expected_message == @formatter_message
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,15 @@
1
+ require 'chef/client'
2
+
3
+ # @private
4
+ class Chef::Client
5
+ attr_reader :events
6
+
7
+ #
8
+ # Don't actually run ohai (we have fake data for that)
9
+ #
10
+ # @see Chef::Client#run_ohai
11
+ #
12
+ def run_ohai
13
+ # noop
14
+ end
15
+ end
@@ -0,0 +1,11 @@
1
+ require 'chef/resource/conditional'
2
+
3
+ class Chef::Resource::Conditional
4
+ # @see Chef::Resource::Conditional#evaluate_command
5
+ def evaluate_command
6
+ stub = ChefSpec::Stubs::CommandRegistry.stub_for(@command)
7
+ raise ChefSpec::CommandNotStubbedError.new(@command) if stub.nil?
8
+
9
+ stub.result
10
+ end
11
+ end
@@ -0,0 +1,29 @@
1
+ require 'chef/dsl/data_query'
2
+
3
+ module Chef::DSL::DataQuery
4
+ # @see Chef::DSL::DataQuery#search
5
+ def search(*args, &block)
6
+ type = args[0]
7
+ query = args[1] || '*:*'
8
+ stub = ChefSpec::Stubs::SearchRegistry.stub_for(type, query)
9
+ raise ChefSpec::SearchNotStubbedError.new(type, query) if stub.nil?
10
+
11
+ stub.result
12
+ end
13
+
14
+ # @see Chef::DSL::DataQuery#data_bag
15
+ def data_bag(bag)
16
+ stub = ChefSpec::Stubs::DataBagRegistry.stub_for(bag)
17
+ raise ChefSpec::DataBagNotStubbedError.new(bag) if stub.nil?
18
+
19
+ stub.result
20
+ end
21
+
22
+ # @see Chef::DSL::DataQuery#data_bag_item
23
+ def data_bag_item(bag, id)
24
+ stub = ChefSpec::Stubs::DataBagItemRegistry.stub_for(bag, id)
25
+ raise ChefSpec::DataBagItemNotStubbedError.new(bag, id) if stub.nil?
26
+
27
+ stub.result
28
+ end
29
+ end
@@ -0,0 +1,44 @@
1
+ # Override Chef LWRP creation to remove existing class to avoid redefinition warnings.
2
+ class Chef
3
+ class Provider
4
+ # Chef provider for a resource
5
+ class LWRPBase < Provider
6
+ class << self
7
+ alias_method :old_build_from_file, :build_from_file
8
+
9
+ #
10
+ # Override Opscode provider to remove any existing LWRP
11
+ #
12
+ # @param [String] cookbook_name
13
+ # The name of the cookbook
14
+ # @param [String] filename
15
+ # File to load as a LWRP
16
+ # @param [Chef::RunContext] run_context
17
+ # Context of a Chef Run
18
+ #
19
+ # @return [Chef::Provider]
20
+ #
21
+ def build_from_file(*args)
22
+ cookbook_name, filename = args[0,2]
23
+ remove_existing_lwrp(convert_to_class_name(filename_to_qualified_string(cookbook_name, filename)))
24
+ old_build_from_file(*args)
25
+ end
26
+
27
+ #
28
+ # Remove any existing Chef provider or resource with the specified name.
29
+ #
30
+ # @param [String] class_name
31
+ # The class name. Must be a valid constant name.
32
+ #
33
+ def remove_existing_lwrp(class_name)
34
+ [Chef::Resource::LWRPBase, Chef::Provider::LWRPBase, Chef::Resource, Chef::Provider].each do |resource_holder|
35
+ if resource_holder.const_defined? class_name, false
36
+ resource_holder.send(:remove_const, class_name)
37
+ end
38
+ end
39
+ end
40
+
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,27 @@
1
+ require 'chef/resource'
2
+
3
+ class Chef::Resource
4
+ alias :old_run_action :run_action unless method_defined?(:old_run_action)
5
+
6
+ #
7
+ # Pretend to run resource actions, adding them to the resource collection
8
+ # to prevent actually executing resources
9
+ #
10
+ # @see Chef::Resource#run_action
11
+ #
12
+ def run_action(action, notification_type = nil, notifying_resource = nil)
13
+ return if should_skip?(action)
14
+
15
+ if node.runner.step_into.include?(self.resource_name.to_s)
16
+ instance_eval { @not_if = []; @only_if = [] }
17
+ old_run_action(action, notification_type, notifying_resource)
18
+ end
19
+
20
+ Chef::Log.info("Processing #{self} action #{action} (#{defined_at})")
21
+
22
+ # Append the currently run action to the existing resource actions,
23
+ # making sure it's a unique array of symbols.
24
+ @action = [self.action, action].flatten.compact.map(&:to_sym).uniq
25
+ node.runner.resources[self.to_s] ||= self
26
+ end
27
+ end