chef 11.14.6-x86-mingw32 → 11.16.0-x86-mingw32
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/chef/exceptions.rb +4 -0
- data/lib/chef/mixin/windows_architecture_helper.rb +16 -0
- data/lib/chef/platform/query_helpers.rb +5 -1
- data/lib/chef/provider/dsc_script.rb +148 -0
- data/lib/chef/provider/user/dscl.rb +32 -28
- data/lib/chef/providers.rb +1 -0
- data/lib/chef/resource/dsc_script.rb +140 -0
- data/lib/chef/resources.rb +1 -0
- data/lib/chef/util/dsc/configuration_generator.rb +115 -0
- data/lib/chef/util/dsc/lcm_output_parser.rb +133 -0
- data/lib/chef/util/dsc/local_configuration_manager.rb +137 -0
- data/lib/chef/util/dsc/resource_info.rb +26 -0
- data/lib/chef/util/path_helper.rb +2 -2
- data/lib/chef/util/powershell/cmdlet.rb +136 -0
- data/lib/chef/util/powershell/cmdlet_result.rb +46 -0
- data/lib/chef/version.rb +1 -1
- data/spec/functional/resource/dsc_script_spec.rb +337 -0
- data/spec/functional/resource/group_spec.rb +5 -1
- data/spec/functional/util/powershell/cmdlet_spec.rb +114 -0
- data/spec/spec_helper.rb +3 -0
- data/spec/support/platform_helpers.rb +24 -0
- data/spec/unit/platform/query_helpers_spec.rb +23 -0
- data/spec/unit/provider/dsc_script_spec.rb +145 -0
- data/spec/unit/provider/user/dscl_spec.rb +2 -1
- data/spec/unit/resource/dsc_script_spec.rb +127 -0
- data/spec/unit/util/dsc/configuration_generator_spec.rb +171 -0
- data/spec/unit/util/dsc/lcm_output_parser_spec.rb +169 -0
- data/spec/unit/util/dsc/local_configuration_manager_spec.rb +134 -0
- data/spec/unit/util/powershell/cmdlet_spec.rb +106 -0
- metadata +20 -4
data/lib/chef/resources.rb
CHANGED
@@ -28,6 +28,7 @@ require 'chef/resource/deploy'
|
|
28
28
|
require 'chef/resource/deploy_revision'
|
29
29
|
require 'chef/resource/directory'
|
30
30
|
require 'chef/resource/dpkg_package'
|
31
|
+
require 'chef/resource/dsc_script'
|
31
32
|
require 'chef/resource/easy_install_package'
|
32
33
|
require 'chef/resource/env'
|
33
34
|
require 'chef/resource/erl_call'
|
@@ -0,0 +1,115 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Adam Edwards (<adamed@getchef.com>)
|
3
|
+
#
|
4
|
+
# Copyright:: 2014, Chef Software, Inc.
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
|
19
|
+
require 'chef/util/powershell/cmdlet'
|
20
|
+
|
21
|
+
class Chef::Util::DSC
|
22
|
+
class ConfigurationGenerator
|
23
|
+
def initialize(node, config_directory)
|
24
|
+
@node = node
|
25
|
+
@config_directory = config_directory
|
26
|
+
end
|
27
|
+
|
28
|
+
def configuration_document_from_script_code(code, configuration_flags, shellout_flags)
|
29
|
+
Chef::Log.debug("DSC: DSC code:\n '#{code}'")
|
30
|
+
generated_script_path = write_document_generation_script(code, 'chef_dsc')
|
31
|
+
begin
|
32
|
+
configuration_document_from_script_path(generated_script_path, 'chef_dsc', configuration_flags, shellout_flags)
|
33
|
+
ensure
|
34
|
+
::FileUtils.rm(generated_script_path)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def configuration_document_from_script_path(script_path, configuration_name, configuration_flags, shellout_flags)
|
39
|
+
validate_configuration_name!(configuration_name)
|
40
|
+
|
41
|
+
document_generation_cmdlet = Chef::Util::Powershell::Cmdlet.new(
|
42
|
+
@node,
|
43
|
+
configuration_document_generation_code(script_path, configuration_name))
|
44
|
+
|
45
|
+
merged_configuration_flags = get_merged_configuration_flags!(configuration_flags, configuration_name)
|
46
|
+
|
47
|
+
document_generation_cmdlet.run!(merged_configuration_flags, shellout_flags)
|
48
|
+
configuration_document_location = find_configuration_document(configuration_name)
|
49
|
+
|
50
|
+
if ! configuration_document_location
|
51
|
+
raise RuntimeError, "No DSC configuration for '#{configuration_name}' was generated from supplied DSC script"
|
52
|
+
end
|
53
|
+
|
54
|
+
configuration_document = get_configuration_document(configuration_document_location)
|
55
|
+
::FileUtils.rm_rf(configuration_document_location)
|
56
|
+
configuration_document
|
57
|
+
end
|
58
|
+
|
59
|
+
protected
|
60
|
+
|
61
|
+
# From PowerShell error help for the Configuration language element:
|
62
|
+
# Standard names may only contain letters (a-z, A-Z), numbers (0-9), and underscore (_).
|
63
|
+
# The name may not be null or empty, and should start with a letter.
|
64
|
+
def validate_configuration_name!(configuration_name)
|
65
|
+
if !!(configuration_name =~ /\A[A-Za-z]+[_a-zA-Z0-9]*\Z/) == false
|
66
|
+
raise ArgumentError, 'Configuration `#{configuration_name}` is not a valid PowerShell cmdlet name'
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def get_merged_configuration_flags!(configuration_flags, configuration_name)
|
71
|
+
merged_configuration_flags = { :outputpath => configuration_document_directory(configuration_name) }
|
72
|
+
if configuration_flags
|
73
|
+
configuration_flags.map do | switch, value |
|
74
|
+
if merged_configuration_flags.key?(switch.to_s.downcase.to_sym)
|
75
|
+
raise ArgumentError, "The `flags` attribute for the dsc_script resource contained a command line switch :#{switch.to_s} that is disallowed."
|
76
|
+
end
|
77
|
+
merged_configuration_flags[switch.to_s.downcase.to_sym] = value
|
78
|
+
end
|
79
|
+
end
|
80
|
+
merged_configuration_flags
|
81
|
+
end
|
82
|
+
|
83
|
+
def configuration_code(code, configuration_name)
|
84
|
+
"$ProgressPreference = 'SilentlyContinue';Configuration '#{configuration_name}'\n{\n\tnode 'localhost'\n{\n\t#{code}\n}}\n"
|
85
|
+
end
|
86
|
+
|
87
|
+
def configuration_document_generation_code(configuration_script, configuration_name)
|
88
|
+
". '#{configuration_script}';#{configuration_name}"
|
89
|
+
end
|
90
|
+
|
91
|
+
def write_document_generation_script(code, configuration_name)
|
92
|
+
script_path = "#{@config_directory}/chef_dsc_config.ps1"
|
93
|
+
::File.open(script_path, 'wt') do | script |
|
94
|
+
script.write(configuration_code(code, configuration_name))
|
95
|
+
end
|
96
|
+
script_path
|
97
|
+
end
|
98
|
+
|
99
|
+
def find_configuration_document(configuration_name)
|
100
|
+
document_directory = configuration_document_directory(configuration_name)
|
101
|
+
document_file_name = ::Dir.entries(document_directory).find { | path | path =~ /.*.mof/ }
|
102
|
+
::File.join(document_directory, document_file_name) if document_file_name
|
103
|
+
end
|
104
|
+
|
105
|
+
def configuration_document_directory(configuration_name)
|
106
|
+
::File.join(@config_directory, configuration_name)
|
107
|
+
end
|
108
|
+
|
109
|
+
def get_configuration_document(document_path)
|
110
|
+
::File.open(document_path, 'rb') do | file |
|
111
|
+
file.read
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Jay Mundrawala (<jdm@getchef.com>)
|
3
|
+
#
|
4
|
+
# Copyright:: 2014, Chef Software, Inc.
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
|
19
|
+
require 'chef/log'
|
20
|
+
require 'chef/util/dsc/resource_info'
|
21
|
+
|
22
|
+
class Chef
|
23
|
+
class Util
|
24
|
+
class DSC
|
25
|
+
class LocalConfigurationManager
|
26
|
+
module Parser
|
27
|
+
# Parses the output from LCM and returns a list of Chef::Util::DSC::ResourceInfo objects
|
28
|
+
# that describe how the resources affected the system
|
29
|
+
#
|
30
|
+
# Example:
|
31
|
+
# parse <<-EOF
|
32
|
+
# What if: [Machine]: LCM: [Start Set ]
|
33
|
+
# What if: [Machine]: LCM: [Start Resource ] [[File]FileToNotBeThere]
|
34
|
+
# What if: [Machine]: LCM: [Start Set ] [[File]FileToNotBeThere]
|
35
|
+
# What if: [C:\ShouldNotExist.txt] removed
|
36
|
+
# What if: [Machine]: LCM: [End Set ] [[File]FileToNotBeThere] in 0.1 seconds
|
37
|
+
# What if: [Machine]: LCM: [End Resource ] [[File]FileToNotBeThere]
|
38
|
+
# What if: [Machine]: LCM: [End Set ]
|
39
|
+
# EOF
|
40
|
+
#
|
41
|
+
# would return
|
42
|
+
#
|
43
|
+
# [
|
44
|
+
# Chef::Util::DSC::ResourceInfo.new(
|
45
|
+
# '[[File]FileToNotBeThere]',
|
46
|
+
# true,
|
47
|
+
# [
|
48
|
+
# '[[File]FileToNotBeThere]',
|
49
|
+
# '[C:\Shouldnotexist.txt]',
|
50
|
+
# '[[File]FileToNotBeThere] in 0.1 seconds'
|
51
|
+
# ]
|
52
|
+
# )
|
53
|
+
# ]
|
54
|
+
#
|
55
|
+
def self.parse(lcm_output)
|
56
|
+
return [] unless lcm_output
|
57
|
+
|
58
|
+
current_resource = Hash.new
|
59
|
+
|
60
|
+
resources = []
|
61
|
+
lcm_output.lines.each do |line|
|
62
|
+
op_action, op_type, info = parse_line(line)
|
63
|
+
|
64
|
+
case op_action
|
65
|
+
when :start
|
66
|
+
case op_type
|
67
|
+
when :set
|
68
|
+
if current_resource[:name]
|
69
|
+
current_resource[:context] = :logging
|
70
|
+
current_resource[:logs] = [info]
|
71
|
+
end
|
72
|
+
when :resource
|
73
|
+
if current_resource[:name]
|
74
|
+
resources.push(current_resource)
|
75
|
+
end
|
76
|
+
current_resource = {:name => info}
|
77
|
+
else
|
78
|
+
Chef::Log.debug("Ignoring op_action #{op_action}: Read line #{line}")
|
79
|
+
end
|
80
|
+
when :end
|
81
|
+
# Make sure we log the last line
|
82
|
+
if current_resource[:context] == :logging and info.include? current_resource[:name]
|
83
|
+
current_resource[:logs].push(info)
|
84
|
+
end
|
85
|
+
current_resource[:context] = nil
|
86
|
+
when :skip
|
87
|
+
current_resource[:skipped] = true
|
88
|
+
when :info
|
89
|
+
if current_resource[:context] == :logging
|
90
|
+
current_resource[:logs].push(info)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
if current_resource[:name]
|
96
|
+
resources.push(current_resource)
|
97
|
+
end
|
98
|
+
|
99
|
+
build_resource_info(resources)
|
100
|
+
end
|
101
|
+
|
102
|
+
def self.parse_line(line)
|
103
|
+
if match = line.match(/^.*?:.*?:\s*LCM:\s*\[(.*?)\](.*)/)
|
104
|
+
# If the line looks like
|
105
|
+
# What If: [machinename]: LCM: [op_action op_type] message
|
106
|
+
# extract op_action, op_type, and message
|
107
|
+
operation, info = match.captures
|
108
|
+
op_action, op_type = operation.strip.split(' ').map {|m| m.downcase.to_sym}
|
109
|
+
else
|
110
|
+
op_action = op_type = :info
|
111
|
+
if match = line.match(/^.*?:.*?: \s+(.*)/)
|
112
|
+
info = match.captures[0]
|
113
|
+
else
|
114
|
+
info = line
|
115
|
+
end
|
116
|
+
end
|
117
|
+
info.strip! # Because this was formatted for humans
|
118
|
+
return [op_action, op_type, info]
|
119
|
+
end
|
120
|
+
private_class_method :parse_line
|
121
|
+
|
122
|
+
def self.build_resource_info(resources)
|
123
|
+
resources.map do |r|
|
124
|
+
Chef::Util::DSC::ResourceInfo.new(r[:name], !r[:skipped], r[:logs])
|
125
|
+
end
|
126
|
+
end
|
127
|
+
private_class_method :build_resource_info
|
128
|
+
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Adam Edwards (<adamed@getchef.com>)
|
3
|
+
#
|
4
|
+
# Copyright:: 2014, Chef Software, Inc.
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
|
19
|
+
require 'chef/util/powershell/cmdlet'
|
20
|
+
require 'chef/util/dsc/lcm_output_parser'
|
21
|
+
|
22
|
+
class Chef::Util::DSC
|
23
|
+
class LocalConfigurationManager
|
24
|
+
def initialize(node, configuration_path)
|
25
|
+
@node = node
|
26
|
+
@configuration_path = configuration_path
|
27
|
+
clear_execution_time
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_configuration(configuration_document)
|
31
|
+
status = run_configuration_cmdlet(configuration_document)
|
32
|
+
handle_what_if_exception!(status.stderr) unless status.succeeded?
|
33
|
+
configuration_update_required?(status.return_value)
|
34
|
+
end
|
35
|
+
|
36
|
+
def set_configuration(configuration_document)
|
37
|
+
run_configuration_cmdlet(configuration_document, true)
|
38
|
+
end
|
39
|
+
|
40
|
+
def last_operation_execution_time_seconds
|
41
|
+
if @operation_start_time && @operation_end_time
|
42
|
+
@operation_end_time - @operation_start_time
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def run_configuration_cmdlet(configuration_document, apply_configuration = false)
|
49
|
+
Chef::Log.debug("DSC: Calling DSC Local Config Manager to #{apply_configuration ? "set" : "test"} configuration document.")
|
50
|
+
test_only_parameters = ! apply_configuration ? '-whatif; if (! $?) { exit 1 }' : ''
|
51
|
+
|
52
|
+
start_operation_timing
|
53
|
+
command_code = lcm_command_code(@configuration_path, test_only_parameters)
|
54
|
+
status = nil
|
55
|
+
|
56
|
+
begin
|
57
|
+
save_configuration_document(configuration_document)
|
58
|
+
cmdlet = ::Chef::Util::Powershell::Cmdlet.new(@node, "#{command_code}")
|
59
|
+
if apply_configuration
|
60
|
+
status = cmdlet.run!
|
61
|
+
else
|
62
|
+
status = cmdlet.run
|
63
|
+
end
|
64
|
+
ensure
|
65
|
+
end_operation_timing
|
66
|
+
remove_configuration_document
|
67
|
+
if last_operation_execution_time_seconds
|
68
|
+
Chef::Log.debug("DSC: DSC operation completed in #{last_operation_execution_time_seconds} seconds.")
|
69
|
+
end
|
70
|
+
end
|
71
|
+
Chef::Log.debug("DSC: Completed call to DSC Local Config Manager")
|
72
|
+
status
|
73
|
+
end
|
74
|
+
|
75
|
+
def lcm_command_code(configuration_path, test_only_parameters)
|
76
|
+
<<-EOH
|
77
|
+
$ProgressPreference = 'SilentlyContinue';start-dscconfiguration -path #{@configuration_path} -wait -erroraction 'continue' -force #{test_only_parameters}
|
78
|
+
EOH
|
79
|
+
end
|
80
|
+
|
81
|
+
def handle_what_if_exception!(what_if_exception_output)
|
82
|
+
if what_if_exception_output.gsub(/\s+/, ' ') =~ /A parameter cannot be found that matches parameter name 'Whatif'/i
|
83
|
+
# LCM returns an error if any of the resources do not support the opptional What-If
|
84
|
+
Chef::Log::warn("Received error while testing configuration due to resource not supporting 'WhatIf'")
|
85
|
+
elsif output_has_dsc_module_failure?(what_if_exception_output)
|
86
|
+
Chef::Log::warn("Received error while testing configuration due to a module for an imported resource possibly not being fully installed:\n#{what_if_exception_output.gsub(/\s+/, ' ')}")
|
87
|
+
else
|
88
|
+
raise Chef::Exceptions::PowershellCmdletException, "Powershell Cmdlet failed: #{what_if_exception_output.gsub(/\s+/, ' ')}"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def output_has_dsc_module_failure?(what_if_output)
|
93
|
+
!! (what_if_output =~ /\sCimException/ &&
|
94
|
+
what_if_output =~ /ProviderOperationExecutionFailure/ &&
|
95
|
+
what_if_output =~ /\smodule\s+is\s+installed/)
|
96
|
+
end
|
97
|
+
|
98
|
+
def configuration_update_required?(what_if_output)
|
99
|
+
Chef::Log.debug("DSC: DSC returned the following '-whatif' output from test operation:\n#{what_if_output}")
|
100
|
+
begin
|
101
|
+
Parser::parse(what_if_output)
|
102
|
+
rescue Chef::Util::DSC::LocalConfigurationManager::Parser => e
|
103
|
+
Chef::Log::warn("Could not parse LCM output: #{e}")
|
104
|
+
[Chef::Util::DSC::ResourceInfo.new('Unknown DSC Resources', true, ['Unknown changes because LCM output was not parsable.'])]
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def save_configuration_document(configuration_document)
|
109
|
+
::FileUtils.mkdir_p(@configuration_path)
|
110
|
+
::File.open(configuration_document_path, 'wb') do | file |
|
111
|
+
file.write(configuration_document)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def remove_configuration_document
|
116
|
+
::FileUtils.rm(configuration_document_path)
|
117
|
+
end
|
118
|
+
|
119
|
+
def configuration_document_path
|
120
|
+
File.join(@configuration_path,'..mof')
|
121
|
+
end
|
122
|
+
|
123
|
+
def clear_execution_time
|
124
|
+
@operation_start_time = nil
|
125
|
+
@operation_end_time = nil
|
126
|
+
end
|
127
|
+
|
128
|
+
def start_operation_timing
|
129
|
+
clear_execution_time
|
130
|
+
@operation_start_time = Time.now
|
131
|
+
end
|
132
|
+
|
133
|
+
def end_operation_timing
|
134
|
+
@operation_end_time = Time.now
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
|
2
|
+
class Chef
|
3
|
+
class Util
|
4
|
+
class DSC
|
5
|
+
class ResourceInfo
|
6
|
+
# The name is the text following [Start Set]
|
7
|
+
attr_reader :name
|
8
|
+
|
9
|
+
# A list of all log messages between [Start Set] and [End Set].
|
10
|
+
# Each line is an element in the list.
|
11
|
+
attr_reader :change_log
|
12
|
+
|
13
|
+
def initialize(name, sets, change_log)
|
14
|
+
@name = name
|
15
|
+
@sets = sets
|
16
|
+
@change_log = change_log || []
|
17
|
+
end
|
18
|
+
|
19
|
+
# Does this resource change the state of the system?
|
20
|
+
def changes_state?
|
21
|
+
@sets
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -32,7 +32,7 @@ class Chef
|
|
32
32
|
Chef::Log.error(msg)
|
33
33
|
raise Chef::Exceptions::ValidationFailed, msg
|
34
34
|
end
|
35
|
-
|
35
|
+
|
36
36
|
if windows_max_length_exceeded?(path)
|
37
37
|
Chef::Log.debug("Path '#{path}' is longer than #{WIN_MAX_PATH}, prefixing with'\\\\?\\'")
|
38
38
|
path.insert(0, "\\\\?\\")
|
@@ -50,7 +50,7 @@ class Chef
|
|
50
50
|
return true
|
51
51
|
end
|
52
52
|
end
|
53
|
-
|
53
|
+
|
54
54
|
false
|
55
55
|
end
|
56
56
|
|
@@ -0,0 +1,136 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Adam Edwards (<adamed@getchef.com>)
|
3
|
+
#
|
4
|
+
# Copyright:: 2014, Chef Software, Inc.
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
|
19
|
+
require 'mixlib/shellout'
|
20
|
+
require 'chef/mixin/windows_architecture_helper'
|
21
|
+
require 'chef/util/powershell/cmdlet_result'
|
22
|
+
|
23
|
+
class Chef::Util::Powershell
|
24
|
+
class Cmdlet
|
25
|
+
def initialize(node, cmdlet, output_format=nil, output_format_options={})
|
26
|
+
@output_format = output_format
|
27
|
+
@node = node
|
28
|
+
|
29
|
+
case output_format
|
30
|
+
when nil
|
31
|
+
@json_format = false
|
32
|
+
when :json
|
33
|
+
@json_format = true
|
34
|
+
when :text
|
35
|
+
@json_format = false
|
36
|
+
when :object
|
37
|
+
@json_format = true
|
38
|
+
else
|
39
|
+
raise ArgumentError, "Invalid output format #{output_format.to_s} specified"
|
40
|
+
end
|
41
|
+
|
42
|
+
@cmdlet = cmdlet
|
43
|
+
@output_format_options = output_format_options
|
44
|
+
end
|
45
|
+
|
46
|
+
attr_reader :output_format
|
47
|
+
|
48
|
+
def run(switches={}, execution_options={}, *arguments)
|
49
|
+
arguments_string = arguments.join(' ')
|
50
|
+
|
51
|
+
switches_string = command_switches_string(switches)
|
52
|
+
|
53
|
+
json_depth = 5
|
54
|
+
|
55
|
+
if @json_format && @output_format_options.has_key?(:depth)
|
56
|
+
json_depth = @output_format_options[:depth]
|
57
|
+
end
|
58
|
+
|
59
|
+
json_command = @json_format ? " | convertto-json -compress -depth #{json_depth}" : ""
|
60
|
+
command_string = "powershell.exe -executionpolicy bypass -noprofile -noninteractive -command \"trap [Exception] {write-error -exception ($_.Exception.Message);exit 1};#{@cmdlet} #{switches_string} #{arguments_string}#{json_command}\";if ( ! $? ) { exit 1 }"
|
61
|
+
|
62
|
+
augmented_options = {:returns => [0], :live_stream => false}.merge(execution_options)
|
63
|
+
command = Mixlib::ShellOut.new(command_string, augmented_options)
|
64
|
+
|
65
|
+
os_architecture = "#{ENV['PROCESSOR_ARCHITEW6432']}" == 'AMD64' ? :x86_64 : :i386
|
66
|
+
|
67
|
+
status = nil
|
68
|
+
|
69
|
+
with_os_architecture(@node) do
|
70
|
+
status = command.run_command
|
71
|
+
end
|
72
|
+
|
73
|
+
CmdletResult.new(status, @output_format)
|
74
|
+
end
|
75
|
+
|
76
|
+
def run!(switches={}, execution_options={}, *arguments)
|
77
|
+
result = run(switches, execution_options, arguments)
|
78
|
+
|
79
|
+
if ! result.succeeded?
|
80
|
+
raise Chef::Exceptions::PowershellCmdletException, "Powershell Cmdlet failed: #{result.stderr}"
|
81
|
+
end
|
82
|
+
|
83
|
+
result
|
84
|
+
end
|
85
|
+
|
86
|
+
protected
|
87
|
+
|
88
|
+
include Chef::Mixin::WindowsArchitectureHelper
|
89
|
+
|
90
|
+
def validate_switch_name!(switch_parameter_name)
|
91
|
+
if !!(switch_parameter_name.to_s =~ /\A[A-Za-z]+[_a-zA-Z0-9]*\Z/) == false
|
92
|
+
raise ArgumentError, "`#{switch_parameter_name}` is not a valid PowerShell cmdlet switch parameter name"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def escape_parameter_value(parameter_value)
|
97
|
+
parameter_value.gsub(/(`|'|"|#)/,'`\1')
|
98
|
+
end
|
99
|
+
|
100
|
+
def escape_string_parameter_value(parameter_value)
|
101
|
+
"'#{escape_parameter_value(parameter_value)}'"
|
102
|
+
end
|
103
|
+
|
104
|
+
def command_switches_string(switches)
|
105
|
+
command_switches = switches.map do | switch_name, switch_value |
|
106
|
+
if switch_name.class != Symbol
|
107
|
+
raise ArgumentError, "Invalid type `#{switch_name} `for PowerShell switch '#{switch_name.to_s}'. The switch must be specified as a Symbol'"
|
108
|
+
end
|
109
|
+
|
110
|
+
validate_switch_name!(switch_name)
|
111
|
+
|
112
|
+
switch_argument = ''
|
113
|
+
switch_present = true
|
114
|
+
|
115
|
+
case switch_value
|
116
|
+
when Numeric
|
117
|
+
switch_argument = switch_value.to_s
|
118
|
+
when Float
|
119
|
+
switch_argument = switch_value.to_s
|
120
|
+
when FalseClass
|
121
|
+
switch_present = false
|
122
|
+
when TrueClass
|
123
|
+
when String
|
124
|
+
switch_argument = escape_string_parameter_value(switch_value)
|
125
|
+
else
|
126
|
+
raise ArgumentError, "Invalid argument type `#{switch_value.class}` specified for PowerShell switch `:#{switch_name.to_s}`. Arguments to PowerShell must be of type `String`, `Numeric`, `Float`, `FalseClass`, or `TrueClass`"
|
127
|
+
end
|
128
|
+
|
129
|
+
switch_present ? ["-#{switch_name.to_s.downcase}", switch_argument].join(' ').strip : ''
|
130
|
+
end
|
131
|
+
|
132
|
+
command_switches.join(' ')
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|