summaryrefslogtreecommitdiff
path: root/lib/chef/provider/execute.rb
blob: 61107a84baadf2f2ca32cd04bd490ce71edbe4c6 (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
#
# Author:: Adam Jacob (<adam@chef.io>)
# Copyright:: Copyright 2008-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 "../log"
require_relative "../provider"
require "forwardable" unless defined?(Forwardable)

class Chef
  class Provider
    class Execute < Chef::Provider
      extend Forwardable

      provides :execute, target_mode: true

      def_delegators :new_resource, :command, :returns, :environment, :user, :domain, :password, :group, :cwd, :umask, :creates, :elevated, :default_env

      def load_current_resource
        current_resource = Chef::Resource::Execute.new(new_resource.name)
        current_resource
      end

      def define_resource_requirements
        if creates && creates_relative? && !cwd
          # FIXME? move this onto the resource?
          raise Chef::Exceptions::Execute, "Please either specify a full path for the creates property, or specify a cwd property to the #{new_resource} resource"
        end
      end

      def timeout
        # original implementation did not specify a timeout, but ShellOut
        # *always* times out. So, set a very long default timeout
        new_resource.timeout || 3600
      end

      def action_run
        if creates && sentinel_file.exist?
          logger.debug("#{new_resource} sentinel file #{sentinel_file} exists - nothing to do")
          return false
        end

        converge_by("execute #{description}") do
          begin
            shell_out!(command, opts)
          rescue Mixlib::ShellOut::ShellCommandFailed
            if sensitive?
              ex = Mixlib::ShellOut::ShellCommandFailed.new("Command execution failed. STDOUT/STDERR suppressed for sensitive resource")
              # Forcibly hide the exception cause chain here so we don't log the unredacted version
              def ex.cause
                nil
              end
              raise ex
            else
              raise
            end
          end
          logger.info("#{new_resource} ran successfully")
        end
      end

      private

      def sensitive?
        !!new_resource.sensitive
      end

      def live_stream?
        Chef::Config[:stream_execute_output] || !!new_resource.live_stream
      end

      def stream_to_stdout?
        STDOUT.tty? && !Chef::Config[:daemon]
      end

      def opts
        opts = {}
        opts[:timeout]     = timeout
        opts[:returns]     = returns if returns
        opts[:environment] = environment if environment
        opts[:user]        = user if user
        opts[:domain]      = domain if domain
        opts[:password]    = password if password
        opts[:group]       = group if group
        opts[:cwd]         = cwd if cwd
        opts[:umask]       = umask if umask
        opts[:default_env] = default_env
        opts[:log_level]   = :info
        opts[:log_tag]     = new_resource.to_s
        if (logger.info? || live_stream?) && !sensitive?
          if run_context.events.formatter?
            opts[:live_stream] = Chef::EventDispatch::EventsOutputStream.new(run_context.events, name: :execute)
          elsif stream_to_stdout?
            opts[:live_stream] = STDOUT
          end
        end
        opts[:elevated] = elevated if elevated
        opts
      end

      def description
        sensitive? ? "sensitive resource" : command
      end

      def creates_relative?
        Pathname(creates).relative?
      end

      def sentinel_file
        Pathname.new(Chef::Util::PathHelper.cleanpath(
          ( cwd && creates_relative? ) ? ::File.join(cwd, creates) : creates
        ))
      end

    end
  end
end