mixlib-shellout 3.1.1 → 3.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3e6f0d990000b229cfa3d812040828440973f528374790b2eb261c28db29bc13
4
- data.tar.gz: 23ff6a2702bcb423b56daa92a23938299f541f1724dfb5599059dd65aa4e957c
3
+ metadata.gz: 82102f99cbf59a6ec48f6b7685e470ecd94bc629e27ae83b36a3eaa7e02ace33
4
+ data.tar.gz: 83d3a8aeb7bf139a209440bc8877b63f524b7d30c783b99af2c7406d237618ef
5
5
  SHA512:
6
- metadata.gz: 6c76cc403ee32e7dae114f34e7684b3aa530eacfde72eb160ab0c007de6dbf3d8497f651d2894c873b6f3e734918de922ad57632965ada3fb1ef67418efb6d31
7
- data.tar.gz: cad33035223cdd99827846282a9e9a201e63a6f99611f25430caaa6ddf72fd3da21f18e97840d77c70d1af9abd2da65bfeb732a20aa41b1f039ae494c8968b44
6
+ metadata.gz: 201ec59f8e4dd8f20443e9d78f880037cd65f91a890321647d95347d29bf478968554548efc311d2a1da82ae3a7c00220cb27021c13d21c8b2ecc28bdfbd20cc
7
+ data.tar.gz: 3c1caed9698c8820fb70b2245b134e303fcb55343323ea1c51350d625ceeee5f9c3ec8a68d5f81355aafad3c30d89a244316576b674a13eb922f7b0d8fb10948
@@ -16,8 +16,8 @@
16
16
  # limitations under the License.
17
17
  #
18
18
 
19
- require "etc"
20
- require "tmpdir"
19
+ require "etc" unless defined?(Etc)
20
+ require "tmpdir" unless defined?(Dir.mktmpdir)
21
21
  require "fcntl"
22
22
  require_relative "shellout/exceptions"
23
23
 
@@ -122,7 +122,7 @@ module Mixlib
122
122
  # === Options:
123
123
  # If the last argument is a Hash, it is removed from the list of args passed
124
124
  # to exec and used as an options hash. The following options are available:
125
- # * +user+: the user the commmand should run as. if an integer is given, it is
125
+ # * +user+: the user the command should run as. if an integer is given, it is
126
126
  # used as a uid. A string is treated as a username and resolved to a uid
127
127
  # with Etc.getpwnam
128
128
  # * +group+: the group the command should run as. works similarly to +user+
@@ -16,33 +16,23 @@
16
16
  # limitations under the License.
17
17
 
18
18
  require_relative "../shellout"
19
- require "chef-utils"
20
- require "chef-utils/dsl/path_sanity"
19
+ require "chef-utils" unless defined?(ChefUtils)
20
+ require "chef-utils/dsl/default_paths"
21
21
  require "chef-utils/internal"
22
22
 
23
23
  module Mixlib
24
24
  class ShellOut
25
25
  module Helper
26
26
  include ChefUtils::Internal
27
- include ChefUtils::DSL::PathSanity
27
+ include ChefUtils::DSL::DefaultPaths
28
28
 
29
- # PREFERRED APIS:
30
29
  #
31
- # all consumers should now call shell_out!/shell_out.
30
+ # These APIs are considered public for use in ohai and chef (by cookbooks and plugins, etc)
31
+ # but are considered private/experimental for now for the direct users of mixlib-shellout.
32
32
  #
33
- # the shell_out_compacted/shell_out_compacted! APIs are private but are intended for use
34
- # in rspec tests, and should ideally always be used to make code refactoring that do not
35
- # change behavior easier:
36
- #
37
- # allow(provider).to receive(:shell_out_compacted!).with("foo", "bar", "baz")
38
- # provider.shell_out!("foo", [ "bar", nil, "baz"])
39
- # provider.shell_out!(["foo", nil, "bar" ], ["baz"])
40
- #
41
- # note that shell_out_compacted also includes adding the magical timeout option to force
42
- # people to setup expectations on that value explicitly. it does not include the default_env
43
- # mangling in order to avoid users having to setup an expectation on anything other than
44
- # setting `default_env: false` and allow us to make tweak to the default_env without breaking
45
- # a thousand unit tests.
33
+ # You can see an example of how to handle the "dependency injection" in the rspec unit test.
34
+ # That backend API is left deliberately undocumented for now and may not follow SemVer and may
35
+ # break at any time (at least for the rest of 2020).
46
36
  #
47
37
 
48
38
  def shell_out(*args, **options)
@@ -98,15 +88,28 @@ module Mixlib
98
88
  "LC_ALL" => __config[:internal_locale],
99
89
  "LANGUAGE" => __config[:internal_locale],
100
90
  "LANG" => __config[:internal_locale],
101
- __env_path_name => sanitized_path,
91
+ __env_path_name => default_paths,
102
92
  }.update(options[env_key] || {})
103
93
  end
104
94
  options
105
95
  end
106
96
 
107
- # this SHOULD be used for setting up expectations in rspec, see banner comment at top.
97
+ # The shell_out_compacted/shell_out_compacted! APIs are private but are intended for use
98
+ # in rspec tests. They should always be used in rspec tests instead of shell_out to allow
99
+ # for less brittle rspec tests.
108
100
  #
109
- # the private constraint is meant to avoid code calling this directly, rspec expectations are fine.
101
+ # This expectation:
102
+ #
103
+ # allow(provider).to receive(:shell_out_compacted!).with("foo", "bar", "baz")
104
+ #
105
+ # Is met by many different possible calling conventions that mean the same thing:
106
+ #
107
+ # provider.shell_out!("foo", [ "bar", nil, "baz"])
108
+ # provider.shell_out!(["foo", nil, "bar" ], ["baz"])
109
+ #
110
+ # Note that when setting `default_env: false` that you should just setup an expectation on
111
+ # :shell_out_compacted for `default_env: false`, rather than the expanded env settings so
112
+ # that the default_env implementation can change without breaking unit tests.
110
113
  #
111
114
  def shell_out_compacted(*args, **options)
112
115
  options = __apply_default_env(options)
@@ -117,10 +120,6 @@ module Mixlib
117
120
  end
118
121
  end
119
122
 
120
- # this SHOULD be used for setting up expectations in rspec, see banner comment at top.
121
- #
122
- # the private constraint is meant to avoid code calling this directly, rspec expectations are fine.
123
- #
124
123
  def shell_out_compacted!(*args, **options)
125
124
  options = __apply_default_env(options)
126
125
  cmd = if options.empty?
@@ -132,23 +131,12 @@ module Mixlib
132
131
  cmd
133
132
  end
134
133
 
135
- # Helper for subclasses to reject nil out of an array. It allows
136
- # using the array form of shell_out (which avoids the need to surround arguments with
137
- # quote marks to deal with shells).
138
- #
139
- # Usage:
140
- # shell_out!(*clean_array("useradd", universal_options, useradd_options, new_resource.username))
141
- #
142
- # universal_options and useradd_options can be nil, empty array, empty string, strings or arrays
143
- # and the result makes sense.
144
- #
145
- # keeping this separate from shell_out!() makes it a bit easier to write expectations against the
146
- # shell_out args and be able to omit nils and such in the tests (and to test that the nils are
147
- # being rejected correctly).
134
+ # Helper for subclasses to reject nil out of an array. It allows using the array form of
135
+ # shell_out (which avoids the need to surround arguments with quote marks to deal with shells).
148
136
  #
149
137
  # @param args [String] variable number of string arguments
150
138
  # @return [Array] array of strings with nil and null string rejection
151
-
139
+ #
152
140
  def __clean_array(*args)
153
141
  args.flatten.compact.map(&:to_s)
154
142
  end
@@ -1,5 +1,5 @@
1
1
  module Mixlib
2
2
  class ShellOut
3
- VERSION = "3.1.1".freeze
3
+ VERSION = "3.2.0".freeze
4
4
  end
5
5
  end
@@ -208,7 +208,7 @@ module Mixlib
208
208
  # 4. if the argument must be quoted by #1 and terminates in a sequence of backslashes then all the backlashes must themselves
209
209
  # be backslash excaped (double the backslashes).
210
210
  # 5. if an interior quote that must be escaped by #2 has a sequence of backslashes before it then all the backslashes must
211
- # themselves be backslash excaped along with the backslash ecape of the interior quote (double plus one backslashes).
211
+ # themselves be backslash excaped along with the backslash escape of the interior quote (double plus one backslashes).
212
212
  #
213
213
  # And to restate. We are constructing a string which will be parsed by the windows parser into arguments, and we want those
214
214
  # arguments to match the *args array we are passed here. So call the windows parser operation A then we need to apply A^-1 to
@@ -18,6 +18,7 @@
18
18
  #
19
19
 
20
20
  require "win32/process"
21
+ require "ffi/win32/extensions"
21
22
 
22
23
  # Add new constants for Logon
23
24
  module Process::Constants
@@ -45,6 +46,8 @@ module Process::Constants
45
46
  WIN32_PROFILETYPE_PT_MANDATORY = 0x04
46
47
  WIN32_PROFILETYPE_PT_ROAMING_PREEXISTING = 0x08
47
48
 
49
+ # The environment block list ends with two nulls (\0\0).
50
+ ENVIRONMENT_BLOCK_ENDS = "\0\0".freeze
48
51
  end
49
52
 
50
53
  # Structs required for data handling
@@ -78,6 +81,12 @@ module Process::Functions
78
81
  attach_pfunc :UnloadUserProfile,
79
82
  %i{handle handle}, :bool
80
83
 
84
+ attach_pfunc :CreateEnvironmentBlock,
85
+ %i{pointer ulong bool}, :bool
86
+
87
+ attach_pfunc :DestroyEnvironmentBlock,
88
+ %i{pointer}, :bool
89
+
81
90
  ffi_lib :advapi32
82
91
 
83
92
  attach_pfunc :LogonUserW,
@@ -172,9 +181,25 @@ module Process
172
181
 
173
182
  env = nil
174
183
 
184
+ # Retrieve the environment variables for the specified user.
185
+ if hash["with_logon"]
186
+ logon, passwd, domain = format_creds_from_hash(hash)
187
+ logon_type = hash["elevated"] ? LOGON32_LOGON_BATCH : LOGON32_LOGON_INTERACTIVE
188
+ token = logon_user(logon, domain, passwd, logon_type)
189
+ logon_ptr = FFI::MemoryPointer.from_string(logon)
190
+ profile = PROFILEINFO.new.tap do |dat|
191
+ dat[:dwSize] = dat.size
192
+ dat[:dwFlags] = 1
193
+ dat[:lpUserName] = logon_ptr
194
+ end
195
+
196
+ load_user_profile(token, profile.pointer)
197
+ env_list = retrieve_environment_variables(token)
198
+ end
199
+
175
200
  # The env string should be passed as a string of ';' separated paths.
176
201
  if hash["environment"]
177
- env = hash["environment"]
202
+ env = env_list.nil? ? hash["environment"] : merge_env_variables(env_list, hash["environment"])
178
203
 
179
204
  unless env.respond_to?(:join)
180
205
  env = hash["environment"].split(File::PATH_SEPARATOR)
@@ -396,6 +421,33 @@ module Process
396
421
  true
397
422
  end
398
423
 
424
+ # Retrieves the environment variables for the specified user.
425
+ #
426
+ # @param env_pointer [Pointer] The environment block is an array of null-terminated Unicode strings.
427
+ # @param token [Integer] User token handle.
428
+ # @return [Boolean] true if successfully retrieves the environment variables for the specified user.
429
+ #
430
+ def create_environment_block(env_pointer, token)
431
+ unless CreateEnvironmentBlock(env_pointer, token, false)
432
+ raise SystemCallError.new("CreateEnvironmentBlock", FFI.errno)
433
+ end
434
+
435
+ true
436
+ end
437
+
438
+ # Frees environment variables created by the CreateEnvironmentBlock function.
439
+ #
440
+ # @param env_pointer [Pointer] The environment block is an array of null-terminated Unicode strings.
441
+ # @return [Boolean] true if successfully frees environment variables created by the CreateEnvironmentBlock function.
442
+ #
443
+ def destroy_environment_block(env_pointer)
444
+ unless DestroyEnvironmentBlock(env_pointer)
445
+ raise SystemCallError.new("DestroyEnvironmentBlock", FFI.errno)
446
+ end
447
+
448
+ true
449
+ end
450
+
399
451
  def create_process_as_user(token, app, cmd, process_security,
400
452
  thread_security, inherit, creation_flags, env, cwd, startinfo, procinfo)
401
453
 
@@ -530,5 +582,51 @@ module Process
530
582
  [ logon, passwd, domain ]
531
583
  end
532
584
 
585
+ # Retrieves the environment variables for the specified user.
586
+ #
587
+ # @param token [Integer] User token handle.
588
+ # @return env_list [Array<String>] Environment variables of specified user.
589
+ #
590
+ def retrieve_environment_variables(token)
591
+ env_list = []
592
+ env_pointer = FFI::MemoryPointer.new(:pointer)
593
+ create_environment_block(env_pointer, token)
594
+ str_ptr = env_pointer.read_pointer
595
+ offset = 0
596
+ loop do
597
+ new_str_pointer = str_ptr + offset
598
+ break if new_str_pointer.read_string(2) == ENVIRONMENT_BLOCK_ENDS
599
+
600
+ environment = new_str_pointer.read_wstring
601
+ env_list << environment
602
+ offset = offset + environment.length * 2 + 2
603
+ end
604
+
605
+ # To free the buffer when we have finished with the environment block
606
+ destroy_environment_block(str_ptr)
607
+ env_list
608
+ end
609
+
610
+ # Merge environment variables of specified user and current environment variables.
611
+ #
612
+ # @param fetched_env [Array<String>] environment variables of specified user.
613
+ # @param current_env [Array<String>] current environment variables.
614
+ # @return [Array<String>] Merged environment variables.
615
+ #
616
+ def merge_env_variables(fetched_env, current_env)
617
+ env_hash_1 = environment_list_to_hash(fetched_env)
618
+ env_hash_2 = environment_list_to_hash(current_env)
619
+ merged_env = env_hash_2.merge(env_hash_1)
620
+ merged_env.map { |k, v| "#{k}=#{v}" }
621
+ end
622
+
623
+ # Convert an array to a hash.
624
+ #
625
+ # @param env_var [Array<String>] Environment variables.
626
+ # @return [Hash] Converted an array to hash.
627
+ #
628
+ def environment_list_to_hash(env_var)
629
+ Hash[ env_var.map { |pair| pair.split("=", 2) } ]
630
+ end
533
631
  end
534
632
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mixlib-shellout
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.1.1
4
+ version: 3.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chef Software Inc.
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-07-17 00:00:00.000000000 Z
11
+ date: 2020-11-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: chef-utils