ansible-test - fix up relative util import for powershell validate-modules (#69753)
* ansible-test - fix up relative util import for powershell validate-modules * Use different tactic for generic group * Use python 2 and 3
This commit is contained in:
parent
31bf3a5622
commit
f5f3ba7ab5
19 changed files with 302 additions and 35 deletions
|
@ -38,6 +38,8 @@ namespace Ansible.Basic
|
|||
public delegate void WriteLineHandler(string line);
|
||||
public static WriteLineHandler WriteLine = new WriteLineHandler(WriteLineModule);
|
||||
|
||||
public static bool _DebugArgSpec = false;
|
||||
|
||||
private static List<string> BOOLEANS_TRUE = new List<string>() { "y", "yes", "on", "1", "true", "t", "1.0" };
|
||||
private static List<string> BOOLEANS_FALSE = new List<string>() { "n", "no", "off", "0", "false", "f", "0.0" };
|
||||
|
||||
|
@ -193,12 +195,30 @@ namespace Ansible.Basic
|
|||
try
|
||||
{
|
||||
ValidateArgumentSpec(argumentSpec);
|
||||
|
||||
// Used by ansible-test to retrieve the module argument spec, not designed for public use.
|
||||
if (_DebugArgSpec)
|
||||
{
|
||||
// Cannot call exit here because it will be caught with the catch (Exception e) below. Instead
|
||||
// just throw a new exception with a specific message and the exception block will handle it.
|
||||
ScriptBlock.Create("Set-Variable -Name ansibleTestArgSpec -Value $args[0] -Scope Global"
|
||||
).Invoke(argumentSpec);
|
||||
throw new Exception("ansible-test validate-modules check");
|
||||
}
|
||||
|
||||
// Now make sure all the metadata keys are set to their defaults, this must be done after we've
|
||||
// potentially output the arg spec for ansible-test.
|
||||
SetArgumentSpecDefaults(argumentSpec);
|
||||
|
||||
Params = GetParams(args);
|
||||
aliases = GetAliases(argumentSpec, Params);
|
||||
SetNoLogValues(argumentSpec, Params);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (e.Message == "ansible-test validate-modules check")
|
||||
Exit(0);
|
||||
|
||||
Dictionary<string, object> result = new Dictionary<string, object>
|
||||
{
|
||||
{ "failed", true },
|
||||
|
@ -657,8 +677,10 @@ namespace Ansible.Basic
|
|||
// Outside of the spec iterator, change the values that were casted above
|
||||
foreach (KeyValuePair<string, object> changedValue in changedValues)
|
||||
argumentSpec[changedValue.Key] = changedValue.Value;
|
||||
}
|
||||
|
||||
// Now make sure all the metadata keys are set to their defaults
|
||||
private void SetArgumentSpecDefaults(IDictionary argumentSpec)
|
||||
{
|
||||
foreach (KeyValuePair<string, List<object>> metadataEntry in specDefaults)
|
||||
{
|
||||
List<object> defaults = metadataEntry.Value;
|
||||
|
@ -669,6 +691,22 @@ namespace Ansible.Basic
|
|||
if (!argumentSpec.Contains(metadataEntry.Key))
|
||||
argumentSpec[metadataEntry.Key] = defaultValue;
|
||||
}
|
||||
|
||||
// Recursively set the defaults for any inner options.
|
||||
foreach (DictionaryEntry entry in argumentSpec)
|
||||
{
|
||||
if (entry.Value == null || entry.Key.ToString() != "options")
|
||||
continue;
|
||||
|
||||
IDictionary optionsSpec = (IDictionary)entry.Value;
|
||||
foreach (DictionaryEntry optionEntry in optionsSpec)
|
||||
{
|
||||
optionsContext.Add((string)optionEntry.Key);
|
||||
IDictionary optionMeta = (IDictionary)optionEntry.Value;
|
||||
SetArgumentSpecDefaults(optionMeta);
|
||||
optionsContext.RemoveAt(optionsContext.Count - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Dictionary<string, string> GetAliases(IDictionary argumentSpec, IDictionary parameters)
|
||||
|
|
|
@ -110,6 +110,9 @@ matrix:
|
|||
- env: T=fallaxy/2.7/1
|
||||
- env: T=fallaxy/3.6/1
|
||||
|
||||
- env: T=generic/2.7/1
|
||||
- env: T=generic/3.6/1
|
||||
|
||||
- env: T=i/osx/10.11
|
||||
- env: T=i/rhel/7.8
|
||||
- env: T=i/rhel/8.2
|
||||
|
|
1
test/integration/targets/ansible-test-docker/aliases
Normal file
1
test/integration/targets/ansible-test-docker/aliases
Normal file
|
@ -0,0 +1 @@
|
|||
shippable/generic/group1 # Runs in the default test container so access to tools like pwsh
|
|
@ -0,0 +1,21 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2020 Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
class ModuleDocFragment:
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
options:
|
||||
option1:
|
||||
description:
|
||||
- Test description
|
||||
required: yes
|
||||
aliases:
|
||||
- alias1
|
||||
type: str
|
||||
'''
|
|
@ -0,0 +1,16 @@
|
|||
# Copyright (c) 2020 Ansible Project
|
||||
# # Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
|
||||
|
||||
Function Get-PSUtilSpec {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Shared util spec test
|
||||
#>
|
||||
@{
|
||||
options = @{
|
||||
option1 = @{ type = 'str'; required = $true; aliases = 'alias1' }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Export-ModuleMember -Function Get-PSUtilSpec
|
|
@ -0,0 +1,6 @@
|
|||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
def hello(name):
|
||||
return 'Hello %s' % name
|
|
@ -0,0 +1,46 @@
|
|||
#!/usr/bin/python
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
module: hello
|
||||
short_description: Hello test module
|
||||
description: Hello test module.
|
||||
options:
|
||||
name:
|
||||
description: Name to say hello to.
|
||||
type: str
|
||||
author:
|
||||
- Ansible Core Team
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- minimal:
|
||||
'''
|
||||
|
||||
RETURN = ''''''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ..module_utils.my_util import hello
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
name=dict(type='str'),
|
||||
),
|
||||
)
|
||||
|
||||
module.exit_json(**say_hello(module.params['name']))
|
||||
|
||||
|
||||
def say_hello(name):
|
||||
return dict(
|
||||
message=hello(name),
|
||||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -0,0 +1,18 @@
|
|||
#!powershell
|
||||
|
||||
# Copyright (c) 2020 Ansible Project
|
||||
# # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
#AnsibleRequires -CSharpUtil Ansible.Basic
|
||||
#AnsibleRequires -PowerShell ..module_utils.PSUtil
|
||||
|
||||
$spec = @{
|
||||
options = @{
|
||||
my_opt = @{ type = "str"; required = $true }
|
||||
}
|
||||
}
|
||||
$util_spec = Get-PSUtilSpec
|
||||
$spec.options += $util_spec.options
|
||||
|
||||
$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec)
|
||||
$module.ExitJson()
|
|
@ -0,0 +1,39 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2020 Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['stableinterface'],
|
||||
'supported_by': 'core'}
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: win_util_args
|
||||
short_description: Short description
|
||||
description:
|
||||
- Some test description for the module
|
||||
options:
|
||||
my_opt:
|
||||
description:
|
||||
- Test description
|
||||
required: yes
|
||||
type: str
|
||||
extends_documentation_fragment:
|
||||
- ns.col.ps_util
|
||||
|
||||
author:
|
||||
- Ansible Test (@ansible)
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- win_util_args:
|
||||
option1: test
|
||||
my_opt: test
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
#
|
||||
'''
|
|
@ -0,0 +1,7 @@
|
|||
- hello:
|
||||
name: Ansibull
|
||||
register: hello
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- hello.message == 'Hello Ansibull'
|
|
@ -0,0 +1,8 @@
|
|||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
from .....plugins.module_utils.my_util import hello
|
||||
|
||||
|
||||
def test_hello():
|
||||
assert hello('Ansibull') == 'Hello Ansibull'
|
|
@ -0,0 +1,8 @@
|
|||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
from .....plugins.modules.hello import say_hello
|
||||
|
||||
|
||||
def test_say_hello():
|
||||
assert say_hello('Ansibull') == dict(message='Hello Ansibull')
|
18
test/integration/targets/ansible-test-docker/collection-tests/docker.sh
Executable file
18
test/integration/targets/ansible-test-docker/collection-tests/docker.sh
Executable file
|
@ -0,0 +1,18 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -eux -o pipefail
|
||||
|
||||
cp -a "${TEST_DIR}/ansible_collections" "${WORK_DIR}"
|
||||
cd "${WORK_DIR}/ansible_collections/ns/col"
|
||||
|
||||
# common args for all tests
|
||||
# because we are running in shippable/generic/ we are already in the default docker container
|
||||
common=(--python "${ANSIBLE_TEST_PYTHON_VERSION}" --color --truncate 0 "${@}")
|
||||
|
||||
# prime the venv to work around issue with PyYAML detection in ansible-test
|
||||
ansible-test sanity "${common[@]}" --test ignores
|
||||
|
||||
# tests
|
||||
ansible-test sanity "${common[@]}"
|
||||
ansible-test units "${common[@]}"
|
||||
ansible-test integration "${common[@]}"
|
24
test/integration/targets/ansible-test-docker/runme.sh
Executable file
24
test/integration/targets/ansible-test-docker/runme.sh
Executable file
|
@ -0,0 +1,24 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -eu -o pipefail
|
||||
|
||||
# tests must be executed outside of the ansible source tree
|
||||
# otherwise ansible-test will test the ansible source instead of the test collection
|
||||
# the temporary directory provided by ansible-test resides within the ansible source tree
|
||||
tmp_dir=$(mktemp -d)
|
||||
|
||||
trap 'rm -rf "${tmp_dir}"' EXIT
|
||||
|
||||
export TEST_DIR
|
||||
export WORK_DIR
|
||||
|
||||
TEST_DIR="$PWD"
|
||||
|
||||
for test in collection-tests/*.sh; do
|
||||
WORK_DIR="${tmp_dir}/$(basename "${test}" ".sh")"
|
||||
mkdir "${WORK_DIR}"
|
||||
echo "**********************************************************************"
|
||||
echo "TEST: ${test}: STARTING"
|
||||
"${test}" "${@}" || (echo "TEST: ${test}: FAILED" && exit 1)
|
||||
echo "TEST: ${test}: PASSED"
|
||||
done
|
|
@ -89,9 +89,9 @@ def setup_env(filename):
|
|||
del sys.modules[k]
|
||||
|
||||
|
||||
def get_ps_argument_spec(filename):
|
||||
# This uses a very small skeleton of Ansible.Basic.AnsibleModule to return the argspec defined by the module. This
|
||||
# is pretty rudimentary and will probably require something better going forward.
|
||||
def get_ps_argument_spec(filename, collection):
|
||||
fqc_name = get_module_name_from_filename(filename, collection)
|
||||
|
||||
pwsh = find_executable('pwsh')
|
||||
if not pwsh:
|
||||
raise FileNotFoundError('Required program for PowerShell arg spec inspection "pwsh" not found.')
|
||||
|
@ -102,11 +102,15 @@ def get_ps_argument_spec(filename):
|
|||
b_module_data = module_fd.read()
|
||||
|
||||
ps_dep_finder = PSModuleDepFinder()
|
||||
ps_dep_finder.scan_module(b_module_data)
|
||||
ps_dep_finder.scan_module(b_module_data, fqn=fqc_name)
|
||||
|
||||
# For ps_argspec.ps1 to compile Ansible.Basic it also needs the AddType module_util.
|
||||
ps_dep_finder._add_module((b"Ansible.ModuleUtils.AddType", ".psm1", None), wrapper=False)
|
||||
|
||||
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()])
|
||||
'ansible_basic': ps_dep_finder.cs_utils_module["Ansible.Basic"]['path'],
|
||||
'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')
|
||||
|
@ -115,7 +119,7 @@ def get_ps_argument_spec(filename):
|
|||
stdout, stderr = proc.communicate()
|
||||
|
||||
if proc.returncode != 0:
|
||||
raise AnsibleModuleImportError(stderr.decode('utf-8'))
|
||||
raise AnsibleModuleImportError("STDOUT:\n%s\nSTDERR:\n%s" % (stdout.decode('utf-8'), stderr.decode('utf-8')))
|
||||
|
||||
kwargs = json.loads(stdout)
|
||||
|
||||
|
@ -163,4 +167,4 @@ def get_argument_spec(filename, collection):
|
|||
if filename.endswith('.py'):
|
||||
return get_py_argument_spec(filename, collection)
|
||||
else:
|
||||
return get_ps_argument_spec(filename)
|
||||
return get_ps_argument_spec(filename, collection)
|
||||
|
|
|
@ -63,30 +63,6 @@ if (-not (Test-Path -LiteralPath $module_path -PathType Leaf)) {
|
|||
exit 1
|
||||
}
|
||||
|
||||
$dummy_ansible_basic = @'
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Management.Automation;
|
||||
|
||||
namespace Ansible.Basic
|
||||
{
|
||||
public class AnsibleModule
|
||||
{
|
||||
public AnsibleModule(string[] args, IDictionary argumentSpec)
|
||||
{
|
||||
ScriptBlock.Create("Set-Variable -Name arg_spec -Value $args[0] -Scope Global; exit 0"
|
||||
).Invoke(new Object[] { argumentSpec });
|
||||
}
|
||||
|
||||
public static AnsibleModule Create(string[] args, IDictionary argumentSpec)
|
||||
{
|
||||
return new AnsibleModule(args, argumentSpec);
|
||||
}
|
||||
}
|
||||
}
|
||||
'@
|
||||
Add-Type -TypeDefinition $dummy_ansible_basic
|
||||
|
||||
$module_code = Get-Content -LiteralPath $module_path -Raw
|
||||
|
||||
$powershell = [PowerShell]::Create()
|
||||
|
@ -111,9 +87,15 @@ if ($manifest.Contains('ps_utils')) {
|
|||
}) > $null
|
||||
$powershell.AddCommand('Import-Module').AddParameter('WarningAction', 'SilentlyContinue') > $null
|
||||
$powershell.AddCommand('Out-Null').AddStatement() > $null
|
||||
|
||||
# Also import it into the current runspace in case ps_argspec.ps1 needs to use it.
|
||||
$null = New-Module -Name $util_name -ScriptBlock $util_sb | Import-Module -WarningAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
|
||||
Add-CSharpType -References @(Get-Content -LiteralPath $manifest.ansible_basic -Raw)
|
||||
[Ansible.Basic.AnsibleModule]::_DebugArgSpec = $true
|
||||
|
||||
$powershell.AddScript($module_code) > $null
|
||||
$powershell.Invoke() > $null
|
||||
|
||||
|
@ -122,7 +104,7 @@ if ($powershell.HadErrors) {
|
|||
exit 1
|
||||
}
|
||||
|
||||
$arg_spec = $powershell.Runspace.SessionStateProxy.GetVariable('arg_spec')
|
||||
$arg_spec = $powershell.Runspace.SessionStateProxy.GetVariable('ansibleTestArgSpec')
|
||||
Resolve-CircularReference -Hash $arg_spec
|
||||
|
||||
ConvertTo-Json -InputObject $arg_spec -Compress -Depth 99
|
|
@ -219,11 +219,18 @@ class IntegrationAliasesTest(SanityVersionNeutral):
|
|||
messages.append(SanityMessage('invalid alias `%s`' % alias, '%s/aliases' % target.path))
|
||||
|
||||
messages += self.check_ci_group(
|
||||
targets=tuple(filter_targets(posix_targets, ['cloud/'], include=False, directories=False, errors=False)),
|
||||
targets=tuple(filter_targets(posix_targets, ['cloud/', 'shippable/generic/'], include=False,
|
||||
directories=False, errors=False)),
|
||||
find=self.format_shippable_group_alias('linux').replace('linux', 'posix'),
|
||||
find_incidental=['shippable/posix/incidental/'],
|
||||
)
|
||||
|
||||
messages += self.check_ci_group(
|
||||
targets=tuple(filter_targets(posix_targets, ['shippable/generic/'], include=True, directories=False,
|
||||
errors=False)),
|
||||
find=self.format_shippable_group_alias('generic'),
|
||||
)
|
||||
|
||||
for cloud in clouds:
|
||||
messages += self.check_ci_group(
|
||||
targets=tuple(filter_targets(posix_targets, ['cloud/%s/' % cloud], include=True, directories=False, errors=False)),
|
||||
|
|
|
@ -270,6 +270,9 @@ test/integration/targets/ansible-runner/files/playbook_example1.py metaclass-boi
|
|||
test/integration/targets/ansible-test/ansible_collections/ns/col/plugins/modules/hello.py pylint:relative-beyond-top-level
|
||||
test/integration/targets/ansible-test/ansible_collections/ns/col/tests/unit/plugins/module_utils/test_my_util.py pylint:relative-beyond-top-level
|
||||
test/integration/targets/ansible-test/ansible_collections/ns/col/tests/unit/plugins/modules/test_hello.py pylint:relative-beyond-top-level
|
||||
test/integration/targets/ansible-test-docker/ansible_collections/ns/col/plugins/modules/hello.py pylint:relative-beyond-top-level
|
||||
test/integration/targets/ansible-test-docker/ansible_collections/ns/col/tests/unit/plugins/module_utils/test_my_util.py pylint:relative-beyond-top-level
|
||||
test/integration/targets/ansible-test-docker/ansible_collections/ns/col/tests/unit/plugins/modules/test_hello.py pylint:relative-beyond-top-level
|
||||
test/integration/targets/async/library/async_test.py future-import-boilerplate
|
||||
test/integration/targets/async/library/async_test.py metaclass-boilerplate
|
||||
test/integration/targets/async_fail/library/async_test.py future-import-boilerplate
|
||||
|
|
18
test/utils/shippable/generic.sh
Executable file
18
test/utils/shippable/generic.sh
Executable file
|
@ -0,0 +1,18 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -o pipefail -eux
|
||||
|
||||
declare -a args
|
||||
IFS='/:' read -ra args <<< "$1"
|
||||
|
||||
python="${args[1]}"
|
||||
|
||||
if [ "${#args[@]}" -gt 2 ]; then
|
||||
target="shippable/generic/group${args[2]}/"
|
||||
else
|
||||
target="shippable/generic/"
|
||||
fi
|
||||
|
||||
# shellcheck disable=SC2086
|
||||
ansible-test integration --color -v --retry-on-error "${target}" ${COVERAGE:+"$COVERAGE"} ${CHANGED:+"$CHANGED"} ${UNSTABLE:+"$UNSTABLE"} \
|
||||
--docker default --python "${python}"
|
Loading…
Reference in a new issue