chef 11.14.6 → 11.16.0.rc.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,178 @@
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
+ class ParseException < RuntimeError; end
28
+
29
+ class Operation
30
+ attr_reader :op_type
31
+ attr_reader :resources
32
+ attr_reader :info
33
+ attr_reader :sets
34
+ attr_reader :tests
35
+
36
+ def initialize(op_type, info)
37
+ @op_type = op_type
38
+ @info = []
39
+ @sets = []
40
+ @tests = []
41
+ @resources = []
42
+ add_info(info)
43
+ end
44
+
45
+ def add_info(info)
46
+ @info << info
47
+ end
48
+
49
+ def add_set(set)
50
+ raise ParseException, "add_set is not allowed in this context. Found #{@op_type}" unless [:resource, :set].include?(@op_type)
51
+ @sets << set
52
+ end
53
+
54
+ def add_test(test)
55
+ raise ParseException, "add_test is not allowed in this context. Found #{@op_type}" unless [:resource, :set].include?(@op_type)
56
+ @tests << test
57
+ end
58
+
59
+ def add_resource(resource)
60
+ raise ParseException, 'add_resource is only allowed to be added to the set op_type' unless @op_type == :set
61
+ @resources << resource
62
+ end
63
+ end
64
+
65
+ # Parses the output from LCM and returns a list of Chef::Util::DSC::ResourceInfo objects
66
+ # that describe how the resources affected the system
67
+ #
68
+ # Example:
69
+ # parse <<-EOF
70
+ # What if: [Machine]: LCM: [Start Set ]
71
+ # What if: [Machine]: LCM: [Start Resource ] [[File]FileToNotBeThere]
72
+ # What if: [Machine]: LCM: [Start Set ] [[File]FileToNotBeThere]
73
+ # What if: [C:\ShouldNotExist.txt] removed
74
+ # What if: [Machine]: LCM: [End Set ] [[File]FileToNotBeThere] in 0.1 seconds
75
+ # What if: [Machine]: LCM: [End Resource ] [[File]FileToNotBeThere]
76
+ # What if: [Machine]: LCM: [End Set ]
77
+ # EOF
78
+ #
79
+ # would return
80
+ #
81
+ # [
82
+ # Chef::Util::DSC::ResourceInfo.new(
83
+ # '[[File]FileToNotBeThere]',
84
+ # true,
85
+ # [
86
+ # '[[File]FileToNotBeThere]',
87
+ # '[C:\Shouldnotexist.txt]',
88
+ # '[[File]FileToNotBeThere] in 0.1 seconds'
89
+ # ]
90
+ # )
91
+ # ]
92
+ #
93
+ def self.parse(lcm_output)
94
+ return [] unless lcm_output
95
+
96
+ stack = Array.new
97
+ popped_op = nil
98
+ lcm_output.lines.each do |line|
99
+ op_action, op_type, info = parse_line(line)
100
+ info.strip! # Because this was formatted for humans
101
+
102
+ # The rules:
103
+ # - For each `start` action, there must be a matching `end` action
104
+ # - `skip` actions do not not do anything (They don't add to the stack)
105
+ case op_action
106
+ when :start
107
+ new_op = Operation.new(op_type, info)
108
+ case op_type
109
+ when :set
110
+ stack[-1].add_set(new_op) if stack[-1]
111
+ when :test
112
+ stack[-1].add_test(new_op)
113
+ when :resource
114
+ while stack[-1].op_type != :set
115
+ Chef::Log.warn("Can't add resource to set...popping until it is allowed.")
116
+ popped_op = stack.pop
117
+ end
118
+ stack[-1].add_resource(new_op)
119
+ else
120
+ Chef::Log.warn("Unknown op_action #{op_action}: Read line #{line}")
121
+ end
122
+ stack.push(new_op)
123
+ when :end
124
+ popped_op = stack.pop
125
+ popped_op.add_info(info)
126
+ while popped_op.op_type != op_type
127
+ Chef::Log::warn("Unmatching end for op_type. Expected op_type=#{op_type}, found op_type=#{popped_op.op_type}. From output:\n#{lcm_output}")
128
+ popped_op = stack.pop
129
+ end
130
+ when :skip
131
+ # We don't really have anything to do here
132
+ when :info
133
+ stack[-1].add_info(info) if stack[-1]
134
+ else
135
+ stack[-1].add_info(line) if stack[-1]
136
+ end
137
+ end
138
+
139
+ op_to_resource_infos(popped_op)
140
+ end
141
+
142
+ def self.parse_line(line)
143
+ if match = line.match(/^.*?:.*?:\s*LCM:\s*\[(.*?)\](.*)/)
144
+ # If the line looks like
145
+ # x: [y]: LCM: [op_action op_type] message
146
+ # extract op_action, op_type, and message
147
+ operation, info = match.captures
148
+ op_action, op_type = operation.strip.split(' ').map {|m| m.downcase.to_sym}
149
+ else
150
+ # If the line looks like
151
+ # x: [y]: message
152
+ # extract message
153
+ match = line.match(/^.*?:.*?: \s+(.*)/)
154
+ op_action = op_type = :info
155
+ info = match.captures[0]
156
+ end
157
+ info.strip! # Because this was formatted for humans
158
+ return [op_action, op_type, info]
159
+ end
160
+ private_class_method :parse_line
161
+
162
+ def self.op_to_resource_infos(op)
163
+ resources = op ? op.resources : []
164
+
165
+ resources.map do |r|
166
+ name = r.info[0]
167
+ sets = r.sets.length > 0
168
+ change_log = r.sets[-1].info if sets
169
+ Chef::Util::DSC::ResourceInfo.new(name, sets, change_log)
170
+ end
171
+ end
172
+ private_class_method :op_to_resource_infos
173
+
174
+ end
175
+ end
176
+ end
177
+ end
178
+ 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
+