Skip to content

Support unit testing DSC resources on Unix OS distributions by enabling option of powershell or pwsh within the dsc_base_provider #308

Closed
@chambersmp

Description

@chambersmp

Use Case

Unix OS should be able to run unit tests for DSC resources

  • DSC resources are dependencies for the premium forge module CEM_windows
  • Job hardware supporting CD4PE code jobs are usually Unix based.
  • DSC unit testing requires complex setup of temporary Windows VM provisioning (via Vagrant, Terraform, other)
  • The proposed changes would allow any pwsh supported platform to run unit tests for DSC resources.
    • This includes docker images like puppet-dev-tools for ephemeral testing.

Currently unit tests for DSC resources must be performed on a Windows host as there is a dependency on PowerShell to execute the canonicalize provider method during catalog compilation.

This often requires a complex Vagrant, Terraform, Build setup to provision short-term Windows VMs for ephemeral unit testing. This is a potentially complex and costly setup for users.

Issue Background

The canonicalize method triggers a PowerShell process on agent or test hardware during catalog compilation to Get current state of DSC resources on the target node.
The feature is implemented to negate unnecessary corrective actions due to case-mismatch. The provider is currently limited to PowerShell (Windows only).
See ref: https://www.puppet.com/docs/puppet/8/about_the_resource_api.html#provider_features-canonicalize

Describe the Solution You Would Like

  1. The Unix OS running as test hardware will require pwsh to be installed and the $PSPATH added to $PATH.

  2. The dsc_base_provider should utilise pwsh in the ps_manager method to ensure Unix platforms successfully execute the canonicalise method to normalise User data values supplied within the manifest.

    • The canonicalize method attempts to start a Powershell instance on Unix to validate manifest values during catalog compilation.
      def canonicalize(context, resources)
      canonicalized_resources = []
      resources.collect do |r|
      # During RSAPI refresh runs mandatory parameters are stripped and not available;
      # Instead of checking again and failing, search the cache for a namevar match.
      namevarized_r = r.select { |k, _v| namevar_attributes(context).include?(k) }
      cached_result = fetch_cached_hashes(@cached_canonicalized_resource, [namevarized_r]).first
      if cached_result.nil?
      # If the resource is meant to be absent, skip canonicalization and rely on the manifest
      # value; there's no reason to compare system state to desired state for casing if the
      # resource is being removed.
      if r[:dsc_ensure] == 'absent'
      canonicalized = r.dup
      @cached_canonicalized_resource << r.dup
      else
      canonicalized = invoke_get_method(context, r)
    • It should use pwsh on Unix to inspect the local dsc state. The invoke-dscresource -method get will return nil state and the catalog will compile manifest values as source of truth
      def ps_manager
      debug_output = Puppet::Util::Log.level == :debug
      # TODO: Allow you to specify an alternate path, either to pwsh generally or a specific pwsh path.
      Pwsh::Manager.instance(Pwsh::Manager.powershell_path, Pwsh::Manager.powershell_args, debug: debug_output)
      end
  3. The 'Pwsh::Manager.pwsh_path' method should consistently split the $PATH system variable by ':' when the ruby interpreter is run on Unix OS.

    • When unit testing via a Unix host, the rspec context fools the interpreter into thinking the File::PATH_SEPARATOR constant is Windows based ; instead of : for Unix. This prevents the execution of pwsh from within the $PSPATH listed within the $PATH system variable.
    • Ensuring the $PSPATH has been added to the $PATH system variable on the Unix OS, the pwsh executable will be dynamically found

      ruby-pwsh/lib/pwsh.rb

      Lines 378 to 386 in fec0326

      if Pwsh::Util.on_windows?
      search_paths.split(File::PATH_SEPARATOR).each do |path|
      pwsh_paths << File.join(path, 'pwsh.exe') if File.exist?(File.join(path, 'pwsh.exe'))
      end
      else
      search_paths.split(File::PATH_SEPARATOR).each do |path|
      pwsh_paths << File.join(path, 'pwsh') if File.exist?(File.join(path, 'pwsh'))
      end
      end
  4. Pwsh::Util.on_windows? method should identify the OS running the Ruby interpreter not the synthetic context OS within rspec to ensure Unix conditional code is respected.

    • File::ALT_SEPARATOR constant is set for Windows even if the OS is Unix when rspec context OS is Windows.
    • This means a Unix host running rspec will not accurately return the correct boolean value for this method and follows Windows specific logic.
      def on_windows?
      # Ruby only sets File::ALT_SEPARATOR on Windows and the Ruby standard
      # library uses that to test what platform it's on.
      !!File::ALT_SEPARATOR
      end

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions