summaryrefslogtreecommitdiff
path: root/lib/chef/provider/execute.rb
blob: 5494405a02ab097dedf3ccdc86b8d5790170da7b (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
#
# Author:: Adam Jacob (<adam@chef.io>)
# Copyright:: Copyright 2008-2016, 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 "chef/log"
require "chef/provider"
require "forwardable"
require "chef/mixin/user_identity"

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

      include Chef::Mixin::UserIdentity

      provides :execute

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

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

      def whyrun_supported?
        true
      end

      def define_resource_requirements
        # @todo: this should change to raise in some appropriate major version bump.
        requirements.assert(:all_actions) do |a|
          a.assertion { validate_identity(new_resource.user, new_resource.password, new_resource.domain) }
        end

        if creates && creates_relative? && !cwd
          Chef::Log.warn "Providing a relative path for the creates attribute without the cwd is deprecated and will be changed to fail in the future (CHEF-3819)"
        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
        # parse username if it's in the following format: domain/username or username@domain
        identity = qualify_user(new_resource.user, new_resource.domain)
        new_resource.user identity[:user]
        new_resource.domain identity[:domain]

        if creates && sentinel_file.exist?
          Chef::Log.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?
              raise Mixlib::ShellOut::ShellCommandFailed,
                "Command execution failed. STDOUT/STDERR suppressed for sensitive resource"
            else
              raise
            end
          end
          Chef::Log.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[:log_level]   = :info
        opts[:log_tag]     = new_resource.to_s
        if (Chef::Log.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
      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