From 423ae521db23842ace4c72af7787b8ba86626e23 Mon Sep 17 00:00:00 2001 From: James Pogran Date: Wed, 1 Nov 2017 14:40:15 -0400 Subject: [PATCH] (MODULES-5842) WIP dsc type and invoker --- .../invoke_generic_dsc_resource.ps1.erb | 53 +++++++++++++++++ .../provider/base_dsc_lite/powershell.rb | 22 ++++++- lib/puppet/type/dsc.rb | 58 +++++++++++++++++++ .../puppetlabs/dsc_lite/dsc_type_helpers.rb | 44 ++++++++++++++ 4 files changed, 176 insertions(+), 1 deletion(-) create mode 100644 lib/puppet/provider/base_dsc_lite/invoke_generic_dsc_resource.ps1.erb create mode 100644 lib/puppet/type/dsc.rb diff --git a/lib/puppet/provider/base_dsc_lite/invoke_generic_dsc_resource.ps1.erb b/lib/puppet/provider/base_dsc_lite/invoke_generic_dsc_resource.ps1.erb new file mode 100644 index 00000000..f0553dde --- /dev/null +++ b/lib/puppet/provider/base_dsc_lite/invoke_generic_dsc_resource.ps1.erb @@ -0,0 +1,53 @@ +$script:ErrorActionPreference = 'Stop' +$script:WarningPreference = 'SilentlyContinue' + +function new-pscredential +{ + [CmdletBinding()] + param ( + [parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true)] + [string]$user, + [parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true)] + [string]$password + ) + + $secpasswd = ConvertTo-SecureString $password -AsPlainText -Force + $credentials = New-Object System.Management.Automation.PSCredential ($user, $secpasswd) + return $credentials +} + +$response = @{ + indesiredstate = $false + rebootrequired = $false + errormessage = '' +} + +$invokeParams = @{ +Name = '<%= resource.name %>' +ModuleName = '<%= resource.parameters[:module_name].value %>' +Method = '<%= dsc_invoke_method %>' +Property = @{ +<% provider.dsc_property_param.each do |p| -%><%= format_dsc_lite(p.value) %> +<% end -%> +} +} + +try{ + $result = Invoke-DscResource @invokeParams +}catch{ + $response.errormessage = $_.Exception.Message + return ($response | ConvertTo-Json -Compress) +} + +# keep the switch for when Test passes back changed properties +switch ($invokeParams.Method) { + 'Test' { + $response.indesiredstate = $result.InDesiredState + return ($response | ConvertTo-Json -Compress) + } + 'Set' { + $response.indesiredstate = $true + $response.rebootrequired = $result.RebootRequired + return ($response | ConvertTo-Json -Compress) + } +} diff --git a/lib/puppet/provider/base_dsc_lite/powershell.rb b/lib/puppet/provider/base_dsc_lite/powershell.rb index d5513a31..0c0cba8d 100644 --- a/lib/puppet/provider/base_dsc_lite/powershell.rb +++ b/lib/puppet/provider/base_dsc_lite/powershell.rb @@ -61,6 +61,12 @@ def dsc_parameters end end + def dsc_property_param + resource.parameters_with_value.select{ |pr| pr.name == :dsc_properties }.each do |p| + p.name.to_s =~ /dsc_/ + end + end + def self.template_path File.expand_path(Pathname.new(__FILE__).dirname) end @@ -83,6 +89,7 @@ def exists? version = Facter.value(:powershell_version) Puppet.debug "PowerShell Version: #{version}" script_content = ps_script_content('test') + require 'pry';binding.pry Puppet.debug "\n" + script_content fail DSC_MODULE_POWERSHELL_UPGRADE_MSG if !PuppetX::DscLite::PowerShellManager.compatible_version_of_powershell? @@ -184,6 +191,15 @@ def self.format_dsc_value(dsc_value) end end + def self.format_dsc_lite(dsc_value) + case + when dsc_value.class.name == 'Hash' + dsc_value.collect{|k, v| format_dsc_value(k) + ' = ' + format_dsc_value(v) + ';' }.join("\n") + else + fail "unsupported type #{dsc_value.class} of value '#{dsc_value}'" + end + end + def self.escape_quotes(text) text.gsub("'", "''") end @@ -195,7 +211,11 @@ def ps_script_content(mode) def self.ps_script_content(mode, resource, provider) dsc_invoke_method = mode @param_hash = resource - template = ERB.new(File.new(template_path + "/invoke_dsc_resource.ps1.erb").read, nil, '-') + # TODO: double-check that 'type' is the right thing to check + template_name = resource.type == 'dsc' ? + 'invoke_generic_dsc_resource.ps1.erb' : + 'invoke_dsc_resource.ps1.erb' + template = ERB.new(File.new(template_path + "/#{template_name}").read, nil, '-') template.result(binding) end end diff --git a/lib/puppet/type/dsc.rb b/lib/puppet/type/dsc.rb new file mode 100644 index 00000000..54d06450 --- /dev/null +++ b/lib/puppet/type/dsc.rb @@ -0,0 +1,58 @@ +require 'pathname' + +Puppet::Type.newtype(:dsc) do + require Pathname.new(__FILE__).dirname + '../../' + 'puppet/type/base_dsc' + require Pathname.new(__FILE__).dirname + '../../puppet_x/puppetlabs/dsc_type_helpers' + + ensurable do + newvalue(:exists?) { provider.exists? } + newvalue(:present) { provider.create } + newvalue(:absent) { provider.destroy } + defaultto { :present } + end + + newparam(:name, :namevar => true ) do + end + + newparam(:module_name) do + def mof_type; 'string' end + def mof_is_embedded?; false end + desc "wakka_module_name" + isrequired + validate do |value| + unless value.kind_of?(String) + fail("Invalid value '#{value}'. Should be a string") + end + end + end + + newparam(:dsc_name) do + def mof_type; 'string' end + def mof_is_embedded?; false end + desc "Name" + isrequired + validate do |value| + unless value.kind_of?(String) + fail("Invalid value '#{value}'. Should be a string") + end + end + end + + newparam(:dsc_properties, :array_matching => :all) do + desc "Properties" + def mof_type; 'hash' end + def mof_is_embedded?; false end + validate do |value| + unless value.kind_of?(Array) || value.kind_of?(Hash) + fail("Invalid value '#{value}'. Should be an array of hashes or a hash") + end + end + end +end + +Puppet::Type.type(:dsc).provide :powershell, :parent => Puppet::Type.type(:base_dsc).provider(:powershell) do + confine :true => (Gem::Version.new(Facter.value(:powershell_version)) >= Gem::Version.new('5.0.10586.117')) + defaultfor :operatingsystem => :windows + + mk_resource_methods +end diff --git a/lib/puppet_x/puppetlabs/dsc_lite/dsc_type_helpers.rb b/lib/puppet_x/puppetlabs/dsc_lite/dsc_type_helpers.rb index fb8c13a9..d4783a1e 100644 --- a/lib/puppet_x/puppetlabs/dsc_lite/dsc_type_helpers.rb +++ b/lib/puppet_x/puppetlabs/dsc_lite/dsc_type_helpers.rb @@ -159,6 +159,50 @@ def self.ensure_reboot_relationship(resource, pending_relationships) pending_relationships end + + def self.validate_MSFT_xWebBindingInformation(mof_type_map, name, value) + required = ['protocol'] + allowed = ['bindinginformation','ipaddress','port','hostname','certificatethumbprint','certificatestorename','sslflags'] + lowkey_hash = Hash[value.map { |k, v| [k.to_s.downcase, v] }] + + missing = required - lowkey_hash.keys + unless missing.empty? + fail "#{name} is missing the following required keys: #{missing.join(',')}" + end + + extraneous = lowkey_hash.keys - required - allowed + unless extraneous.empty? + fail "#{name} includes invalid keys: #{extraneous.join(',')}" + end + + lowkey_hash.keys.each do |key| + if lowkey_hash[key] + validate_mof_type(mof_type_map[key], 'MSFT_xWebBindingInformation', key, lowkey_hash[key]) + end + end + end + + def self.validate_MSFT_xWebAuthenticationInformation(mof_type_map, name, value) + required = [] + allowed = ['anonymous','basic','digest','windows'] + lowkey_hash = Hash[value.map { |k, v| [k.to_s.downcase, v] }] + + missing = required - lowkey_hash.keys + unless missing.empty? + fail "#{name} is missing the following required keys: #{missing.join(',')}" + end + + extraneous = lowkey_hash.keys - required - allowed + unless extraneous.empty? + fail "#{name} includes invalid keys: #{extraneous.join(',')}" + end + + lowkey_hash.keys.each do |key| + if lowkey_hash[key] + validate_mof_type(mof_type_map[key], 'MSFT_xWebAuthenticationInformation', key, lowkey_hash[key]) + end + end + end end end end