diff --git a/changelogs/fragments/valdate-modules-ps-arg-util.yaml b/changelogs/fragments/valdate-modules-ps-arg-util.yaml new file mode 100644 index 00000000000..bbb534077e9 --- /dev/null +++ b/changelogs/fragments/valdate-modules-ps-arg-util.yaml @@ -0,0 +1,2 @@ +bugfixes: +- ansible-test validate-modules - Fix arg spec collector for PowerShell to find utils in both a collection and base. diff --git a/lib/ansible/executor/powershell/module_manifest.py b/lib/ansible/executor/powershell/module_manifest.py index aa746d4755b..ce705db560e 100644 --- a/lib/ansible/executor/powershell/module_manifest.py +++ b/lib/ansible/executor/powershell/module_manifest.py @@ -29,6 +29,7 @@ from ansible.plugins.loader import ps_module_utils_loader class PSModuleDepFinder(object): def __init__(self): + # This is also used by validate-modules to get a module's required utils in base and a collection. self.ps_modules = dict() self.exec_scripts = dict() diff --git a/test/lib/ansible_test/_data/sanity/validate-modules/validate_modules/module_args.py b/test/lib/ansible_test/_data/sanity/validate-modules/validate_modules/module_args.py index ef1f83778ed..953b40091db 100644 --- a/test/lib/ansible_test/_data/sanity/validate-modules/validate_modules/module_args.py +++ b/test/lib/ansible_test/_data/sanity/validate-modules/validate_modules/module_args.py @@ -26,8 +26,10 @@ import sys from contextlib import contextmanager +from ansible.executor.powershell.module_manifest import PSModuleDepFinder from ansible.module_utils.basic import FILE_COMMON_ARGUMENTS from ansible.module_utils.six import reraise +from ansible.module_utils._text import to_bytes, to_text from .utils import CaptureStd, find_executable, get_module_name_from_filename @@ -94,8 +96,22 @@ def get_ps_argument_spec(filename): if not pwsh: raise FileNotFoundError('Required program for PowerShell arg spec inspection "pwsh" not found.') + module_path = os.path.join(os.getcwd(), filename) + b_module_path = to_bytes(module_path, errors='surrogate_or_strict') + with open(b_module_path, mode='rb') as module_fd: + b_module_data = module_fd.read() + + ps_dep_finder = PSModuleDepFinder() + ps_dep_finder.scan_module(b_module_data) + + util_manifest = json.dumps({ + 'module_path': to_text(module_path, errors='surrogiate_or_strict'), + 'ps_utils': dict([(name, info['path']) for name, info in ps_dep_finder.ps_modules.items()]) + }) + script_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'ps_argspec.ps1') - proc = subprocess.Popen([script_path, filename], stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=False) + proc = subprocess.Popen([script_path, util_manifest], stdout=subprocess.PIPE, stderr=subprocess.PIPE, + shell=False) stdout, stderr = proc.communicate() if proc.returncode != 0: diff --git a/test/lib/ansible_test/_data/sanity/validate-modules/validate_modules/ps_argspec.ps1 b/test/lib/ansible_test/_data/sanity/validate-modules/validate_modules/ps_argspec.ps1 index 35b918c569f..4293d2d8544 100755 --- a/test/lib/ansible_test/_data/sanity/validate-modules/validate_modules/ps_argspec.ps1 +++ b/test/lib/ansible_test/_data/sanity/validate-modules/validate_modules/ps_argspec.ps1 @@ -5,11 +5,12 @@ Set-StrictMode -Version 2.0 $ErrorActionPreference = "Stop" $WarningPreference = "Stop" -$module_path = $args[0] -if (-not $module_path) { +$manifest = ConvertFrom-Json -InputObject $args[0] -AsHashtable +if (-not $manifest.Contains('module_path') -or -not $manifest.module_path) { Write-Error -Message "No module specified." exit 1 } +$module_path = $manifest.module_path # Check if the path is relative and get the full path to the module if (-not ([System.IO.Path]::IsPathRooted($module_path))) { @@ -51,33 +52,26 @@ $module_code = Get-Content -LiteralPath $module_path -Raw $powershell = [PowerShell]::Create() $powershell.Runspace.SessionStateProxy.SetVariable("ErrorActionPreference", "Stop") -# Load the PowerShell module utils as the module may be using them to refer to shared module options -# FUTURE: Lookup utils in the role or collection's module_utils dir based on #AnsibleRequires -$script_requirements = [ScriptBlock]::Create($module_code).Ast.ScriptRequirements -$required_modules = @() -if ($null -ne $script_requirements) { - $required_modules = $script_requirements.RequiredModules -} -foreach ($required_module in $required_modules) { - if (-not $required_module.Name.StartsWith('Ansible.ModuleUtils.')) { - continue +# Load the PowerShell module utils as the module may be using them to refer to shared module options. Currently we +# can only load the PowerShell utils due to cross platform compatiblity issues. +if ($manifest.Contains('ps_utils')) { + foreach ($util_info in $manifest.ps_utils.GetEnumerator()) { + $util_name = $util_info.Key + $util_path = $util_info.Value + + if (-not (Test-Path -LiteralPath $util_path -PathType Leaf)) { + # Failed to find the util path, just silently ignore for now and hope for the best. + continue + } + + $util_sb = [ScriptBlock]::Create((Get-Content -LiteralPath $util_path -Raw)) + $powershell.AddCommand('New-Module').AddParameters(@{ + Name = $util_name + ScriptBlock = $util_sb + }) > $null + $powershell.AddCommand('Import-Module').AddParameter('WarningAction', 'SilentlyContinue') > $null + $powershell.AddCommand('Out-Null').AddStatement() > $null } - - $module_util_path = [System.IO.Path]::GetFullPath([System.IO.Path]::Combine($module_path, '..', '..', '..', - 'module_utils', 'powershell', "$($required_module.Name).psm1")) - if (-not (Test-Path -LiteralPath $module_util_path -PathType Leaf)) { - # Failed to find path, just silently ignore for now and hope for the best - continue - } - - $module_util_sb = [ScriptBlock]::Create((Get-Content -LiteralPath $module_util_path -Raw)) - $powershell.AddCommand('New-Module').AddParameters(@{ - Name = $required_module.Name - ScriptBlock = $module_util_sb - }) > $null - $powershell.AddCommand('Import-Module').AddParameter('WarningAction', 'SilentlyContinue') > $null - $powershell.AddCommand('Out-Null').AddStatement() > $null - } $powershell.AddScript($module_code) > $null