# # Author:: Adam Edwards () # # Copyright:: Copyright 2014-2016, Chef Software, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/util/powershell/cmdlet" require "chef/util/dsc/lcm_output_parser" class Chef::Util::DSC class LocalConfigurationManager def initialize(node, configuration_path) @node = node @configuration_path = configuration_path clear_execution_time end def test_configuration(configuration_document, shellout_flags) status = run_configuration_cmdlet(configuration_document, false, shellout_flags) log_what_if_exception(status.stderr) unless status.succeeded? configuration_update_required?(status.return_value) end def set_configuration(configuration_document, shellout_flags) run_configuration_cmdlet(configuration_document, true, shellout_flags) end def last_operation_execution_time_seconds if @operation_start_time && @operation_end_time @operation_end_time - @operation_start_time end end private def run_configuration_cmdlet(configuration_document, apply_configuration, shellout_flags) Chef::Log.debug("DSC: Calling DSC Local Config Manager to #{apply_configuration ? "set" : "test"} configuration document.") test_only_parameters = ! apply_configuration ? "-whatif; if (! $?) { exit 1 }" : "" start_operation_timing command_code = lcm_command_code(@configuration_path, test_only_parameters) status = nil begin save_configuration_document(configuration_document) cmdlet = ::Chef::Util::Powershell::Cmdlet.new(@node, "#{command_code}") if apply_configuration status = cmdlet.run!({}, shellout_flags) else status = cmdlet.run({}, shellout_flags) end ensure end_operation_timing remove_configuration_document if last_operation_execution_time_seconds Chef::Log.debug("DSC: DSC operation completed in #{last_operation_execution_time_seconds} seconds.") end end Chef::Log.debug("DSC: Completed call to DSC Local Config Manager") status end def lcm_command_code(configuration_path, test_only_parameters) <<-EOH $ProgressPreference = 'SilentlyContinue';start-dscconfiguration -path #{@configuration_path} -wait -erroraction 'continue' -force #{test_only_parameters} EOH end def log_what_if_exception(what_if_exception_output) if whatif_not_supported?(what_if_exception_output) # LCM returns an error if any of the resources do not support the opptional What-If Chef::Log::warn("Received error while testing configuration due to resource not supporting 'WhatIf'") elsif dsc_module_import_failure?(what_if_exception_output) 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+/, ' ')}") else Chef::Log::warn("Received error while testing configuration:\n#{what_if_exception_output.gsub(/\s+/, ' ')}") end end def whatif_not_supported?(what_if_exception_output) !! (what_if_exception_output.gsub(/[\r\n]+/, "").gsub(/\s+/, " ") =~ /A parameter cannot be found that matches parameter name 'Whatif'/i) end def dsc_module_import_failure?(what_if_output) !! (what_if_output =~ /\sCimException/ && what_if_output =~ /ProviderOperationExecutionFailure/ && what_if_output =~ /\smodule\s+is\s+installed/) end def configuration_update_required?(what_if_output) Chef::Log.debug("DSC: DSC returned the following '-whatif' output from test operation:\n#{what_if_output}") begin Parser::parse(what_if_output) rescue Chef::Exceptions::LCMParser => e Chef::Log::warn("Could not parse LCM output: #{e}") [Chef::Util::DSC::ResourceInfo.new("Unknown DSC Resources", true, ["Unknown changes because LCM output was not parsable."])] end end def save_configuration_document(configuration_document) ::FileUtils.mkdir_p(@configuration_path) ::File.open(configuration_document_path, "wb") do | file | file.write(configuration_document) end end def remove_configuration_document ::FileUtils.rm(configuration_document_path) end def configuration_document_path File.join(@configuration_path,"..mof") end def clear_execution_time @operation_start_time = nil @operation_end_time = nil end def start_operation_timing clear_execution_time @operation_start_time = Time.now end def end_operation_timing @operation_end_time = Time.now end end end