Class: Kitchen::Verifier::Pester
- Inherits:
-
Base
- Object
- Base
- Kitchen::Verifier::Pester
- Defined in:
- lib/kitchen/verifier/pester.rb
Overview
A Test Kitchen verifier that runs Pester tests on the system under test.
The verifier does almost all of its work by generating PowerShell source locally and handing it to the transport to execute remotely. Each command hook -- #install_command, #init_command, #prepare_command and #run_command -- returns a script string rather than performing the work itself.
Test files, helper files and any folders named in copy_folders are
staged into a sandbox by #create_sandbox, shipped to the instance, and
discovered there through $Env:PSModulePath.
Instance Method Summary collapse
-
#absolute_test_folder ⇒ String
private
Resolves
test_folderto an absolute path, descending into anintegrationsubfolder when one exists. -
#call(state) ⇒ void
Runs the verifier on the instance, retrieving the test results even when the run fails.
-
#copy_if_src_exists(src_to_validate, destination) ⇒ void
private
Copies a folder recursively, preserving its layers.
-
#create_sandbox ⇒ void
Creates a temporary directory on the local workstation into which verifier related files and directories can be copied or created.
-
#download_test_files(state) ⇒ void
private
Retrieves the configured result files from the instance.
-
#get_powershell_modules_from_nugetapi ⇒ Array<String>?
private
Returns the commands that install the bootstrap modules straight from a NuGet feed.
-
#helper_files ⇒ Array<String>
private
Returns an Array of common helper filenames currently residing on the local workstation.
-
#init_command ⇒ String
Generates a command string which will perform any data initialization or configuration required after the verifier software is installed but before the sandbox has been transferred to the instance.
-
#initialize(config = {}) ⇒ Pester
constructor
Creates a new Verifier object using the provided configuration data which will be merged with any default configuration.
-
#install_command ⇒ String
Generates a command string which will install and configure the verifier software on an instance.
-
#install_command_script ⇒ String
private
Returns the PowerShell that prepares the SUT once the sandbox has been transferred.
-
#install_modules_from_gallery ⇒ Array<String>
private
returns a piece of PS scriptblock for each Module to install from gallery that has been specified in install_modules config.
-
#install_pester ⇒ String
private
Returns the string command set the PSGallery as trusted, and Install Pester from gallery based on the params from Pester_install_params config.
-
#invoke_pester_scriptblock ⇒ String
private
Returns the PowerShell that imports Pester and invokes it.
-
#list_files(path) ⇒ Array<String>
private
Creates a flat list of the files contained in a folder.
-
#pad(depth = 0) ⇒ String
private
Returns a run of spaces of the given width, used to pad messages and indent generated PowerShell hashtables.
-
#prepare_command ⇒ String
Generates a command string which will perform any commands or configuration required just before the main verifier run command but after the sandbox has been transferred to the instance.
-
#prepare_copy_folders ⇒ void
private
Copies the folders named in
copy_foldersinto the sandbox's modules folder, so they can be discovered through the updated $Env:PSModulePath. -
#prepare_helpers ⇒ void
private
Copies all common testing helper files into the suites directory in the sandbox, stripping the
helpers/prefix from their paths. -
#prepare_pester_tests ⇒ void
private
Copies all test suite files into the suites directory in the sandbox.
-
#prepare_supporting_psmodules ⇒ void
private
Copies PesterUtil.psm1 into the sandbox's modules folder, where the updated $Env:PSModulePath will find it.
-
#ps_environment(obj) ⇒ String
private
Creates environment variable assignments from a ruby map.
-
#ps_hash(obj, depth = 0) ⇒ String
private
Renders a Ruby value as PowerShell source.
-
#ps_single_quote(value) ⇒ String
private
Renders a value as a single-quoted PowerShell string literal.
-
#really_wrap_posix_shell_code(code) ⇒ String
private
Wraps PowerShell for a non-Windows instance.
-
#really_wrap_shell_code(code) ⇒ String
private
Wraps generated PowerShell in the platform's shell invocation.
-
#really_wrap_windows_shell_code(code) ⇒ String
private
Wraps PowerShell for a Windows instance.
-
#register_psrepository_scriptblock ⇒ Array<String>
private
Returns the string command to set a PS Repository for each PSRepo configured.
-
#remote_basename(path) ⇒ String
private
Returns the final segment of a path that lives on the SUT.
-
#resolve_downloads_paths! ⇒ nil
Resolves the remote Downloads path from the verifier root path, unless they're absolute path (starts with / or C:) This updates the config, nothing (nil) is returned.
-
#restart_winrm_service ⇒ String?
private
Returns the command that schedules and runs a WinRM restart.
-
#run_command ⇒ String
Generates a command string which will invoke the main verifier command on the prepared instance.
-
#sandbox_module_path ⇒ String
private
Returns the path of the modules subfolder in the sandbox, where PS modules and folders will be copied to.
-
#script_root ⇒ string
private
Returns the current file's parent folder's full path.
-
#shell_cmd ⇒ String
private
Returns the shell binary used to run the generated script.
-
#suite_test_folder ⇒ Array<String>
private
Returns an Array of test suite filenames for the related suite currently residing on the local workstation.
-
#support_psmodule_folder ⇒ string
private
Returns the absolute path of the Supporting PS module to be copied to the SUT via the Sandbox.
-
#test_folder ⇒ String
private
Returns the folder containing the test suites, falling back to
test_base_pathwhentest_folderis not set. -
#use_local_powershell_modules(script) ⇒ String
private
Prefixes a script with the preamble that makes the sandbox's modules folder importable.
Constructor Details
#initialize(config = {}) ⇒ Pester
Creates a new Verifier object using the provided configuration data which will be merged with any default configuration.
104 105 106 107 |
# File 'lib/kitchen/verifier/pester.rb', line 104 def initialize(config = {}) init_config(config) raise ClientError.new "Environment Variables must be specified as a hash, not a #{config[:environment].class}" unless config[:environment].is_a?(Hash) end |
Instance Method Details
#absolute_test_folder ⇒ String
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Resolves test_folder to an absolute path, descending into an
integration subfolder when one exists.
866 867 868 869 870 |
# File 'lib/kitchen/verifier/pester.rb', line 866 def absolute_test_folder path = (Pathname.new config[:test_folder]).realpath integration_path = File.join(path, "integration") Dir.exist?(integration_path) ? integration_path : path.to_s end |
#call(state) ⇒ void
This method returns an undefined value.
Runs the verifier on the instance, retrieving the test results even when the run fails.
256 257 258 259 260 261 262 263 |
# File 'lib/kitchen/verifier/pester.rb', line 256 def call(state) super rescue info("Rescue to download test files.") download_test_files(state) unless config[:downloads].nil? # Rethrow the original exception; the failure still has to register. raise end |
#copy_if_src_exists(src_to_validate, destination) ⇒ void
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
This method returns an undefined value.
Copies a folder recursively, preserving its layers. Mostly used to copy into the sandbox. Does nothing when the source does not exist.
837 838 839 840 841 842 843 844 845 846 847 848 849 850 |
# File 'lib/kitchen/verifier/pester.rb', line 837 def copy_if_src_exists(src_to_validate, destination) unless Dir.exist?(src_to_validate) info("The path #{src_to_validate} was not found. Not copying to #{destination}.") return end info("Moving #{src_to_validate} to #{destination}") unless Dir.exist?(destination) FileUtils.mkdir_p(destination) debug("Folder '#{destination}' created.") end FileUtils.mkdir_p(File.join(destination, "__bugfix")) FileUtils.cp_r(src_to_validate, destination, preserve: true) end |
#create_sandbox ⇒ void
This method returns an undefined value.
Creates a temporary directory on the local workstation into which verifier related files and directories can be copied or created. The contents of this directory will be copied over to the instance before invoking the verifier's run command. After this method completes, it is expected that the contents of the sandbox is complete and ready for copy to the remote instance.
Note: any subclasses would be well advised to call super first when overriding this method, for example:
129 130 131 132 133 134 135 136 137 138 139 140 141 |
# File 'lib/kitchen/verifier/pester.rb', line 129 def create_sandbox super prepare_supporting_psmodules prepare_copy_folders prepare_pester_tests prepare_helpers debug("\n\n") debug("Sandbox content:\n") list_files(sandbox_path).each do |f| debug(" #{f}") end end |
#download_test_files(state) ⇒ void
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
This method returns an undefined value.
Retrieves the configured result files from the instance.
639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 |
# File 'lib/kitchen/verifier/pester.rb', line 639 def download_test_files(state) if config[:downloads].nil? info("Skipped downloading test result file from #{instance.to_str}; 'downloads' hash is empty.") return end info("Downloading test result files from #{instance.to_str}") instance.transport.connection(state) do |conn| config[:downloads].each do |remotes, local| debug("downloading #{Array(remotes).join(", ")} to #{local}") conn.download(remotes, local) end end debug("Finished downloading test result files from #{instance.to_str}") end |
#get_powershell_modules_from_nugetapi ⇒ Array<String>?
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Returns the commands that install the bootstrap modules straight from a NuGet feed.
This runs before PowerShellGet is available, so it uses
Install-ModuleFromNuget from PesterUtil.psm1 rather than
Install-Module. Each entry of bootstrap.modules may be a plain
module name or a hash of parameters.
367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 |
# File 'lib/kitchen/verifier/pester.rb', line 367 def get_powershell_modules_from_nugetapi # don't return anything is the modules subkey or bootstrap is null return if config.dig(:bootstrap, :modules).nil? bootstrap = config[:bootstrap] # if the repository url is set, use that as parameter to Install-ModuleFromNuget. Default is the PSGallery url gallery_url_param = bootstrap[:repository_url] ? "-GalleryUrl '#{bootstrap[:repository_url]}'" : "" info("Bootstrapping environment without PowerShellGet Provider...") Array(bootstrap[:modules]).map do |powershell_module| if powershell_module.is_a? Hash <<-PS1 ${#{powershell_module[:Name]}} = #{ps_hash(powershell_module)} Install-ModuleFromNuget -Module ${#{powershell_module[:Name]}} #{gallery_url_param} PS1 else <<-PS1 Install-ModuleFromNuget -Module @{Name = '#{powershell_module}'} #{gallery_url_param} PS1 end end end |
#helper_files ⇒ Array<String>
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Returns an Array of common helper filenames currently residing on the local workstation.
688 689 690 691 |
# File 'lib/kitchen/verifier/pester.rb', line 688 def helper_files glob = Dir.glob(File.join(test_folder, "helpers", "*/**/*")) glob.reject { |f| File.directory?(f) } end |
#init_command ⇒ String
Generates a command string which will perform any data initialization
or configuration required after the verifier software is installed
but before the sandbox has been transferred to the instance. If no work
is required, then nil will be returned.
189 190 191 |
# File 'lib/kitchen/verifier/pester.rb', line 189 def init_command restart_winrm_service if config[:restart_winrm] end |
#install_command ⇒ String
Generates a command string which will install and configure the
verifier software on an instance. If no work is required, then nil
will be returned.
PowerShellGet & Pester Bootstrap are done in prepare_command (after sandbox is transferred)
so that we can use the PesterUtil.psm1
150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 |
# File 'lib/kitchen/verifier/pester.rb', line 150 def install_command # the sandbox has not yet been copied to the SUT. install_command_string = <<-PS1 Write-Verbose 'Running Install Command...' $modulesToRemove = @( if ($#{config[:remove_builtin_powershellget]}) { Get-module -ListAvailable -FullyQualifiedName @{ModuleName = 'PackageManagement'; RequiredVersion = '1.0.0.1'} Get-module -ListAvailable -FullyQualifiedName @{ModuleName = 'PowerShellGet'; RequiredVersion = '1.0.0.1'} } if ($#{config[:remove_builtin_pester]}) { Get-module -ListAvailable -FullyQualifiedName @{ModuleName = 'Pester'; RequiredVersion = '3.4.0'} } ) if ($modulesToRemove.ModuleBase.Count -eq 0) { # for PS7 on linux return } $modulesToRemove.ModuleBase | Foreach-Object { $ModuleBaseLeaf = Split-Path -Path $_ -Leaf if ($ModuleBaseLeaf -as [System.version]) { Remove-Item -force -Recurse (Split-Path -Parent -Path $_) -ErrorAction SilentlyContinue } else { Remove-Item -force -Recurse $_ -ErrorAction SilentlyContinue } } PS1 really_wrap_shell_code(Util.outdent!(install_command_string)) end |
#install_command_script ⇒ String
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Returns the PowerShell that prepares the SUT once the sandbox has been transferred.
Runs after the transfer so that PesterUtil.psm1 is available to import. Composes, in order: the NuGet bootstrap, any PSRepository registration, the Pester install, and any gallery modules. Each section is omitted when its config is nil.
596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 |
# File 'lib/kitchen/verifier/pester.rb', line 596 def install_command_script <<-PS1 $PSModPathToPrepend = "#{config[:root_path]}" Import-Module -ErrorAction Stop PesterUtil #{get_powershell_modules_from_nugetapi.join("\n") unless config.dig(:bootstrap, :modules).nil?} #{register_psrepository_scriptblock.join("\n") unless config[:register_repository].nil?} #{install_pester} #{install_modules_from_gallery.join("\n") unless config[:install_modules].nil?} PS1 end |
#install_modules_from_gallery ⇒ Array<String>
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
returns a piece of PS scriptblock for each Module to install from gallery that has been specified in install_modules config.
439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 |
# File 'lib/kitchen/verifier/pester.rb', line 439 def install_modules_from_gallery return if config[:install_modules].nil? Array(config[:install_modules]).map do |powershell_module| if powershell_module.is_a? Hash # Sanitize variable name so that $powershell-yaml becomes $powershell_yaml module_name = powershell_module[:Name].gsub(/[\W]/, "_") # so we can splat that variable to install module <<-PS1 $#{module_name} = #{ps_hash(powershell_module)} Write-Host -NoNewline 'Installing #{module_name}' Install-Module @#{module_name} Write-host '... done.' PS1 else <<-PS1 Write-host -NoNewline 'Installing #{powershell_module} ...' Install-Module -Name '#{powershell_module}' Write-host '... done.' PS1 end end end |
#install_pester ⇒ String
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Returns the string command set the PSGallery as trusted, and Install Pester from gallery based on the params from Pester_install_params config
416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 |
# File 'lib/kitchen/verifier/pester.rb', line 416 def install_pester return if config[:skip_pester_install] pester_install_params = config[:pester_install] || {} <<-PS1 if ((Get-PSRepository -Name PSGallery).InstallationPolicy -ne 'Trusted') { Write-Host -Object "Trusting the PSGallery to install Pester without -Force" Set-PSRepository -Name PSGallery -InstallationPolicy Trusted -ErrorAction SilentlyContinue } Write-Host "Installing Pester..." $installPesterParams = #{ps_hash(pester_install_params)} $installPesterParams['Name'] = 'Pester' Install-module @installPesterParams Write-Host 'Pester Installed.' PS1 end |
#invoke_pester_scriptblock ⇒ String
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Returns the PowerShell that imports Pester and invokes it.
Two dialects are emitted behind a version check evaluated on the SUT:
Pester 4 and earlier take loose parameters, Pester 5 and later take a
PesterConfiguration object. The script exits with Pester's failed
test count so the transport registers the failure.
274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 |
# File 'lib/kitchen/verifier/pester.rb', line 274 def invoke_pester_scriptblock <<-PS1 $PesterModule = Import-Module -Name Pester -Force -ErrorAction Stop -PassThru $TestPath = Join-Path "#{config[:root_path]}" -ChildPath "suites" $OutputFilePath = Join-Path "#{config[:root_path]}" -ChildPath 'PesterTestResults.xml' #{ps_environment(config[:environment])} if ($PesterModule.Version.Major -le 4) { Write-Host -Object "Invoke Pester with v$($PesterModule.Version) Options" $options = New-PesterOption -TestSuiteName "Pester - #{instance.to_str}" $defaultPesterParameters = @{ Script = $TestPath OutputFile = $OutputFilePath OutputFormat = 'NUnitXml' PassThru = $true PesterOption = $options } $pesterCmd = Get-Command -Name 'Invoke-Pester' $pesterConfig = #{ps_hash(config[:pester_configuration])} $invokePesterParams = @{} foreach ($paramName in $pesterCmd.Parameters.Keys) { $paramValue = $pesterConfig.($paramName) if ($paramValue) { Write-Host -Object "Using $paramName from Yaml config." $invokePesterParams[$paramName] = $paramValue } elseif ($defaultPesterParameters.ContainsKey($paramName)) { Write-Host -Object "Using $paramName from Defaults: $($defaultPesterParameters[$paramName])." $invokePesterParams[$paramName] = $defaultPesterParameters[$paramName] } } $result = Invoke-Pester @invokePesterParams } else { Write-Host -Object "Invoke Pester with v$($PesterModule.Version) Configuration." $pesterConfigHash = #{ps_hash(config[:pester_configuration])} if (-not $pesterConfigHash.ContainsKey('run')) { $pesterConfigHash['run'] = @{} } if (-not $pesterConfigHash.ContainsKey('TestResult')) { $pesterConfigHash['TestResult'] = @{} } if (-not $pesterConfigHash.run.path) { $pesterConfigHash['run']['path'] = $TestPath } if (-not $pesterConfigHash.TestResult.TestSuiteName) { $pesterConfigHash['TestResult']['TestSuiteName'] = 'Pester - #{instance.to_str}' } if (-not $pesterConfigHash.TestResult.OutputPath) { $pesterConfigHash['TestResult']['OutputPath'] = $OutputFilePath } $PesterConfig = New-PesterConfiguration -Hashtable $pesterConfigHash $result = Invoke-Pester -Configuration $PesterConfig } $resultXmlPath = (Join-Path -Path $TestPath -ChildPath 'result.xml') if (Test-Path -Path $resultXmlPath) { $result | Export-CliXml -Path $resultXmlPath } $LASTEXITCODE = $result.FailedCount $host.SetShouldExit($LASTEXITCODE) exit $LASTEXITCODE PS1 end |
#list_files(path) ⇒ Array<String>
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Creates a flat list of the files contained in a folder.
Useful when debugging what has actually been copied to the sandbox.
803 804 805 806 807 |
# File 'lib/kitchen/verifier/pester.rb', line 803 def list_files(path) base_directory_content = Dir.glob(File.join(path, "*")) nested_directory_content = Dir.glob(File.join(path, "*/**/*")) [base_directory_content, nested_directory_content].flatten end |
#pad(depth = 0) ⇒ String
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Returns a run of spaces of the given width, used to pad messages and indent generated PowerShell hashtables.
892 893 894 |
# File 'lib/kitchen/verifier/pester.rb', line 892 def pad(depth = 0) " " * depth end |
#prepare_command ⇒ String
Generates a command string which will perform any commands or
configuration required just before the main verifier run command but
after the sandbox has been transferred to the instance. If no work is
required, then nil will be returned.
199 200 201 202 203 |
# File 'lib/kitchen/verifier/pester.rb', line 199 def prepare_command info("Preparing the SUT and Pester dependencies...") resolve_downloads_paths! really_wrap_shell_code(install_command_script) end |
#prepare_copy_folders ⇒ void
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
This method returns an undefined value.
Copies the folders named in copy_folders into the sandbox's modules
folder, so they can be discovered through the updated
$Env:PSModulePath.
783 784 785 786 787 788 789 790 791 792 793 |
# File 'lib/kitchen/verifier/pester.rb', line 783 def prepare_copy_folders return if config[:copy_folders].nil? info("Preparing to copy specified folders to #{sandbox_module_path}.") kitchen_root_path = config[:kitchen_root] config[:copy_folders].each do |folder| debug("copying #{folder}") folder_to_copy = File.join(kitchen_root_path, folder) copy_if_src_exists(folder_to_copy, sandbox_module_path) end end |
#prepare_helpers ⇒ void
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
This method returns an undefined value.
Copies all common testing helper files into the suites directory in
the sandbox, stripping the helpers/ prefix from their paths.
698 699 700 701 702 703 704 705 706 707 |
# File 'lib/kitchen/verifier/pester.rb', line 698 def prepare_helpers base = File.join(test_folder, "helpers") helper_files.each do |src| dest = File.join(sandbox_path, src.sub("#{base}/", "")) debug("Copying #{src} to #{dest}") FileUtils.mkdir_p(File.dirname(dest)) FileUtils.cp(src, dest, preserve: true) end end |
#prepare_pester_tests ⇒ void
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
This method returns an undefined value.
Copies all test suite files into the suites directory in the sandbox.
813 814 815 816 817 |
# File 'lib/kitchen/verifier/pester.rb', line 813 def prepare_pester_tests info("Preparing to copy files from '#{suite_test_folder}' to the SUT.") sandboxed_suites_path = File.join(sandbox_path, "suites") copy_if_src_exists(suite_test_folder, sandboxed_suites_path) end |
#prepare_supporting_psmodules ⇒ void
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
This method returns an undefined value.
Copies PesterUtil.psm1 into the sandbox's modules folder, where the updated $Env:PSModulePath will find it.
824 825 826 827 828 |
# File 'lib/kitchen/verifier/pester.rb', line 824 def prepare_supporting_psmodules info("Preparing to copy files from '#{support_psmodule_folder}' to the SUT.") sandbox_module_path = File.join(sandbox_path, "modules") copy_if_src_exists(support_psmodule_folder, sandbox_module_path) end |
#ps_environment(obj) ⇒ String
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Creates environment variable assignments from a ruby map.
746 747 748 749 750 751 752 |
# File 'lib/kitchen/verifier/pester.rb', line 746 def ps_environment(obj) commands = obj.map do |k, v| "$env:#{k} = #{ps_single_quote(v)}" end commands.join("\n") end |
#ps_hash(obj, depth = 0) ⇒ String
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Renders a Ruby value as PowerShell source.
Hashes become hashtables, arrays become arrays, booleans become $true or $false, and everything else is quoted as a string -- PowerShell is generally able to coerce it back to the type it needs.
719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 |
# File 'lib/kitchen/verifier/pester.rb', line 719 def ps_hash(obj, depth = 0) if [true, false].include? obj %{$#{obj}} # Return $true or $false when value is a bool elsif obj.is_a?(Hash) obj.map do |k, v| # Format "Key = Value" enabling recursion %{#{pad(depth + 2)}#{ps_hash(k)} = #{ps_hash(v, depth + 2)}} end .join("\n") # append \n to the key/value definitions .insert(0, "@{\n") # prepend @{\n .insert(-1, "\n#{pad(depth)}}\n") # append \n}\n elsif obj.is_a?(Array) array_string = obj.map { |v| ps_hash(v, depth + 4) }.join(",") "#{pad(depth)}@(\n#{array_string}\n)" else # When the object is not a string nor a hash or array, it will be quoted as a string. # In most cases, PS is smart enough to convert back to the type it needs. ps_single_quote(obj) end end |
#ps_single_quote(value) ⇒ String
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Renders a value as a single-quoted PowerShell string literal.
PowerShell escapes a literal quote inside a single-quoted string by doubling it. Without this an apostrophe anywhere in the config -- a module name, an environment value, a password -- closes the string early and corrupts the rest of the generated script.
764 765 766 |
# File 'lib/kitchen/verifier/pester.rb', line 764 def ps_single_quote(value) "'#{value.to_s.gsub("'", "''")}'" end |
#really_wrap_posix_shell_code(code) ⇒ String
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Wraps PowerShell for a non-Windows instance.
Writes the payload to kitchen_cmd.ps1 through a quoted heredoc, so the POSIX shell does not interpolate PowerShell variables, adds a pwsh shebang and invokes it.
541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 |
# File 'lib/kitchen/verifier/pester.rb', line 541 def really_wrap_posix_shell_code(code) my_command = <<-BASH echo "Running as '$(whoami)'" # create the modules folder, making sure it's done as current user (not root) mkdir -p #{config[:root_path]}/modules cd #{config[:root_path]} # Send the bash heredoc 'EOF' to the file kitchen_cmd.ps1 using the tool cat cat << 'EOF' > kitchen_cmd.ps1 #!/usr/bin/env pwsh #{Util.outdent!(use_local_powershell_modules(code))} EOF chmod +x kitchen_cmd.ps1 # Invoke the created kitchen_cmd.ps1 file using pwsh #{shell_cmd} ./kitchen_cmd.ps1 BASH debug(Util.outdent!(my_command)) Util.outdent!(my_command) end |
#really_wrap_shell_code(code) ⇒ String
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Wraps generated PowerShell in the platform's shell invocation.
475 476 477 |
# File 'lib/kitchen/verifier/pester.rb', line 475 def really_wrap_shell_code(code) windows_os? ? really_wrap_windows_shell_code(code) : really_wrap_posix_shell_code(code) end |
#really_wrap_windows_shell_code(code) ⇒ String
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Wraps PowerShell for a Windows instance.
The payload is written to kitchen_cmd.ps1 and invoked, rather than passed on the command line, so that quoting and length limits do not apply to it.
507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 |
# File 'lib/kitchen/verifier/pester.rb', line 507 def really_wrap_windows_shell_code(code) my_command = <<-PWSH echo "Running as '$(whoami)'..." New-Item -ItemType Directory -Path '#{config[:root_path]}/modules' -Force -ErrorAction SilentlyContinue Set-Location -Path "#{config[:root_path]}" # Send the pwsh here string to the file kitchen_cmd.ps1 @' try { if (@('Bypass', 'Unrestricted') -notcontains (Get-ExecutionPolicy)) { Set-ExecutionPolicy Unrestricted -Force -Scope Process } } catch { $_ | Out-String | Write-Warning } #{Util.outdent!(use_local_powershell_modules(code))} '@ | Set-Content -Path kitchen_cmd.ps1 -Encoding utf8 -Force -ErrorAction 'Stop' # create the modules folder, making sure it's done as current user (not root) # # Invoke the created kitchen_cmd.ps1 file using pwsh #{shell_cmd} ./kitchen_cmd.ps1 PWSH wrap_shell_code(Util.outdent!(my_command)) end |
#register_psrepository_scriptblock ⇒ Array<String>
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Returns the string command to set a PS Repository for each PSRepo configured.
396 397 398 399 400 401 402 403 404 405 406 407 408 409 |
# File 'lib/kitchen/verifier/pester.rb', line 396 def register_psrepository_scriptblock return if config[:register_repository].nil? info("Registering a new PowerShellGet Repository") Array(config[:register_repository]).map do |psrepo| # Using Set-PSRepo from ../../*/*/*/PesterUtil.psm1 debug("Command to set PSRepo #{psrepo[:Name]}.") <<-PS1 Write-Host 'Registering psrepo #{psrepo[:Name]}...' ${#{psrepo[:Name]}} = #{ps_hash(psrepo)} Set-PSRepo -Repository ${#{psrepo[:Name]}} PS1 end end |
#remote_basename(path) ⇒ String
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Returns the final segment of a path that lives on the SUT.
File.basename applies the workstation's separator rules, so a Windows remote path such as 'C:\results\out.xml' comes back unchanged when kitchen runs on macOS or Linux -- the usual case for a Windows SUT. Split on either separator instead.
882 883 884 |
# File 'lib/kitchen/verifier/pester.rb', line 882 def remote_basename(path) path.to_s.split(%r{[\\/]}).last.to_s end |
#resolve_downloads_paths! ⇒ nil
Resolves the remote Downloads path from the verifier root path, unless they're absolute path (starts with / or C:) This updates the config, nothing (nil) is returned.
219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 |
# File 'lib/kitchen/verifier/pester.rb', line 219 def resolve_downloads_paths! info("Resolving Downloads path from config.") config[:downloads] = config[:downloads] .map do |source, destination| source = source.to_s destination = destination.gsub("%{instance_name}", instance.name) info(" resolving remote source's absolute path.") unless source.match?(%r{^/|^[a-zA-Z]:[\\/]}) # is Absolute? info(" '#{source}' is a relative path, resolving to: #{File.join(config[:root_path], source)}") source = File.join(config[:root_path], source.to_s).to_s end if destination.match?(%r{[\\/]$}) # is Folder (ends with / or \) # Append to the separator the user already supplied. File.join # would add a second one, of whichever flavour the workstation # happens to use. destination = "#{destination}#{remote_basename(source)}" end info(" Destination: #{destination}") if !File.directory?(File.dirname(destination)) FileUtils.mkdir_p(File.dirname(destination)) else info(" Directory #{File.dirname(destination)} seems to exist.") end [ source, destination ] end .to_h # Hash#map yields pairs; keep :downloads the hash it started as nil # make sure we do not return anything end |
#restart_winrm_service ⇒ String?
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Returns the command that schedules and runs a WinRM restart.
The restart is driven through a scheduled task so that it survives the WinRM session being torn down by the restart itself.
620 621 622 623 624 625 626 627 628 629 630 631 |
# File 'lib/kitchen/verifier/pester.rb', line 620 def restart_winrm_service return unless windows_os? cmd = "schtasks /Create /TN restart_winrm /TR " \ '"powershell -Command Restart-Service winrm" ' \ "/SC ONCE /ST 00:00 " wrap_shell_code(Util.outdent!(<<-CMD #{cmd} schtasks /RUN /TN restart_winrm CMD )) end |
#run_command ⇒ String
Generates a command string which will invoke the main verifier
command on the prepared instance. If no work is required, then nil
will be returned.
210 211 212 |
# File 'lib/kitchen/verifier/pester.rb', line 210 def run_command really_wrap_shell_code(invoke_pester_scriptblock) end |
#sandbox_module_path ⇒ String
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Returns the path of the modules subfolder in the sandbox, where PS modules and folders will be copied to.
773 774 775 |
# File 'lib/kitchen/verifier/pester.rb', line 773 def sandbox_module_path File.join(sandbox_path, "modules") end |
#script_root ⇒ string
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Returns the current file's parent folder's full path.
670 671 672 |
# File 'lib/kitchen/verifier/pester.rb', line 670 def script_root @script_root ||= File.dirname(__FILE__) end |
#shell_cmd ⇒ String
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Returns the shell binary used to run the generated script.
An explicit shell config wins, which allows pwsh-preview or a full
path to a specific binary. Otherwise Windows uses powershell and every
other platform uses pwsh. sudo is honoured everywhere except the
Windows branch, where it is meaningless.
488 489 490 491 492 493 494 495 496 |
# File 'lib/kitchen/verifier/pester.rb', line 488 def shell_cmd if !config[:shell].nil? config[:sudo] ? "sudo #{config[:shell]}" : "#{config[:shell]}" elsif windows_os? "powershell" else config[:sudo] ? "sudo pwsh" : "pwsh" end end |
#suite_test_folder ⇒ Array<String>
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Returns an Array of test suite filenames for the related suite currently residing on the local workstation. Any special provisioner-specific directories (such as a Chef roles/ directory) are excluded.
662 663 664 |
# File 'lib/kitchen/verifier/pester.rb', line 662 def suite_test_folder @suite_test_folder ||= File.join(test_folder, config[:suite_name]) end |
#support_psmodule_folder ⇒ string
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Returns the absolute path of the Supporting PS module to be copied to the SUT via the Sandbox.
679 680 681 |
# File 'lib/kitchen/verifier/pester.rb', line 679 def support_psmodule_folder @support_psmodule_folder ||= Pathname.new(File.join(script_root, "../../support/modules/PesterUtil")).cleanpath end |
#test_folder ⇒ String
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Returns the folder containing the test suites, falling back to
test_base_path when test_folder is not set.
857 858 859 |
# File 'lib/kitchen/verifier/pester.rb', line 857 def test_folder config[:test_folder].nil? ? config[:test_base_path] : absolute_test_folder end |
#use_local_powershell_modules(script) ⇒ String
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Prefixes a script with the preamble that makes the sandbox's modules folder importable.
567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 |
# File 'lib/kitchen/verifier/pester.rb', line 567 def use_local_powershell_modules(script) <<-PS1 Write-Host -Object ("{0} - PowerShell {1}" -f $PSVersionTable.OS,$PSVersionTable.PSVersion) $global:ProgressPreference = 'SilentlyContinue' $PSModPathToPrepend = Join-Path "#{config[:root_path]}" -ChildPath 'modules' Write-Verbose "Adding '$PSModPathToPrepend' to `$Env:PSModulePath." if (!$isLinux -and -not (Test-Path -Path $PSModPathToPrepend)) { # if you create this folder now in Linux, it may run as root (via sudo). $null = New-Item -Path $PSModPathToPrepend -Force -ItemType Directory } if ($Env:PSModulePath.Split([io.path]::PathSeparator) -notcontains $PSModPathToPrepend) { $env:PSModulePath = @($PSModPathToPrepend, $env:PSModulePath) -Join [io.path]::PathSeparator } #{script} PS1 end |