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 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197
|
#--
# Author:: Daniel DeLeo (<dan@chef.io>)
# Copyright:: Copyright (c) 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 "../shellout"
require "chef-utils" unless defined?(ChefUtils)
require "chef-utils/dsl/default_paths"
require "chef-utils/internal"
module Mixlib
class ShellOut
module Helper
include ChefUtils::Internal
include ChefUtils::DSL::DefaultPaths
#
# These APIs are considered public for use in ohai and chef (by cookbooks and plugins, etc)
# but are considered private/experimental for now for the direct users of mixlib-shellout.
#
# You can see an example of how to handle the "dependency injection" in the rspec unit test.
# That backend API is left deliberately undocumented for now and may not follow SemVer and may
# break at any time (at least for the rest of 2020).
#
def shell_out(*args, **options)
options = options.dup
options = __maybe_add_timeout(self, options)
if options.empty?
shell_out_compacted(*__clean_array(*args))
else
shell_out_compacted(*__clean_array(*args), **options)
end
end
def shell_out!(*args, **options)
options = options.dup
options = __maybe_add_timeout(self, options)
if options.empty?
shell_out_compacted!(*__clean_array(*args))
else
shell_out_compacted!(*__clean_array(*args), **options)
end
end
private
# helper sugar for resources that support passing timeouts to shell_out
#
# module method to not pollute namespaces, but that means we need self injected as an arg
# @api private
def __maybe_add_timeout(obj, options)
options = options.dup
# historically resources have not properly declared defaults on their timeouts, so a default default of 900s was enforced here
default_val = 900
return options if options.key?(:timeout)
# FIXME: need to nuke descendent tracker out of Chef::Provider so we can just define that class here without requiring the
# world, and then just use symbol lookup
if obj.class.ancestors.map(&:name).include?("Chef::Provider") && obj.respond_to?(:new_resource) && obj.new_resource.respond_to?(:timeout) && !options.key?(:timeout)
options[:timeout] = obj.new_resource.timeout ? obj.new_resource.timeout.to_f : default_val
end
options
end
# helper function to mangle options when `default_env` is true
#
# @api private
def __apply_default_env(options)
options = options.dup
default_env = options.delete(:default_env)
default_env = true if default_env.nil?
if default_env
env_key = options.key?(:env) ? :env : :environment
options[env_key] = {
"LC_ALL" => __config[:internal_locale],
"LANGUAGE" => __config[:internal_locale],
"LANG" => __config[:internal_locale],
__env_path_name => default_paths,
}.update(options[env_key] || {})
end
options
end
# The shell_out_compacted/shell_out_compacted! APIs are private but are intended for use
# in rspec tests. They should always be used in rspec tests instead of shell_out to allow
# for less brittle rspec tests.
#
# This expectation:
#
# allow(provider).to receive(:shell_out_compacted!).with("foo", "bar", "baz")
#
# Is met by many different possible calling conventions that mean the same thing:
#
# provider.shell_out!("foo", [ "bar", nil, "baz"])
# provider.shell_out!(["foo", nil, "bar" ], ["baz"])
#
# Note that when setting `default_env: false` that you should just setup an expectation on
# :shell_out_compacted for `default_env: false`, rather than the expanded env settings so
# that the default_env implementation can change without breaking unit tests.
#
def shell_out_compacted(*args, **options)
options = __apply_default_env(options)
if options.empty?
__shell_out_command(*args)
else
__shell_out_command(*args, **options)
end
end
def shell_out_compacted!(*args, **options)
options = __apply_default_env(options)
cmd = if options.empty?
__shell_out_command(*args)
else
__shell_out_command(*args, **options)
end
cmd.error!
cmd
end
# Helper for subclasses to reject nil out of an array. It allows using the array form of
# shell_out (which avoids the need to surround arguments with quote marks to deal with shells).
#
# @param args [String] variable number of string arguments
# @return [Array] array of strings with nil and null string rejection
#
def __clean_array(*args)
args.flatten.compact.map(&:to_s)
end
def __shell_out_command(*args, **options)
if __transport_connection
FakeShellOut.new(args, options, __transport_connection.run_command(args.join(" "))) # FIXME: train should accept run_command(*args)
else
cmd = if options.empty?
Mixlib::ShellOut.new(*args)
else
Mixlib::ShellOut.new(*args, **options)
end
cmd.live_stream ||= __io_for_live_stream
cmd.run_command
cmd
end
end
def __io_for_live_stream
if !STDOUT.closed? && __log.trace?
STDOUT
else
nil
end
end
def __env_path_name
if ChefUtils.windows?
"Path"
else
"PATH"
end
end
class FakeShellOut
attr_reader :stdout, :stderr, :exitstatus, :status
def initialize(args, options, result)
@args = args
@options = options
@stdout = result.stdout
@stderr = result.stderr
@exitstatus = result.exit_status
@status = OpenStruct.new(success?: ( exitstatus == 0 ))
end
def error?
exitstatus != 0
end
def error!
raise Mixlib::ShellOut::ShellCommandFailed, "Unexpected exit status of #{exitstatus} running #{@args}" if error?
end
end
end
end
end
|