ruby-pwsh 0.5.1 → 0.7.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: 37a20d5c79da4ea0b065cb85afc8e99933128576ef73c759a9f8113b7f738eed
4
- data.tar.gz: 9da59c07a8037eee9bb03591726ed83e4909b95009579b362fb49a4d2e4ac171
3
+ metadata.gz: f8334c3715e69873cda019ad2010187d15ad34a2b593426b567d056a4a1fc364
4
+ data.tar.gz: e66f5fccc419fc4d315c7db3def945bb3108d57c756bf55e5205039d1963c6c3
5
5
  SHA512:
6
- metadata.gz: 760bbbdd326042ff79e4e0f28bbfe48447ddc17fb6968f569ce1c5d75848a83e3b3042022250fb309d98124d97580c9ad1a7a9a8a629aadbbdc57b7de7718f60
7
- data.tar.gz: 1c57dec6a7bd3db1e4b32adfd0e61abb996157317fa2f74f20110f246a3daf8f6302aa0569aef07e863299e9a8ba126631b89f9df3055f5b013a8c720ff308e0
6
+ metadata.gz: 2522c738352df33e552c0fa26b48113b95b65ceece2cc03bbb48d731df5e1fc623ec8590e18b8152205fb07ff33fc1f1f1892ccbc6e399a729a61732385a48c7
7
+ data.tar.gz: 43c5e11f365df85d1b45978bc3ae8855668bd97168ac24ef9d3bffd77b9e4a709b0eb71872d19b63c0225f2c08061dfa346d8e2c7fd52133afd283c14db54b2c
@@ -2,6 +2,58 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org).
4
4
 
5
+ ## [0.7.0](https://github.com/puppetlabs/ruby-pwsh/tree/0.7.0) (2021-01-20)
6
+
7
+ [Full Changelog](https://github.com/puppetlabs/ruby-pwsh/compare/0.6.3...0.7.0)
8
+
9
+ ### Added
10
+
11
+ - \(GH-75\) Including module name in vendored module path [\#85](https://github.com/puppetlabs/ruby-pwsh/pull/85) ([pmcmaw](https://github.com/pmcmaw))
12
+
13
+ ### Fixed
14
+
15
+ - Make root module path use puppetized module name [\#86](https://github.com/puppetlabs/ruby-pwsh/pull/86) ([michaeltlombardi](https://github.com/michaeltlombardi))
16
+
17
+ ## [0.6.3](https://github.com/puppetlabs/ruby-pwsh/tree/0.6.3) (2020-12-16)
18
+
19
+ [Full Changelog](https://github.com/puppetlabs/ruby-pwsh/compare/0.6.2...0.6.3)
20
+
21
+ ### Fixed
22
+
23
+ - \(MAINT\) Add handling for when dsc\_ensure is stripped [\#78](https://github.com/puppetlabs/ruby-pwsh/pull/78) ([michaeltlombardi](https://github.com/michaeltlombardi))
24
+
25
+ ## [0.6.2](https://github.com/puppetlabs/ruby-pwsh/tree/0.6.2) (2020-12-09)
26
+
27
+ [Full Changelog](https://github.com/puppetlabs/ruby-pwsh/compare/0.6.1...0.6.2)
28
+
29
+ ### Fixed
30
+
31
+ - \(MAINT\) Ensure parameters are canonicalized [\#75](https://github.com/puppetlabs/ruby-pwsh/pull/75) ([michaeltlombardi](https://github.com/michaeltlombardi))
32
+
33
+ ## [0.6.1](https://github.com/puppetlabs/ruby-pwsh/tree/0.6.1) (2020-11-25)
34
+
35
+ [Full Changelog](https://github.com/puppetlabs/ruby-pwsh/compare/0.6.0...0.6.1)
36
+
37
+ ### Fixed
38
+
39
+ - \(maint\) - Removal of inappropriate terminology [\#70](https://github.com/puppetlabs/ruby-pwsh/pull/70) ([pmcmaw](https://github.com/pmcmaw))
40
+ - \(Maint\) Fix ensurability in the dsc base provider [\#69](https://github.com/puppetlabs/ruby-pwsh/pull/69) ([michaeltlombardi](https://github.com/michaeltlombardi))
41
+
42
+ ## [0.6.0](https://github.com/puppetlabs/ruby-pwsh/tree/0.6.0) (2020-11-24)
43
+
44
+ [Full Changelog](https://github.com/puppetlabs/ruby-pwsh/compare/0.5.1...0.6.0)
45
+
46
+ ### Added
47
+
48
+ - \(GH-81\) Handle parameters in the dsc base provider [\#62](https://github.com/puppetlabs/ruby-pwsh/pull/62) ([michaeltlombardi](https://github.com/michaeltlombardi))
49
+ - \(GH-74\) Remove special handling for ensure in the dsc base provider [\#61](https://github.com/puppetlabs/ruby-pwsh/pull/61) ([michaeltlombardi](https://github.com/michaeltlombardi))
50
+ - \(GH-59\) Refactor away from Simple Provider [\#60](https://github.com/puppetlabs/ruby-pwsh/pull/60) ([michaeltlombardi](https://github.com/michaeltlombardi))
51
+
52
+ ### Fixed
53
+
54
+ - \(GH-57\) Handle datetimes in dsc [\#58](https://github.com/puppetlabs/ruby-pwsh/pull/58) ([michaeltlombardi](https://github.com/michaeltlombardi))
55
+ - \(GH-55\) Handle intentionally empty arrays [\#56](https://github.com/puppetlabs/ruby-pwsh/pull/56) ([michaeltlombardi](https://github.com/michaeltlombardi))
56
+
5
57
  ## [0.5.1](https://github.com/puppetlabs/ruby-pwsh/tree/0.5.1) (2020-09-25)
6
58
 
7
59
  [Full Changelog](https://github.com/puppetlabs/ruby-pwsh/compare/0.5.0...0.5.1)
@@ -79,7 +79,7 @@ process as easy as possible.
79
79
 
80
80
  2. Sending your patches
81
81
 
82
- To submit your changes via a GitHub pull request, we _highly_ recommend that you have them on a topic branch, instead of directly on "master".
82
+ To submit your changes via a GitHub pull request, we _highly_ recommend that you have them on a topic branch, instead of directly on "main".
83
83
  It makes things much easier to keep track of, especially if you decide to work on another thing before your first change is merged in.
84
84
 
85
85
  GitHub has some pretty good [general documentation](http://help.github.com/) on using their site.
data/README.md CHANGED
@@ -74,9 +74,9 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
74
74
 
75
75
  Steps to release an update to the gem and module include:
76
76
 
77
- 1. Ensure that the release branch is up to date with the master:
77
+ 1. Ensure that the release branch is up to date with the main:
78
78
  ```bash
79
- git push upstream upstream/master:release --force
79
+ git push upstream upstream/main:release --force
80
80
  ```
81
81
  1. Checkout a new working branch for the release prep (where xyz is the appropriate version, sans periods):
82
82
  ```bash
@@ -96,7 +96,7 @@ Steps to release an update to the gem and module include:
96
96
  ```
97
97
  1. Push your changes and submit a pull request for review _against the **release** branch_:
98
98
  ```bash
99
- git push -u origin maint/release-prep-xyz
99
+ git push -u origin maint/release/prep-xyz
100
100
  ```
101
101
  1. Ensure tests pass and the code is merged to `release`.
102
102
  1. Grab the commit hash from the merge commit on release, use that as the tag for the version (replacing `x.y.z` with the appropriate version and `commithash` with the relevant one), then push the tags to upstream:
@@ -114,6 +114,6 @@ Steps to release an update to the gem and module include:
114
114
  bundle exec rake build_module
115
115
  ```
116
116
  1. Publish the updated module version (found in the `pkg` folder) to [the Forge](https://forge.puppet.com/puppetlabs/pwshlib).
117
- 1. Submit the [mergeback PR from the release branch to master](https://github.com/puppetlabs/ruby-pwsh/compare/master...release).
117
+ 1. Submit the [mergeback PR from the release branch to main](https://github.com/puppetlabs/ruby-pwsh/compare/main...release).
118
118
 
119
119
  ## Known Issues
@@ -2,7 +2,7 @@
2
2
  version: 1.1.x.{build}
3
3
  branches:
4
4
  only:
5
- - master
5
+ - main
6
6
  - release
7
7
  clone_depth: 10
8
8
  environment:
@@ -1,12 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'puppet/resource_api/simple_provider'
4
3
  require 'securerandom'
5
4
  require 'ruby-pwsh'
6
5
  require 'pathname'
7
6
  require 'json'
8
7
 
9
- class Puppet::Provider::DscBaseProvider < Puppet::ResourceApi::SimpleProvider
8
+ class Puppet::Provider::DscBaseProvider
10
9
  # Initializes the provider, preparing the class variables which cache:
11
10
  # - the canonicalized resources across calls
12
11
  # - query results
@@ -15,6 +14,7 @@ class Puppet::Provider::DscBaseProvider < Puppet::ResourceApi::SimpleProvider
15
14
  @@cached_canonicalized_resource = []
16
15
  @@cached_query_results = []
17
16
  @@logon_failures = []
17
+ super
18
18
  end
19
19
 
20
20
  # Look through a cache to retrieve the hashes specified, if they have been cached.
@@ -50,6 +50,8 @@ class Puppet::Provider::DscBaseProvider < Puppet::ResourceApi::SimpleProvider
50
50
  canonicalized = r.dup
51
51
  @@cached_canonicalized_resource << r.dup
52
52
  else
53
+ parameters = r.select { |name, _properties| parameter_attributes(context).include?(name) }
54
+ canonicalized.merge!(parameters)
53
55
  canonicalized[:name] = r[:name]
54
56
  if r[:dsc_psdscrunascredential].nil?
55
57
  canonicalized.delete(:dsc_psdscrunascredential)
@@ -109,6 +111,78 @@ class Puppet::Provider::DscBaseProvider < Puppet::ResourceApi::SimpleProvider
109
111
  end
110
112
  end
111
113
 
114
+ # Determines whether a resource is ensurable and which message to write (create, update, or delete),
115
+ # then passes the appropriate values along to the various sub-methods which themselves call the Set
116
+ # method of Invoke-DscResource. Implementation borrowed directly from the Resource API Simple Provider
117
+ #
118
+ # @param context [Object] the Puppet runtime context to operate in and send feedback to
119
+ # @param changes [Hash] the hash of whose key is the name_hash and value is the is and should hashes
120
+ def set(context, changes)
121
+ changes.each do |name, change|
122
+ is = change.key?(:is) ? change[:is] : (get(context, [name]) || []).find { |r| r[:name] == name }
123
+ context.type.check_schema(is) unless change.key?(:is)
124
+
125
+ should = change[:should]
126
+
127
+ name_hash = if context.type.namevars.length > 1
128
+ # pass a name_hash containing the values of all namevars
129
+ name_hash = {}
130
+ context.type.namevars.each do |namevar|
131
+ name_hash[namevar] = change[:should][namevar]
132
+ end
133
+ name_hash
134
+ else
135
+ name
136
+ end
137
+
138
+ # for compatibility sake, we use dsc_ensure instead of ensure, so context.type.ensurable? does not work
139
+ if context.type.attributes.key?(:dsc_ensure)
140
+ is = create_absent(:name, name) if is.nil?
141
+ should = create_absent(:name, name) if should.nil?
142
+
143
+ # HACK: If the DSC Resource is ensurable but doesn't report a default value
144
+ # for ensure, we assume it to be `Present` - this is the most common pattern.
145
+ should_ensure = should[:dsc_ensure].nil? ? 'Present' : should[:dsc_ensure].to_s
146
+ # HACK: Sometimes dsc_ensure is removed???? If it's gone, pretend it's absent??
147
+ is_ensure = is[:dsc_ensure].nil? ? 'Absent' : is[:dsc_ensure].to_s
148
+
149
+ if is_ensure == 'Absent' && should_ensure == 'Present'
150
+ context.creating(name) do
151
+ create(context, name_hash, should)
152
+ end
153
+ elsif is_ensure == 'Present' && should_ensure == 'Present'
154
+ context.updating(name) do
155
+ update(context, name_hash, should)
156
+ end
157
+ elsif is_ensure == 'Present' && should_ensure == 'Absent'
158
+ context.deleting(name) do
159
+ delete(context, name_hash)
160
+ end
161
+ end
162
+ else
163
+ context.updating(name) do
164
+ update(context, name_hash, should)
165
+ end
166
+ end
167
+ end
168
+ end
169
+
170
+ # Creates a hash with the name / name_hash and sets dsc_ensure to absent for comparison
171
+ # purposes; this handles cases where the resource isn't found on the node.
172
+ #
173
+ # @param namevar [Object] the name of the variable being used for the resource name
174
+ # @param title [Hash] the hash of namevar properties and their values
175
+ # @return [Hash] returns a hash representing the absent state of the resource
176
+ def create_absent(namevar, title)
177
+ result = if title.is_a? Hash
178
+ title.dup
179
+ else
180
+ { namevar => title }
181
+ end
182
+ result[:dsc_ensure] = 'Absent'
183
+ result
184
+ end
185
+
112
186
  # Attempts to set an instance of the DSC resource, invoking the `Set` method and thinly wrapping
113
187
  # the `invoke_set_method` method; whether this method, `update`, or `delete` is called is entirely
114
188
  # up to the Resource API based on the results
@@ -158,9 +232,7 @@ class Puppet::Provider::DscBaseProvider < Puppet::ResourceApi::SimpleProvider
158
232
  context.debug("retrieving #{name_hash.inspect}")
159
233
 
160
234
  # Do not bother running if the logon credentials won't work
161
- unless name_hash[:dsc_psdscrunascredential].nil?
162
- return name_hash if logon_failed_already?(name_hash[:dsc_psdscrunascredential])
163
- end
235
+ return name_hash if !name_hash[:dsc_psdscrunascredential].nil? && logon_failed_already?(name_hash[:dsc_psdscrunascredential])
164
236
 
165
237
  query_props = name_hash.select { |k, v| mandatory_get_attributes(context).include?(k) || (k == :dsc_psdscrunascredential && !v.nil?) }
166
238
  resource = should_to_resource(query_props, context, 'get')
@@ -190,28 +262,36 @@ class Puppet::Provider::DscBaseProvider < Puppet::ResourceApi::SimpleProvider
190
262
  # DSC gives back information we don't care about; filter down to only
191
263
  # those properties exposed in the type definition.
192
264
  valid_attributes = context.type.attributes.keys.collect(&:to_s)
265
+ parameters = context.type.attributes.select { |_name, properties| [properties[:behaviour]].collect.include?(:parameter) }.keys.collect(&:to_s)
193
266
  data.select! { |key, _value| valid_attributes.include?("dsc_#{key.downcase}") }
267
+ data.reject! { |key, _value| parameters.include?("dsc_#{key.downcase}") }
194
268
  # Canonicalize the results to match the type definition representation;
195
269
  # failure to do so will prevent the resource_api from comparing the result
196
270
  # to the should hash retrieved from the resource definition in the manifest.
197
- data.keys.each do |key|
271
+ data.keys.each do |key| # rubocop:disable Style/HashEachMethods
198
272
  type_key = "dsc_#{key.downcase}".to_sym
199
273
  data[type_key] = data.delete(key)
200
274
  camelcase_hash_keys!(data[type_key]) if data[type_key].is_a?(Enumerable)
275
+ # Convert DateTime back to appropriate type
276
+ data[type_key] = Puppet::Pops::Time::Timestamp.parse(data[type_key]) if context.type.attributes[type_key][:mof_type] =~ /DateTime/i
277
+ # PowerShell does not distinguish between a return of empty array/string
278
+ # and null but Puppet does; revert to those values if specified.
279
+ if data[type_key].nil? && query_props.keys.include?(type_key) && query_props[type_key].is_a?(Array)
280
+ data[type_key] = query_props[type_key].empty? ? query_props[type_key] : []
281
+ end
201
282
  end
202
- # If a resource is found, it's present, so refill these two Puppet-only keys
283
+ # If a resource is found, it's present, so refill this Puppet-only key
203
284
  data.merge!({ name: name_hash[:name] })
204
- if ensurable?(context)
205
- ensure_value = data.key?(:dsc_ensure) ? data[:dsc_ensure].downcase : 'present'
206
- data.merge!({ ensure: ensure_value })
207
- end
208
- # TODO: Handle PSDscRunAsCredential flapping
209
- # Resources do not return the account under which they were discovered, so re-add that
210
- if name_hash[:dsc_psdscrunascredential].nil?
211
- data.delete(:dsc_psdscrunascredential)
212
- else
213
- data.merge!({ dsc_psdscrunascredential: name_hash[:dsc_psdscrunascredential] })
214
- end
285
+
286
+ # Have to check for this to avoid a weird canonicalization warning
287
+ # The Resource API calls canonicalize against the current state which
288
+ # will lead to dsc_ensure being set to absent in the name_hash even if
289
+ # it was set to present in the manifest
290
+ name_hash_has_nil_keys = name_hash.select { |_k, v| v.nil? }.count.positive?
291
+ # We want to throw away all of the empty keys if and only if the manifest
292
+ # declaration is for an absent resource and the resource is actually absent
293
+ data.reject! { |_k, v| v.nil? } if data[:dsc_ensure] == 'Absent' && name_hash[:dsc_ensure] == 'Absent' && !name_hash_has_nil_keys
294
+
215
295
  # Cache the query to prevent a second lookup
216
296
  @@cached_query_results << data.dup if fetch_cached_hashes(@@cached_query_results, [data]).empty?
217
297
  context.debug("Returned to Puppet as #{data}")
@@ -229,9 +309,7 @@ class Puppet::Provider::DscBaseProvider < Puppet::ResourceApi::SimpleProvider
229
309
  context.debug("Invoking Set Method for '#{name}' with #{should.inspect}")
230
310
 
231
311
  # Do not bother running if the logon credentials won't work
232
- unless should[:dsc_psdscrunascredential].nil?
233
- return nil if logon_failed_already?(should[:dsc_psdscrunascredential])
234
- end
312
+ return nil if !should[:dsc_psdscrunascredential].nil? && logon_failed_already?(should[:dsc_psdscrunascredential])
235
313
 
236
314
  apply_props = should.select { |k, _v| k.to_s =~ /^dsc_/ }
237
315
  resource = should_to_resource(apply_props, context, 'set')
@@ -265,7 +343,6 @@ class Puppet::Provider::DscBaseProvider < Puppet::ResourceApi::SimpleProvider
265
343
  resource[k] = context.type.definition[k]
266
344
  end
267
345
  should.each do |k, v|
268
- next if k == :ensure
269
346
  # PSDscRunAsCredential is considered a namevar and will always be passed, even if nil
270
347
  # To prevent flapping during runs, remove it from the resource definition unless specified
271
348
  next if k == :dsc_psdscrunascredential && v.nil?
@@ -281,17 +358,47 @@ class Puppet::Provider::DscBaseProvider < Puppet::ResourceApi::SimpleProvider
281
358
  # Because Puppet adds all of the modules to the LOAD_PATH we can be sure that the appropriate module lives here during an apply;
282
359
  # PROBLEM: This currently uses the downcased name, we need to capture the module name in the metadata I think.
283
360
  # During a Puppet agent run, the code lives in the cache so we can use the file expansion to discover the correct folder.
284
- root_module_path = $LOAD_PATH.select { |path| path.match?(%r{#{resource[:dscmeta_module_name].downcase}/lib}) }.first
361
+ root_module_path = $LOAD_PATH.select { |path| path.match?(%r{#{puppetize_name(resource[:dscmeta_module_name])}/lib}) }.first
285
362
  resource[:vendored_modules_path] = if root_module_path.nil?
286
- File.expand_path(Pathname.new(__FILE__).dirname + '../../../' + 'puppet_x/dsc_resources')
363
+ File.expand_path(Pathname.new(__FILE__).dirname + '../../../' + 'puppet_x/dsc_resources') # rubocop:disable Style/StringConcatenation
287
364
  else
288
- File.expand_path(root_module_path + '/puppet_x/dsc_resources')
365
+ File.expand_path("#{root_module_path}/puppet_x/dsc_resources")
289
366
  end
367
+
368
+ # This handles setting the vendored_modules_path to include the puppet module name
369
+ # We now add the puppet module name into the path to allow multiple modules to with shared dsc_resources to be installed side by side
370
+ # The old vendored_modules_path: puppet_x/dsc_resources
371
+ # The new vendored_modules_path: puppet_x/<module_name>/dsc_resources
372
+ unless File.exist? resource[:vendored_modules_path]
373
+ resource[:vendored_modules_path] = if root_module_path.nil?
374
+ File.expand_path(Pathname.new(__FILE__).dirname + '../../../' + "puppet_x/#{puppetize_name(resource[:dscmeta_module_name])}/dsc_resources") # rubocop:disable Style/StringConcatenation
375
+ else
376
+ File.expand_path("#{root_module_path}/puppet_x/#{puppetize_name(resource[:dscmeta_module_name])}/dsc_resources")
377
+ end
378
+ end
379
+
380
+ # A warning is thrown if the something went wrong and the file was not created
381
+ raise "Unable to find expected vendored DSC Resource #{resource[:vendored_modules_path]}" unless File.exist? resource[:vendored_modules_path]
382
+
290
383
  resource[:attributes] = nil
384
+
291
385
  context.debug("should_to_resource: #{resource.inspect}")
292
386
  resource
293
387
  end
294
388
 
389
+ # Return a String containing a puppetized name. A puppetized name is a string that only
390
+ # includes lowercase letters, digits, underscores and cannot start with a digit.
391
+ #
392
+ # @return [String] with a puppeized module name
393
+ def puppetize_name(name)
394
+ # Puppet module names must be lower case
395
+ name = name.downcase
396
+ # Puppet module names must only include lowercase letters, digits and underscores
397
+ name = name.gsub(/[^a-z0-9_]/, '_')
398
+ # Puppet module names must not start with a number so if it does, append the letter 'a' to the name. Eg: '7zip' becomes 'a7zip'
399
+ name = name.match?(/^\d/) ? "a#{name}" : name # rubocop:disable Lint/UselessAssignment
400
+ end
401
+
295
402
  # Return a UUID with the dashes turned into underscores to enable the specifying of guaranteed-unique
296
403
  # variables in the PowerShell script.
297
404
  #
@@ -331,7 +438,7 @@ class Puppet::Provider::DscBaseProvider < Puppet::ResourceApi::SimpleProvider
331
438
  # @return [Enumerable] returns the input object with hash keys recursively camelCased
332
439
  def camelcase_hash_keys!(enumerable)
333
440
  if enumerable.is_a?(Hash)
334
- enumerable.keys.each do |key|
441
+ enumerable.keys.each do |key| # rubocop:disable Style/HashEachMethods
335
442
  name = key.dup
336
443
  name[0] = name[0].downcase
337
444
  enumerable[name] = enumerable.delete(key)
@@ -363,14 +470,6 @@ class Puppet::Provider::DscBaseProvider < Puppet::ResourceApi::SimpleProvider
363
470
  end
364
471
  end
365
472
 
366
- # Checks to see whether the DSC resource being managed is defined as ensurable
367
- #
368
- # @param context [Object] the Puppet runtime context to operate in and send feedback to
369
- # @return [Bool] returns true if the DSC Resource is ensurable, otherwise false.
370
- def ensurable?(context)
371
- context.type.attributes.keys.include?(:ensure)
372
- end
373
-
374
473
  # Parses the DSC resource type definition to retrieve the names of any attributes which are specified as mandatory for get operations
375
474
  #
376
475
  # @param context [Object] the Puppet runtime context to operate in and send feedback to
@@ -395,6 +494,14 @@ class Puppet::Provider::DscBaseProvider < Puppet::ResourceApi::SimpleProvider
395
494
  context.type.attributes.select { |_attribute, properties| properties[:behaviour] == :namevar }.keys
396
495
  end
397
496
 
497
+ # Parses the DSC resource type definition to retrieve the names of any attributes which are specified as parameters
498
+ #
499
+ # @param context [Object] the Puppet runtime context to operate in and send feedback to
500
+ # @return [Array] returns an array of attribute names as symbols which are parameters
501
+ def parameter_attributes(context)
502
+ context.type.attributes.select { |_name, properties| properties[:behaviour] == :parameter }.keys
503
+ end
504
+
398
505
  # Look through a fully formatted string, replacing all instances where a value matches the formatted properties
399
506
  # of an instantiated variable with references to the variable instead. This allows us to pass complex and nested
400
507
  # CIM instances to the Invoke-DscResource parameter hash without constructing them *in* the hash.
@@ -441,8 +548,7 @@ class Puppet::Provider::DscBaseProvider < Puppet::ResourceApi::SimpleProvider
441
548
  # @param credential_hash [Hash] the Properties which define the PSCredential Object
442
549
  # @return [String] A line of PowerShell which defines the PSCredential object and stores it to a variable
443
550
  def format_pscredential(variable_name, credential_hash)
444
- definition = "$#{variable_name} = New-PSCredential -User #{credential_hash['user']} -Password '#{credential_hash['password']}' # PuppetSensitive"
445
- definition
551
+ "$#{variable_name} = New-PSCredential -User #{credential_hash['user']} -Password '#{credential_hash['password']}' # PuppetSensitive"
446
552
  end
447
553
 
448
554
  # Parses a resource definition (as from `should_to_resource`) for any properties which are CIM instances
@@ -522,8 +628,7 @@ class Puppet::Provider::DscBaseProvider < Puppet::ResourceApi::SimpleProvider
522
628
  # EVEN WORSE HACK - this one we can't even be sure it's a cim instance...
523
629
  # but I don't _think_ anything but nested cim instances show up as hashes inside an array
524
630
  definition = definition.gsub('@(@{', '[CimInstance[]]@(@{')
525
- definition = interpolate_variables(definition)
526
- definition
631
+ interpolate_variables(definition)
527
632
  end
528
633
 
529
634
  # Munge a resource definition (as from `should_to_resource`) into valid PowerShell which represents
@@ -548,18 +653,32 @@ class Puppet::Provider::DscBaseProvider < Puppet::ResourceApi::SimpleProvider
548
653
  resource[:parameters].each do |property_name, property_hash|
549
654
  # strip dsc_ from the beginning of the property name declaration
550
655
  name = property_name.to_s.gsub(/^dsc_/, '').to_sym
551
- params[:Property][name] = if property_hash[:mof_type] == 'PSCredential'
656
+ params[:Property][name] = case property_hash[:mof_type]
657
+ when 'PSCredential'
552
658
  # format can't unwrap Sensitive strings nested in arbitrary hashes/etc, so make
553
659
  # the Credential hash interpolable as it will be replaced by a variable reference.
554
660
  {
555
661
  'user' => property_hash[:value]['user'],
556
662
  'password' => escape_quotes(property_hash[:value]['password'].unwrap)
557
663
  }
664
+ when 'DateTime'
665
+ # These have to be handled specifically because they rely on the *Puppet* DateTime,
666
+ # not a generic ruby data type (and so can't go in the shared util in ruby-pwsh)
667
+ "[DateTime]#{property_hash[:value].format('%FT%T%z')}"
558
668
  else
559
669
  property_hash[:value]
560
670
  end
561
671
  end
562
672
  params_block = interpolate_variables("$InvokeParams = #{format(params)}")
673
+ # Move the Apostrophe for DateTime declarations
674
+ params_block = params_block.gsub("'[DateTime]", "[DateTime]'")
675
+ # HACK: Handle intentionally empty arrays - need to strongly type them because
676
+ # CIM instances do not do a consistent job of casting an empty array properly.
677
+ empty_array_parameters = resource[:parameters].select { |_k, v| v[:value].empty? }
678
+ empty_array_parameters.each do |name, properties|
679
+ param_block_name = name.to_s.gsub(/^dsc_/, '')
680
+ params_block = params_block.gsub("#{param_block_name} = @()", "#{param_block_name} = [#{properties[:mof_type]}]@()")
681
+ end
563
682
  # HACK: make CIM instances work:
564
683
  resource[:parameters].select { |_key, hash| hash[:mof_is_embedded] && hash[:mof_type] =~ /\[\]/ }.each do |_property_name, property_hash|
565
684
  formatted_property_hash = interpolate_variables(format(property_hash[:value]))
@@ -577,11 +696,11 @@ class Puppet::Provider::DscBaseProvider < Puppet::ResourceApi::SimpleProvider
577
696
  def ps_script_content(resource)
578
697
  template_path = File.expand_path('../', __FILE__)
579
698
  # Defines the helper functions
580
- functions = File.new(template_path + '/invoke_dsc_resource_functions.ps1').read
699
+ functions = File.new("#{template_path}/invoke_dsc_resource_functions.ps1").read
581
700
  # Defines the response hash and the runtime settings
582
- preamble = File.new(template_path + '/invoke_dsc_resource_preamble.ps1').read
701
+ preamble = File.new("#{template_path}/invoke_dsc_resource_preamble.ps1").read
583
702
  # The postscript defines the invocation error and result handling; expects `$InvokeParams` to be defined
584
- postscript = File.new(template_path + '/invoke_dsc_resource_postscript.ps1').read
703
+ postscript = File.new("#{template_path}/invoke_dsc_resource_postscript.ps1").read
585
704
  # The blocks define the variables to define for the postscript.
586
705
  credential_block = prepare_credentials(resource)
587
706
  cim_instances_block = prepare_cim_instances(resource)
@@ -589,8 +708,7 @@ class Puppet::Provider::DscBaseProvider < Puppet::ResourceApi::SimpleProvider
589
708
  # clean them out of the temporary cache now that they're not needed; failure to do so can goof up future executions in this run
590
709
  clear_instantiated_variables!
591
710
 
592
- content = [functions, preamble, credential_block, cim_instances_block, parameters_block, postscript].join("\n")
593
- content
711
+ [functions, preamble, credential_block, cim_instances_block, parameters_block, postscript].join("\n")
594
712
  end
595
713
 
596
714
  # Convert a Puppet/Ruby value into a PowerShell representation. Requires some slight additional
@@ -614,15 +732,16 @@ class Puppet::Provider::DscBaseProvider < Puppet::ResourceApi::SimpleProvider
614
732
  # @param value [Object] The object to unwrap sensitive data inside of
615
733
  # @return [Object] The object with any sensitive strings unwrapped and annotated
616
734
  def unwrap(value)
617
- if value.class.name == 'Puppet::Pops::Types::PSensitiveType::Sensitive'
735
+ case value
736
+ when Puppet::Pops::Types::PSensitiveType::Sensitive
618
737
  "#{value.unwrap}#PuppetSensitive"
619
- elsif value.class.name == 'Hash'
738
+ when Hash
620
739
  unwrapped = {}
621
740
  value.each do |k, v|
622
741
  unwrapped[k] = unwrap(v)
623
742
  end
624
743
  unwrapped
625
- elsif value.class.name == 'Array'
744
+ when Array
626
745
  unwrapped = []
627
746
  value.each do |v|
628
747
  unwrapped << unwrap(v)
@@ -68,6 +68,10 @@ Function ConvertTo-CanonicalResult {
68
68
  $Value = $null
69
69
  }
70
70
  }
71
+ elseif ($Result.$PropertyName.GetType().Name -match 'DateTime') {
72
+ # Handle DateTimes especially since they're an edge case
73
+ $Value = Get-Date $Result.$PropertyName -UFormat "%Y-%m-%dT%H:%M:%S%Z"
74
+ }
71
75
  else {
72
76
  # Looks like a nested CIM instance, recurse if we're not too deep in already.
73
77
  $RecursionLevel++
@@ -14,10 +14,10 @@ require 'logger'
14
14
  module Pwsh
15
15
  # Standard errors
16
16
  class Error < StandardError; end
17
+
17
18
  # Create an instance of a PowerShell host and manage execution of PowerShell code inside that host.
18
19
  class Manager
19
- attr_reader :powershell_command
20
- attr_reader :powershell_arguments
20
+ attr_reader :powershell_command, :powershell_arguments
21
21
 
22
22
  # We actually want this to be a class variable.
23
23
  @@instances = {} # rubocop:disable Style/ClassVars
@@ -70,7 +70,7 @@ module Pwsh
70
70
  def self.win32console_enabled?
71
71
  @win32console_enabled ||= defined?(Win32) &&
72
72
  defined?(Win32::Console) &&
73
- Win32::Console.class == Class
73
+ Win32::Console.instance_of?(Class)
74
74
  end
75
75
 
76
76
  # TODO: This thing isn't called anywhere and the variable it sets is never referenced...
@@ -117,16 +117,16 @@ module Pwsh
117
117
  #
118
118
  # @return [String] representation of the value for interpolation
119
119
  def format_powershell_value(object)
120
- if %i[true false].include?(object) || %w[trueclass falseclass].include?(object.class.name.downcase) # rubocop:disable Lint/BooleanSymbol
120
+ if %i[true false].include?(object) || %w[trueclass falseclass].include?(object.class.name.downcase)
121
121
  "$#{object}"
122
- elsif object.class.name == 'Symbol' || object.class.ancestors.include?(Numeric)
122
+ elsif object.instance_of?(Symbol) || object.class.ancestors.include?(Numeric)
123
123
  object.to_s
124
- elsif object.class.name == 'String'
124
+ elsif object.instance_of?(String)
125
125
  "'#{escape_quotes(object)}'"
126
- elsif object.class.name == 'Array'
127
- '@(' + object.collect { |item| format_powershell_value(item) }.join(', ') + ')'
128
- elsif object.class.name == 'Hash'
129
- '@{' + object.collect { |k, v| format_powershell_value(k) + ' = ' + format_powershell_value(v) }.join('; ') + '}'
126
+ elsif object.instance_of?(Array)
127
+ "@(#{object.collect { |item| format_powershell_value(item) }.join(', ')})"
128
+ elsif object.instance_of?(Hash)
129
+ "@{#{object.collect { |k, v| "#{format_powershell_value(k)} = #{format_powershell_value(v)}" }.join('; ')}}"
130
130
  else
131
131
  raise "unsupported type #{object.class} of value '#{object}'"
132
132
  end
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Pwsh
4
4
  # The version of the ruby-pwsh gem
5
- VERSION = '0.5.1'
5
+ VERSION = '0.7.0'
6
6
  end
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "puppetlabs-pwshlib",
3
- "version": "0.5.1",
3
+ "version": "0.7.0",
4
4
  "author": "puppetlabs",
5
5
  "summary": "Provide library code for interoperating with PowerShell.",
6
6
  "license": "MIT",
7
7
  "source": "https://github.com/puppetlabs/ruby-pwsh",
8
- "project_page": "https://github.com/puppetlabs/ruby-pwsh/blob/master/pwshlib.md",
8
+ "project_page": "https://github.com/puppetlabs/ruby-pwsh/blob/main/pwshlib.md",
9
9
  "issues_url": "https://github.com/puppetlabs/ruby-pwsh/issues",
10
10
  "dependencies": [
11
11
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-pwsh
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.1
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Puppet, Inc.
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-09-25 00:00:00.000000000 Z
11
+ date: 2021-01-20 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: PowerShell code manager for ruby.
14
14
  email:
@@ -54,7 +54,7 @@ metadata:
54
54
  homepage_uri: https://github.com/puppetlabs/ruby-pwsh
55
55
  source_code_uri: https://github.com/puppetlabs/ruby-pwsh
56
56
  changelog_uri: https://github.com/puppetlabs/ruby-pwsh
57
- post_install_message:
57
+ post_install_message:
58
58
  rdoc_options: []
59
59
  require_paths:
60
60
  - lib
@@ -69,9 +69,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
69
69
  - !ruby/object:Gem::Version
70
70
  version: '0'
71
71
  requirements: []
72
- rubyforge_project:
73
- rubygems_version: 2.7.6
74
- signing_key:
72
+ rubygems_version: 3.1.4
73
+ signing_key:
75
74
  specification_version: 4
76
75
  summary: PowerShell code manager for ruby.
77
76
  test_files: []