summaryrefslogtreecommitdiff
path: root/lib/chef/guard_interpreter/resource_guard_interpreter.rb
blob: 63702f32491c9c072759c7d576b20dd0db70207e (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
#
# Author:: Adam Edwards (<adamed@chef.io>)
# Copyright:: Copyright 2014-2018, Chef Software 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_relative "../guard_interpreter"

class Chef
  class GuardInterpreter
    class ResourceGuardInterpreter < DefaultGuardInterpreter

      def initialize(parent_resource, command, opts)
        super(command, opts)
        @parent_resource = parent_resource
        @resource = get_interpreter_resource(parent_resource)
      end

      def evaluate
        # Add attributes inherited from the parent class
        # to the resource
        merge_inherited_attributes

        # Only execute and script resources and use guard attributes.
        # The command to be executed on them are passed via different attributes.
        # Script resources use code attribute and execute resources use
        # command property. Moreover script resources are also execute
        # resources. Here we make sure @command is assigned to the right
        # attribute by checking the type of the resources.
        # We need to make sure we check for Script first because any resource
        # that can get to here is an Execute resource.
        if @resource.is_a? Chef::Resource::Script
          block_attributes = @command_opts.merge({ code: @command })
        else
          block_attributes = @command_opts.merge({ command: @command })
        end

        # Handles cases like powershell_script where default
        # attributes are different when used in a guard vs. not. For
        # powershell_script in particular, this will go away when
        # the one attribute that causes this changes its default to be
        # the same after some period to prepare for deprecation
        if @resource.class.respond_to?(:get_default_attributes)
          block_attributes = @resource.class.send(:get_default_attributes, @command_opts).merge(block_attributes)
        end

        resource_block = block_from_attributes(block_attributes)
        evaluate_action(nil, &resource_block)
      end

      protected

      def evaluate_action(action = nil, &block)
        @resource.instance_eval(&block)

        run_action = action || @resource.action

        begin
          # Coerce to an array to be safe. This could happen with a legacy
          # resource or something overriding the default_action code in a
          # subclass.
          Array(run_action).each { |action_to_run| @resource.run_action(action_to_run) }
          resource_updated = @resource.updated
        rescue Mixlib::ShellOut::ShellCommandFailed
          resource_updated = nil
        end

        resource_updated
      end

      def get_interpreter_resource(parent_resource)
        if parent_resource.nil? || parent_resource.node.nil?
          raise ArgumentError, "Node for guard resource parent must not be nil"
        end

        resource_class = Chef::Resource.resource_for_node(parent_resource.guard_interpreter, parent_resource.node)

        if resource_class.nil?
          raise ArgumentError, "Specified guard_interpreter resource #{parent_resource.guard_interpreter} unknown for this platform"
        end

        unless resource_class.ancestors.include?(Chef::Resource::Execute)
          raise ArgumentError, "Specified guard interpreter class #{resource_class} must be a kind of Chef::Resource::Execute resource"
        end

        # Duplicate the node below because the new RunContext
        # overwrites the state of Node instances passed to it.
        # See https://github.com/chef/chef/issues/3485.
        empty_events = Chef::EventDispatch::Dispatcher.new
        anonymous_run_context = Chef::RunContext.new(parent_resource.node.dup, {}, empty_events)
        interpreter_resource = resource_class.new("Guard resource", anonymous_run_context)
        interpreter_resource.is_guard_interpreter = true

        interpreter_resource
      end

      def block_from_attributes(attributes)
        Proc.new do
          attributes.each_key do |attribute_name|
            send(attribute_name, attributes[attribute_name]) if respond_to?(attribute_name)
          end
        end
      end

      def merge_inherited_attributes
        inherited_attributes = []

        if @parent_resource.class.respond_to?(:guard_inherited_attributes)
          inherited_attributes = @parent_resource.class.send(:guard_inherited_attributes)
        end

        if inherited_attributes && !inherited_attributes.empty?
          inherited_attributes.each do |attribute|
            if @parent_resource.respond_to?(attribute) && @resource.respond_to?(attribute)
              parent_value = @parent_resource.send(attribute)
              child_value = @resource.send(attribute)
              if parent_value || child_value
                @resource.send(attribute, parent_value)
              end
            end
          end
        end
      end
    end
  end
end