chef 16.7.61 → 16.8.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +1 -2
  3. data/README.md +1 -1
  4. data/chef.gemspec +2 -1
  5. data/lib/chef/application/base.rb +1 -1
  6. data/lib/chef/client.rb +3 -0
  7. data/lib/chef/compliance/default_attributes.rb +89 -0
  8. data/lib/chef/compliance/fetcher/automate.rb +69 -0
  9. data/lib/chef/compliance/fetcher/chef_server.rb +134 -0
  10. data/lib/chef/compliance/reporter/automate.rb +202 -0
  11. data/lib/chef/compliance/reporter/chef_server_automate.rb +92 -0
  12. data/lib/chef/compliance/reporter/compliance_enforcer.rb +20 -0
  13. data/lib/chef/compliance/reporter/json_file.rb +19 -0
  14. data/lib/chef/compliance/runner.rb +250 -0
  15. data/lib/chef/cookbook_manifest.rb +1 -0
  16. data/lib/chef/encrypted_data_bag_item/assertions.rb +1 -1
  17. data/lib/chef/exceptions.rb +4 -0
  18. data/lib/chef/http/ssl_policies.rb +6 -0
  19. data/lib/chef/knife/bootstrap/train_connector.rb +1 -1
  20. data/lib/chef/knife/core/ui.rb +4 -1
  21. data/lib/chef/knife/ssh.rb +1 -1
  22. data/lib/chef/mixin/powershell_exec.rb +3 -1
  23. data/lib/chef/platform/query_helpers.rb +4 -4
  24. data/lib/chef/powershell.rb +2 -0
  25. data/lib/chef/provider/dsc_resource.rb +12 -24
  26. data/lib/chef/provider/dsc_script.rb +16 -20
  27. data/lib/chef/provider/git.rb +5 -5
  28. data/lib/chef/resource/chef_client_config.rb +1 -1
  29. data/lib/chef/resource/dsc_script.rb +8 -1
  30. data/lib/chef/resource/hostname.rb +3 -3
  31. data/lib/chef/resource/template.rb +2 -2
  32. data/lib/chef/resource/windows_certificate.rb +7 -1
  33. data/lib/chef/resource_collection/resource_set.rb +1 -1
  34. data/lib/chef/util/dsc/configuration_generator.rb +52 -11
  35. data/lib/chef/util/dsc/lcm_output_parser.rb +3 -4
  36. data/lib/chef/util/dsc/local_configuration_manager.rb +17 -14
  37. data/lib/chef/util/dsc/resource_store.rb +5 -11
  38. data/lib/chef/version.rb +1 -1
  39. data/lib/chef/win32/api/file.rb +4 -0
  40. data/spec/functional/resource/dsc_script_spec.rb +3 -6
  41. data/spec/integration/client/client_spec.rb +2 -1
  42. data/spec/integration/compliance/compliance_spec.rb +81 -0
  43. data/spec/integration/recipes/recipe_dsl_spec.rb +1 -0
  44. data/spec/spec_helper.rb +1 -1
  45. data/spec/unit/client_spec.rb +1 -0
  46. data/spec/unit/compliance/fetcher/automate_spec.rb +134 -0
  47. data/spec/unit/compliance/fetcher/chef_server_spec.rb +93 -0
  48. data/spec/unit/compliance/reporter/automate_spec.rb +427 -0
  49. data/spec/unit/compliance/reporter/chef_server_automate_spec.rb +177 -0
  50. data/spec/unit/compliance/reporter/compliance_enforcer_spec.rb +48 -0
  51. data/spec/unit/compliance/runner_spec.rb +113 -0
  52. data/spec/unit/http/ssl_policies_spec.rb +11 -0
  53. data/spec/unit/knife/core/node_editor_spec.rb +1 -1
  54. data/spec/unit/mixin/powershell_exec_spec.rb +1 -1
  55. data/spec/unit/platform/query_helpers_spec.rb +11 -12
  56. data/spec/unit/provider/dsc_resource_spec.rb +10 -27
  57. data/spec/unit/provider/dsc_script_spec.rb +1 -1
  58. data/spec/unit/provider/mount/windows_spec.rb +1 -0
  59. data/spec/unit/provider/systemd_unit_spec.rb +1 -1
  60. data/spec/unit/resource/windows_certificate_spec.rb +12 -0
  61. data/spec/unit/util/dsc/configuration_generator_spec.rb +79 -0
  62. data/spec/unit/util/dsc/local_configuration_manager_spec.rb +27 -35
  63. metadata +37 -12
  64. data/lib/chef/util/powershell/cmdlet.rb +0 -169
  65. data/lib/chef/util/powershell/cmdlet_result.rb +0 -61
  66. data/spec/functional/util/powershell/cmdlet_spec.rb +0 -111
  67. data/spec/unit/util/powershell/cmdlet_spec.rb +0 -106
@@ -64,7 +64,10 @@ class Chef
64
64
  # Creates a new object of class TTY::Prompt
65
65
  # with interrupt as exit so that it can be terminated with status code.
66
66
  def prompt
67
- @prompt ||= TTY::Prompt.new(interrupt: :exit)
67
+ @prompt ||= begin
68
+ require "tty-prompt"
69
+ TTY::Prompt.new(interrupt: :exit)
70
+ end
68
71
  end
69
72
 
70
73
  # pastel.decorate is a lightweight replacement for highline.color
@@ -289,7 +289,7 @@ class Chef
289
289
  opts[:port] = port unless port.nil?
290
290
  opts[:logger] = Chef::Log.with_child(subsystem: "net/ssh") if Chef::Log.level == :trace
291
291
  unless config[:host_key_verify]
292
- opts[:verify_host_key] = false
292
+ opts[:verify_host_key] = :never
293
293
  opts[:user_known_hosts_file] = "/dev/null"
294
294
  end
295
295
  if ssh_config[:keepalive]
@@ -23,10 +23,12 @@ require_relative "../pwsh"
23
23
  # powershell_exec is initialized with a string that should be set to the script
24
24
  # to run and also takes an optional interpreter argument which must be either
25
25
  # :powershell (Windows PowerShell which is the default) or :pwsh (PowerShell
26
- # Core). It will return a Chef::PowerShell object that provides 4 methods:
26
+ # Core). It will return a Chef::PowerShell object that provides 5 methods:
27
27
  #
28
28
  # .result - returns a hash representing the results returned by executing the
29
29
  # PowerShell script block
30
+ # .verbose - this is an array of string containing any messages written to the
31
+ # PowerShell verbose stream during execution
30
32
  # .errors - this is an array of string containing any messages written to the
31
33
  # PowerShell error stream during execution
32
34
  # .error? - returns true if there were error messages written to the PowerShell
@@ -58,10 +58,10 @@ class Chef
58
58
  end
59
59
 
60
60
  def dsc_refresh_mode_disabled?(node)
61
- require_relative "../util/powershell/cmdlet"
62
- cmdlet = Chef::Util::Powershell::Cmdlet.new(node, "Get-DscLocalConfigurationManager", :object)
63
- metadata = cmdlet.run!.return_value
64
- metadata["RefreshMode"] == "Disabled"
61
+ require_relative "../powershell"
62
+ exec = Chef::PowerShell.new("Get-DscLocalConfigurationManager")
63
+ exec.error!
64
+ exec.result["RefreshMode"] == "Disabled"
65
65
  end
66
66
 
67
67
  def supported_powershell_version?(node, version_string)
@@ -24,6 +24,7 @@ class Chef
24
24
 
25
25
  attr_reader :result
26
26
  attr_reader :errors
27
+ attr_reader :verbose
27
28
 
28
29
  # Run a command under PowerShell via FFI
29
30
  # This implementation requires the managed dll and native wrapper to be in the library search
@@ -72,6 +73,7 @@ class Chef
72
73
  hashed_outcome = Chef::JSONCompat.parse(execution)
73
74
  @result = Chef::JSONCompat.parse(hashed_outcome["result"])
74
75
  @errors = hashed_outcome["errors"]
76
+ @verbose = hashed_outcome["verbose"]
75
77
  end
76
78
  end
77
79
  end
@@ -15,7 +15,8 @@
15
15
  # See the License for the specific language governing permissions and
16
16
  # limitations under the License.
17
17
  #
18
- require_relative "../util/powershell/cmdlet"
18
+ require "timeout" unless defined?(Timeout)
19
+ require_relative "../mixin/powershell_exec"
19
20
  require_relative "../util/dsc/local_configuration_manager"
20
21
  require_relative "../mixin/powershell_type_coercions"
21
22
  require_relative "../util/dsc/resource_store"
@@ -130,27 +131,27 @@ class Chef
130
131
  def test_resource
131
132
  result = invoke_resource(:test)
132
133
  add_dsc_verbose_log(result)
133
- return_dsc_resource_result(result, "InDesiredState")
134
+ result.result["InDesiredState"]
134
135
  end
135
136
 
136
137
  def set_resource
137
138
  result = invoke_resource(:set)
138
139
  add_dsc_verbose_log(result)
139
- create_reboot_resource if return_dsc_resource_result(result, "RebootRequired")
140
- result.return_value
140
+ create_reboot_resource if result.result["RebootRequired"]
141
+ result
141
142
  end
142
143
 
143
144
  def add_dsc_verbose_log(result)
144
145
  # We really want this information from the verbose stream,
145
146
  # however in some versions of WMF, Invoke-DscResource is not correctly
146
147
  # writing to that stream and instead just dumping to stdout
147
- verbose_output = result.stream(:verbose)
148
- verbose_output = result.stdout if verbose_output.empty?
148
+ verbose_output = result.verbose.join("\n")
149
+ verbose_output = result.result if verbose_output.empty?
149
150
 
150
151
  if @converge_description.nil? || @converge_description.empty?
151
152
  @converge_description = verbose_output
152
153
  else
153
- @converge_description << "\n"
154
+ @converge_description << "\n\n"
154
155
  @converge_description << verbose_output
155
156
  end
156
157
  end
@@ -159,26 +160,13 @@ class Chef
159
160
  @module_version.nil? ? module_name : "@{ModuleName='#{module_name}';ModuleVersion='#{@module_version}'}"
160
161
  end
161
162
 
162
- def invoke_resource(method, output_format = :object)
163
+ def invoke_resource(method)
163
164
  properties = translate_type(new_resource.properties)
164
165
  switches = "-Method #{method} -Name #{new_resource.resource}"\
165
166
  " -Property #{properties} -Module #{module_info_object} -Verbose"
166
- cmdlet = Chef::Util::Powershell::Cmdlet.new(
167
- node,
168
- "Invoke-DscResource #{switches}",
169
- output_format
170
- )
171
- cmdlet.run!({}, { timeout: new_resource.timeout })
172
- end
173
-
174
- def return_dsc_resource_result(result, property_name)
175
- if result.return_value.is_a?(Array)
176
- # WMF Feb 2015 Preview
177
- result.return_value[0][property_name]
178
- else
179
- # WMF April 2015 Preview
180
- result.return_value[property_name]
181
- end
167
+ Timeout.timeout(new_resource.timeout) {
168
+ powershell_exec!("Invoke-DscResource #{switches}")
169
+ }
182
170
  end
183
171
 
184
172
  def create_reboot_resource
@@ -16,7 +16,6 @@
16
16
  # limitations under the License.
17
17
  #
18
18
 
19
- require_relative "../util/powershell/cmdlet"
20
19
  require_relative "../util/dsc/configuration_generator"
21
20
  require_relative "../util/dsc/local_configuration_manager"
22
21
  require_relative "../util/path_helper"
@@ -32,11 +31,11 @@ class Chef
32
31
  @dsc_resource = dsc_resource
33
32
  @resource_converged = false
34
33
  @operations = {
35
- set: Proc.new do |config_manager, document, shellout_flags|
36
- config_manager.set_configuration(document, shellout_flags)
34
+ set: Proc.new do |config_manager, document|
35
+ config_manager.set_configuration(document)
37
36
  end,
38
- test: Proc.new do |config_manager, document, shellout_flags|
39
- config_manager.test_configuration(document, shellout_flags)
37
+ test: Proc.new do |config_manager, document|
38
+ config_manager.test_configuration(document)
40
39
  end }
41
40
  end
42
41
 
@@ -85,20 +84,23 @@ class Chef
85
84
 
86
85
  config_manager = Chef::Util::DSC::LocalConfigurationManager.new(@run_context.node, config_directory)
87
86
 
88
- shellout_flags = {
89
- cwd: @dsc_resource.cwd,
90
- environment: @dsc_resource.environment,
91
- timeout: @dsc_resource.timeout,
92
- }
87
+ cwd = @dsc_resource.cwd || Dir.pwd
88
+ original_env = ENV.to_hash
93
89
 
94
90
  begin
95
- configuration_document = generate_configuration_document(config_directory, configuration_flags)
96
- @operations[operation].call(config_manager, configuration_document, shellout_flags)
91
+ ENV.update(@dsc_resource.environment) if @dsc_resource.environment
92
+ Dir.chdir(cwd) do
93
+ Timeout.timeout(@dsc_resource.timeout) do
94
+ configuration_document = generate_configuration_document(config_directory, configuration_flags)
95
+ @operations[operation].call(config_manager, configuration_document)
96
+ end
97
+ end
97
98
  rescue Exception => e
98
99
  logger.error("DSC operation failed: #{e.message}")
99
100
  raise e
100
101
  ensure
101
102
  ::FileUtils.rm_rf(config_directory)
103
+ ENV.replace(original_env)
102
104
  end
103
105
  end
104
106
 
@@ -112,20 +114,14 @@ class Chef
112
114
  end
113
115
 
114
116
  def generate_configuration_document(config_directory, configuration_flags)
115
- shellout_flags = {
116
- cwd: @dsc_resource.cwd,
117
- environment: @dsc_resource.environment,
118
- timeout: @dsc_resource.timeout,
119
- }
120
-
121
117
  generator = Chef::Util::DSC::ConfigurationGenerator.new(@run_context.node, config_directory)
122
118
 
123
119
  if @dsc_resource.command
124
- generator.configuration_document_from_script_path(@dsc_resource.command, configuration_name, configuration_flags, shellout_flags)
120
+ generator.configuration_document_from_script_path(@dsc_resource.command, configuration_name, configuration_flags)
125
121
  else
126
122
  # If code is also not provided, we mimic what the other script resources do (execute nothing)
127
123
  logger.warn("Neither code or command were provided for dsc_resource[#{@dsc_resource.name}].") unless @dsc_resource.code
128
- generator.configuration_document_from_script_code(@dsc_resource.code || "", configuration_flags, @dsc_resource.imports, shellout_flags)
124
+ generator.configuration_document_from_script_code(@dsc_resource.code || "", configuration_flags, @dsc_resource.imports)
129
125
  end
130
126
  end
131
127
 
@@ -68,9 +68,9 @@ class Chef
68
68
  a.assertion { !(new_resource.revision =~ %r{^origin/}) }
69
69
  a.failure_message Chef::Exceptions::InvalidRemoteGitReference,
70
70
  "Deploying remote branches is not supported. " +
71
- "Specify the remote branch as a local branch for " +
72
- "the git repository you're deploying from " +
73
- "(ie: '#{new_resource.revision.gsub("origin/", "")}' rather than '#{new_resource.revision}')."
71
+ "Specify the remote branch as a local branch for " +
72
+ "the git repository you're deploying from " +
73
+ "(ie: '#{new_resource.revision.gsub("origin/", "")}' rather than '#{new_resource.revision}')."
74
74
  end
75
75
 
76
76
  requirements.assert(:all_actions) do |a|
@@ -80,8 +80,8 @@ class Chef
80
80
  a.assertion { !target_revision.nil? }
81
81
  a.failure_message Chef::Exceptions::UnresolvableGitReference,
82
82
  "Unable to parse SHA reference for '#{new_resource.revision}' in repository '#{new_resource.repository}'. " +
83
- "Verify your (case-sensitive) repository URL and revision.\n" +
84
- "`git ls-remote '#{new_resource.repository}' '#{rev_search_pattern}'` output: #{@resolved_reference}"
83
+ "Verify your (case-sensitive) repository URL and revision.\n" +
84
+ "`git ls-remote '#{new_resource.repository}' '#{rev_search_pattern}'` output: #{@resolved_reference}"
85
85
  end
86
86
  end
87
87
 
@@ -110,7 +110,7 @@ class Chef
110
110
  property :config_directory, String,
111
111
  description: "The directory to store the client.rb in.",
112
112
  default: ChefConfig::Config.etc_chef_dir,
113
- default_description: "`/etc/chef/` on *nix-like systems and `C:\chef\` on Windows"
113
+ default_description: "`/etc/chef/` on *nix-like systems and `C:\\chef\\` on Windows"
114
114
 
115
115
  property :user, String,
116
116
  description: "The user that should own the client.rb file and the configuration directory if it needs to be created. Note: The configuration directory will not be created if it already exists, which allows you to further control the setup of that directory outside of this resource."
@@ -29,7 +29,14 @@ class Chef
29
29
  unified_mode true
30
30
  provides :dsc_script
31
31
 
32
- description "Many DSC resources are comparable to built-in #{ChefUtils::Dist::Infra::PRODUCT} resources. For example, both DSC and #{ChefUtils::Dist::Infra::PRODUCT} have file, package, and service resources. The dsc_script resource is most useful for those DSC resources that do not have a direct comparison to a resource in #{ChefUtils::Dist::Infra::PRODUCT}, such as the Archive resource, a custom DSC resource, an existing DSC script that performs an important task, and so on. Use the dsc_script resource to embed the code that defines a DSC configuration directly within a #{ChefUtils::Dist::Infra::PRODUCT} recipe."
32
+ description <<~DESC
33
+ Many DSC resources are comparable to built-in #{ChefUtils::Dist::Infra::PRODUCT} resources. For example, both DSC and #{ChefUtils::Dist::Infra::PRODUCT}
34
+ have file, package, and service resources. The dsc_script resource is most useful for those DSC resources that do not have a direct comparison to a
35
+ resource in #{ChefUtils::Dist::Infra::PRODUCT}, such as the Archive resource, a custom DSC resource, an existing DSC script that performs an important
36
+ task, and so on. Use the dsc_script resource to embed the code that defines a DSC configuration directly within a #{ChefUtils::Dist::Infra::PRODUCT} recipe.
37
+
38
+ Warning: The **dsc_script** resource may not be used with the 32 bit Chef Infra client. It must be executed from a 64 bit Chef Infra client.
39
+ DESC
33
40
 
34
41
  default_action :run
35
42
 
@@ -131,18 +131,18 @@ class Chef
131
131
  # darwin
132
132
  declare_resource(:execute, "set HostName via scutil") do
133
133
  command "/usr/sbin/scutil --set HostName #{new_resource.hostname}"
134
- not_if { shell_out!("/usr/sbin/scutil --get HostName").stdout.chomp == new_resource.hostname }
134
+ not_if { shell_out("/usr/sbin/scutil --get HostName").stdout.chomp == new_resource.hostname }
135
135
  notifies :reload, "ohai[reload hostname]"
136
136
  end
137
137
  declare_resource(:execute, "set ComputerName via scutil") do
138
138
  command "/usr/sbin/scutil --set ComputerName #{new_resource.hostname}"
139
- not_if { shell_out!("/usr/sbin/scutil --get ComputerName").stdout.chomp == new_resource.hostname }
139
+ not_if { shell_out("/usr/sbin/scutil --get ComputerName").stdout.chomp == new_resource.hostname }
140
140
  notifies :reload, "ohai[reload hostname]"
141
141
  end
142
142
  shortname = new_resource.hostname[/[^\.]*/]
143
143
  declare_resource(:execute, "set LocalHostName via scutil") do
144
144
  command "/usr/sbin/scutil --set LocalHostName #{shortname}"
145
- not_if { shell_out!("/usr/sbin/scutil --get LocalHostName").stdout.chomp == shortname }
145
+ not_if { shell_out("/usr/sbin/scutil --get LocalHostName").stdout.chomp == shortname }
146
146
  notifies :reload, "ohai[reload hostname]"
147
147
  end
148
148
  when linux?
@@ -169,8 +169,8 @@ class Chef
169
169
  elsif module_name.nil?
170
170
  raise Exceptions::ValidationFailed,
171
171
  "#helpers requires either a module name or inline module code as a block.\n" +
172
- "e.g.: helpers do; helper_code; end;\n" +
173
- "OR: helpers(MyHelpersModule)"
172
+ "e.g.: helpers do; helper_code; end;\n" +
173
+ "OR: helpers(MyHelpersModule)"
174
174
  else
175
175
  raise Exceptions::ValidationFailed,
176
176
  "Argument to #helpers must be a module. You gave #{module_name.inspect} (#{module_name.class})"
@@ -87,6 +87,11 @@ class Chef
87
87
  description: "Ensure that sensitive resource data is not logged by the #{ChefUtils::Dist::Infra::CLIENT}.",
88
88
  default: lazy { pfx_password ? true : false }, skip_docs: true
89
89
 
90
+ property :exportable, [TrueClass, FalseClass],
91
+ description: "Ensure that imported pfx certificate is exportable. Please provide 'true' if you want the certificate to be exportable.",
92
+ default: false,
93
+ introduced: "16.8"
94
+
90
95
  action :create do
91
96
  description "Creates or updates a certificate."
92
97
 
@@ -162,8 +167,9 @@ class Chef
162
167
  end
163
168
 
164
169
  def add_pfx_cert
170
+ exportable = new_resource.exportable ? 1 : 0
165
171
  store = ::Win32::Certstore.open(new_resource.store_name)
166
- store.add_pfx(new_resource.source, new_resource.pfx_password)
172
+ store.add_pfx(new_resource.source, new_resource.pfx_password, exportable)
167
173
  end
168
174
 
169
175
  def delete_cert
@@ -131,7 +131,7 @@ class Chef
131
131
  else
132
132
  raise Chef::Exceptions::InvalidResourceSpecification,
133
133
  "The object `#{query_object.inspect}' is not valid for resource collection lookup. " +
134
- "Use a String like `resource_type[resource_name]' or a Chef::Resource object"
134
+ "Use a String like `resource_type[resource_name]' or a Chef::Resource object"
135
135
  end
136
136
  end
137
137
 
@@ -16,36 +16,34 @@
16
16
  # limitations under the License.
17
17
  #
18
18
 
19
- require_relative "../powershell/cmdlet"
19
+ require_relative "../../mixin/powershell_exec"
20
20
 
21
21
  class Chef::Util::DSC
22
22
  class ConfigurationGenerator
23
+ include Chef::Mixin::PowershellExec
24
+
23
25
  def initialize(node, config_directory)
24
26
  @node = node
25
27
  @config_directory = config_directory
26
28
  end
27
29
 
28
- def configuration_document_from_script_code(code, configuration_flags, imports, shellout_flags)
30
+ def configuration_document_from_script_code(code, configuration_flags, imports)
29
31
  Chef::Log.trace("DSC: DSC code:\n '#{code}'")
30
32
  generated_script_path = write_document_generation_script(code, "chef_dsc", imports)
31
33
  begin
32
- configuration_document_from_script_path(generated_script_path, "chef_dsc", configuration_flags, shellout_flags)
34
+ configuration_document_from_script_path(generated_script_path, "chef_dsc", configuration_flags)
33
35
  ensure
34
36
  ::FileUtils.rm(generated_script_path)
35
37
  end
36
38
  end
37
39
 
38
- def configuration_document_from_script_path(script_path, configuration_name, configuration_flags, shellout_flags)
40
+ def configuration_document_from_script_path(script_path, configuration_name, configuration_flags)
39
41
  validate_configuration_name!(configuration_name)
40
42
 
41
- document_generation_cmdlet = Chef::Util::Powershell::Cmdlet.new(
42
- @node,
43
- configuration_document_generation_code(script_path, configuration_name)
44
- )
45
-
46
- merged_configuration_flags = get_merged_configuration_flags!(configuration_flags, configuration_name)
43
+ config_generation_code = configuration_document_generation_code(script_path, configuration_name)
44
+ switches_string = command_switches_string(get_merged_configuration_flags!(configuration_flags, configuration_name))
47
45
 
48
- document_generation_cmdlet.run!(merged_configuration_flags, shellout_flags)
46
+ powershell_exec!("#{config_generation_code} #{switches_string}")
49
47
  configuration_document_location = find_configuration_document(configuration_name)
50
48
 
51
49
  unless configuration_document_location
@@ -59,6 +57,49 @@ class Chef::Util::DSC
59
57
 
60
58
  protected
61
59
 
60
+ def validate_switch_name!(switch_parameter_name)
61
+ unless switch_parameter_name.match?(/\A[A-Za-z]+[_a-zA-Z0-9]*\Z/)
62
+ raise ArgumentError, "`#{switch_parameter_name}` is not a valid PowerShell cmdlet switch parameter name"
63
+ end
64
+ end
65
+
66
+ def escape_parameter_value(parameter_value)
67
+ parameter_value.gsub(/(`|'|"|#)/, '`\1')
68
+ end
69
+
70
+ def escape_string_parameter_value(parameter_value)
71
+ "'#{escape_parameter_value(parameter_value)}'"
72
+ end
73
+
74
+ def command_switches_string(switches)
75
+ command_switches = switches.map do |switch_name, switch_value|
76
+ if switch_name.class != Symbol
77
+ raise ArgumentError, "Invalid type `#{switch_name} `for PowerShell switch '#{switch_name}'. The switch must be specified as a Symbol'"
78
+ end
79
+
80
+ validate_switch_name!(switch_name)
81
+
82
+ switch_argument = ""
83
+ switch_present = true
84
+
85
+ case switch_value
86
+ when Numeric, Float
87
+ switch_argument = switch_value.to_s
88
+ when FalseClass
89
+ switch_present = false
90
+ when TrueClass
91
+ when String
92
+ switch_argument = escape_string_parameter_value(switch_value)
93
+ else
94
+ raise ArgumentError, "Invalid argument type `#{switch_value.class}` specified for PowerShell switch `:#{switch_name}`. Arguments to PowerShell must be of type `String`, `Numeric`, `Float`, `FalseClass`, or `TrueClass`"
95
+ end
96
+
97
+ switch_present ? ["-#{switch_name.to_s.downcase}", switch_argument].join(" ").strip : ""
98
+ end
99
+
100
+ command_switches.join(" ")
101
+ end
102
+
62
103
  # From PowerShell error help for the Configuration language element:
63
104
  # Standard names may only contain letters (a-z, A-Z), numbers (0-9), and underscore (_).
64
105
  # The name may not be null or empty, and should start with a letter.
@@ -75,15 +75,15 @@ class Chef
75
75
  #
76
76
 
77
77
  def self.parse(lcm_output, test_dsc_configuration)
78
+ lcm_output = String(lcm_output).split("\n")
78
79
  test_dsc_configuration ? test_dsc_parser(lcm_output) : what_if_parser(lcm_output)
79
80
  end
80
81
 
81
82
  def self.test_dsc_parser(lcm_output)
82
- lcm_output ||= ""
83
83
  current_resource = {}
84
84
 
85
85
  resources = []
86
- lcm_output.lines.each do |line|
86
+ lcm_output.each do |line|
87
87
  op_action , op_value = line.strip.split(":")
88
88
  op_action&.strip!
89
89
  case op_action
@@ -107,11 +107,10 @@ class Chef
107
107
  end
108
108
 
109
109
  def self.what_if_parser(lcm_output)
110
- lcm_output ||= ""
111
110
  current_resource = {}
112
111
 
113
112
  resources = []
114
- lcm_output.lines.each do |line|
113
+ lcm_output.each do |line|
115
114
  op_action, op_type, info = parse_line(line)
116
115
 
117
116
  case op_action