#--# 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"unlessdefined?(ChefUtils)require"chef-utils/dsl/default_paths"require"chef-utils/internal"moduleMixlibclassShellOutmoduleHelperincludeChefUtils::InternalincludeChefUtils::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).#defshell_out(*args,**options)options=options.dupoptions=__maybe_add_timeout(self,options)ifoptions.empty?shell_out_compacted(*__clean_array(*args))elseshell_out_compacted(*__clean_array(*args),**options)endenddefshell_out!(*args,**options)options=options.dupoptions=__maybe_add_timeout(self,options)ifoptions.empty?shell_out_compacted!(*__clean_array(*args))elseshell_out_compacted!(*__clean_array(*args),**options)endendprivate# 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 privatedef__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 heredefault_val=900returnoptionsifoptions.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 lookupifobj.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_valendoptionsend# helper function to mangle options when `default_env` is true## @api privatedef__apply_default_env(options)options=options.dupdefault_env=options.delete(:default_env)default_env=trueifdefault_env.nil?ifdefault_envenv_key=options.key?(:env)?:env::environmentoptions[env_key]={"LC_ALL"=>__config[:internal_locale],"LANGUAGE"=>__config[:internal_locale],"LANG"=>__config[:internal_locale],__env_path_name=>default_paths,}.update(options[env_key]||{})endoptionsend# 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.#defshell_out_compacted(*args,**options)options=__apply_default_env(options)ifoptions.empty?__shell_out_command(*args)else__shell_out_command(*args,**options)endenddefshell_out_compacted!(*args,**options)options=__apply_default_env(options)cmd=ifoptions.empty?__shell_out_command(*args)else__shell_out_command(*args,**options)endcmd.error!cmdend# 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)enddef__shell_out_command(*args,**options)if__transport_connectionFakeShellOut.new(args,options,__transport_connection.run_command(args.join(" ")))# FIXME: train should accept run_command(*args)elsecmd=ifoptions.empty?Mixlib::ShellOut.new(*args)elseMixlib::ShellOut.new(*args,**options)endcmd.live_stream||=__io_for_live_streamcmd.run_commandcmdendenddef__io_for_live_streamif!STDOUT.closed?&&__log.trace?STDOUTelsenilendenddef__env_path_nameifChefUtils.windows?"Path"else"PATH"endendclassFakeShellOutattr_reader:stdout,:stderr,:exitstatus,:statusdefinitialize(args,options,result)@args=args@options=options@stdout=result.stdout@stderr=result.stderr@exitstatus=result.exit_status@status=OpenStruct.new(success?: (exitstatus==0))enddeferror?exitstatus!=0enddeferror!raiseMixlib::ShellOut::ShellCommandFailed,"Unexpected exit status of #{exitstatus} running #{@args}"iferror?endendendendend