summaryrefslogtreecommitdiff
path: root/lib/chef/dsl/recipe.rb
blob: e0109f0bc2297db9b4efc43e3f21a79ff58c3c7e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
#--
# Author:: Adam Jacob (<adam@opscode.com>)
# Author:: Christopher Walters (<cw@opscode.com>)
# Copyright:: Copyright (c) 2008, 2009 Opscode, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

require 'chef/resource_platform_map'
require 'chef/mixin/convert_to_class_name'

class Chef
  module DSL

    # == Chef::DSL::Recipe
    # Provides the primary recipe DSL functionality for defining Chef resource
    # objects via method calls.
    module Recipe

      include Chef::Mixin::ShellOut
      include Chef::Mixin::ConvertToClassName

      def method_missing(method_symbol, *args, &block)
        # If we have a definition that matches, we want to use that instead.  This should
        # let you do some really crazy over-riding of "native" types, if you really want
        # to.
        if has_resource_definition?(method_symbol)
          evaluate_resource_definition(method_symbol, *args, &block)
        elsif have_resource_class_for?(method_symbol)
          # Otherwise, we're rocking the regular resource call route.
          declare_resource(method_symbol, args[0], caller[0], &block)
        else
          begin
            super
          rescue NoMethodError
            raise NoMethodError, "No resource or method named `#{method_symbol}' for #{describe_self_for_error}"
          rescue NameError
            raise NameError, "No resource, method, or local variable named `#{method_symbol}' for #{describe_self_for_error}"
          end
        end
      end

      def has_resource_definition?(name)
        yes_or_no = run_context.definitions.has_key?(name)

        yes_or_no
      end

      # Processes the arguments and block as a resource definition.
      def evaluate_resource_definition(definition_name, *args, &block)

        # This dupes the high level object, but we still need to dup the params
        new_def = run_context.definitions[definition_name].dup

        new_def.params = new_def.params.dup
        new_def.node = run_context.node
        # This sets up the parameter overrides
        new_def.instance_eval(&block) if block


        new_recipe = Chef::Recipe.new(cookbook_name, recipe_name, run_context)
        new_recipe.params = new_def.params
        new_recipe.params[:name] = args[0]
        new_recipe.instance_eval(&new_def.recipe)
        new_recipe
      end

      # Instantiates a resource (via #build_resource), then adds it to the
      # resource collection. Note that resource classes are looked up directly,
      # so this will create the resource you intended even if the method name
      # corresponding to that resource has been overridden.
      def declare_resource(type, name, created_at=nil, &resource_attrs_block)
        created_at ||= caller[0]

        resource = build_resource(type, name, created_at, &resource_attrs_block)

        # Some resources (freebsd_package) can be invoked with multiple names
        # (package || freebsd_package).
        # https://github.com/opscode/chef/issues/1773
        # For these resources we want to make sure
        # their key in resource collection is same as the name they are declared
        # as. Since this might be a breaking change for resources that define
        # customer to_s methods, we are working around the issue by letting
        # resources know of their created_as_type until this issue is fixed in
        # Chef 12:
        # https://github.com/opscode/chef/issues/1817
        if resource.respond_to?(:created_as_type=)
          resource.created_as_type = type
        end

        run_context.resource_collection.insert(resource)
        resource
      end

      # Instantiate a resource of the given +type+ with the given +name+ and
      # attributes as given in the +resource_attrs_block+.
      #
      # The resource is NOT added to the resource collection.
      def build_resource(type, name, created_at=nil, &resource_attrs_block)
        created_at ||= caller[0]

        # Checks the new platform => short_name => resource mapping initially
        # then fall back to the older approach (Chef::Resource.const_get) for
        # backward compatibility
        resource_class = resource_class_for(type)

        raise ArgumentError, "You must supply a name when declaring a #{type} resource" if name.nil?

        resource = resource_class.new(name, run_context)
        resource.source_line = created_at
        # If we have a resource like this one, we want to steal its state
        # This behavior is very counter-intuitive and should be removed.
        # See CHEF-3694, https://tickets.opscode.com/browse/CHEF-3694
        # Moved to this location to resolve CHEF-5052, https://tickets.opscode.com/browse/CHEF-5052
        resource.load_prior_resource
        resource.cookbook_name = cookbook_name
        resource.recipe_name = recipe_name
        # Determine whether this resource is being created in the context of an enclosing Provider
        resource.enclosing_provider = self.is_a?(Chef::Provider) ? self : nil

        # XXX: This is very crufty, but it's required for resource definitions
        # to work properly :(
        resource.params = @params

        # Evaluate resource attribute DSL
        resource.instance_eval(&resource_attrs_block) if block_given?

        # Run optional resource hook
        resource.after_created

        resource
      end

      def resource_class_for(snake_case_name)
        Chef::Resource.resource_for_node(snake_case_name, run_context.node)
      end

      def have_resource_class_for?(snake_case_name)
        not resource_class_for(snake_case_name).nil?
      rescue NameError
        false
      end

      def describe_self_for_error
        if respond_to?(:name)
          %Q[`#{self.class.name} "#{name}"']
        elsif respond_to?(:recipe_name)
          %Q[`#{self.class.name} "#{recipe_name}"']
        else
          to_s
        end
      end

      def exec(args)
        raise Chef::Exceptions::NoSuchResourceType.new "exec was called, but you probably meant to use an execute resource.  If not, please call Kernel#exec explicitly"
      end

    end
  end
end

# We require this at the BOTTOM of this file to avoid circular requires (it is used
# at runtime but not load time)
require 'chef/resource'

# **DEPRECATED**
# This used to be part of chef/mixin/recipe_definition_dsl_core. Load the file to activate the deprecation code.
require 'chef/mixin/recipe_definition_dsl_core'