mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
Ansible.Basic - add required_by to module spec (#51407)
* Ansible.Basic - add required_by to module spec * fix typo in docs
This commit is contained in:
parent
994063bbf9
commit
de118734e9
4 changed files with 231 additions and 2 deletions
|
@ -198,6 +198,7 @@ spec. The following options can be set at the root level of the argument spec:
|
||||||
- ``mutually_exclusive``: A list of lists, where the inner list contains module options that cannot be set together
|
- ``mutually_exclusive``: A list of lists, where the inner list contains module options that cannot be set together
|
||||||
- ``no_log``: Stops the module from emitting any logs to the Windows Event log
|
- ``no_log``: Stops the module from emitting any logs to the Windows Event log
|
||||||
- ``options``: A dictionary where the key is the module option and the value is the spec for that option
|
- ``options``: A dictionary where the key is the module option and the value is the spec for that option
|
||||||
|
- ``required_by``: A dictionary where the option(s) specified by the value must be set if the option specified by the key is also set
|
||||||
- ``required_if``: A list of lists where the inner list contains 3 or 4 elements;
|
- ``required_if``: A list of lists where the inner list contains 3 or 4 elements;
|
||||||
* The first element is the module option to check the value against
|
* The first element is the module option to check the value against
|
||||||
* The second element is the value of the option specified by the first element, if matched then the required if check is run
|
* The second element is the value of the option specified by the first element, if matched then the required if check is run
|
||||||
|
@ -236,6 +237,7 @@ When ``type=dict``, or ``type=list`` and ``elements=dict``, the following keys c
|
||||||
- ``mutually_exclusive``: Same as the root level ``mutually_exclusive`` but validated against the values in the sub dict
|
- ``mutually_exclusive``: Same as the root level ``mutually_exclusive`` but validated against the values in the sub dict
|
||||||
- ``options``: Same as the root level ``options`` but contains the valid options for the sub option
|
- ``options``: Same as the root level ``options`` but contains the valid options for the sub option
|
||||||
- ``required_if``: Same as the root level ``required_if`` but validated against the values in the sub dict
|
- ``required_if``: Same as the root level ``required_if`` but validated against the values in the sub dict
|
||||||
|
- ``required_by``: Same as the root level ``required_by`` but validated against the values in the sub dict
|
||||||
- ``required_together``: Same as the root level ``required_together`` but validated against the values in the sub dict
|
- ``required_together``: Same as the root level ``required_together`` but validated against the values in the sub dict
|
||||||
- ``required_one_of``: Same as the root level ``required_one_of`` but validated against the values in the sub dict
|
- ``required_one_of``: Same as the root level ``required_one_of`` but validated against the values in the sub dict
|
||||||
|
|
||||||
|
|
|
@ -83,6 +83,7 @@ namespace Ansible.Basic
|
||||||
{ "options", new List<object>() { typeof(Hashtable), typeof(Hashtable) } },
|
{ "options", new List<object>() { typeof(Hashtable), typeof(Hashtable) } },
|
||||||
{ "removed_in_version", new List<object>() { null, typeof(string) } },
|
{ "removed_in_version", new List<object>() { null, typeof(string) } },
|
||||||
{ "required", new List<object>() { false, typeof(bool) } },
|
{ "required", new List<object>() { false, typeof(bool) } },
|
||||||
|
{ "required_by", new List<object>() { typeof(Hashtable), typeof(Hashtable) } },
|
||||||
{ "required_if", new List<object>() { typeof(List<List<object>>), null } },
|
{ "required_if", new List<object>() { typeof(List<List<object>>), null } },
|
||||||
{ "required_one_of", new List<object>() { typeof(List<List<string>>), null } },
|
{ "required_one_of", new List<object>() { typeof(List<List<string>>), null } },
|
||||||
{ "required_together", new List<object>() { typeof(List<List<string>>), null } },
|
{ "required_together", new List<object>() { typeof(List<List<string>>), null } },
|
||||||
|
@ -792,6 +793,7 @@ namespace Ansible.Basic
|
||||||
CheckRequiredTogether(param, (IList)spec["required_together"]);
|
CheckRequiredTogether(param, (IList)spec["required_together"]);
|
||||||
CheckRequiredOneOf(param, (IList)spec["required_one_of"]);
|
CheckRequiredOneOf(param, (IList)spec["required_one_of"]);
|
||||||
CheckRequiredIf(param, (IList)spec["required_if"]);
|
CheckRequiredIf(param, (IList)spec["required_if"]);
|
||||||
|
CheckRequiredBy(param, (IDictionary)spec["required_by"]);
|
||||||
|
|
||||||
// finally ensure all missing parameters are set to null and handle sub options
|
// finally ensure all missing parameters are set to null and handle sub options
|
||||||
foreach (DictionaryEntry entry in optionSpec)
|
foreach (DictionaryEntry entry in optionSpec)
|
||||||
|
@ -1012,6 +1014,28 @@ namespace Ansible.Basic
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void CheckRequiredBy(IDictionary param, IDictionary requiredBy)
|
||||||
|
{
|
||||||
|
foreach (DictionaryEntry entry in requiredBy)
|
||||||
|
{
|
||||||
|
string key = (string)entry.Key;
|
||||||
|
if (!param.Contains(key))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
List<string> missing = new List<string>();
|
||||||
|
List<string> requires = ParseList(entry.Value).Cast<string>().ToList();
|
||||||
|
foreach (string required in requires)
|
||||||
|
if (!param.Contains(required))
|
||||||
|
missing.Add(required);
|
||||||
|
|
||||||
|
if (missing.Count > 0)
|
||||||
|
{
|
||||||
|
string msg = String.Format("missing parameter(s) required by '{0}': {1}", key, String.Join(", ", missing));
|
||||||
|
FailJson(FormatOptionsContext(msg));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void CheckSubOption(IDictionary param, string key, IDictionary spec)
|
private void CheckSubOption(IDictionary param, string key, IDictionary spec)
|
||||||
{
|
{
|
||||||
string type;
|
string type;
|
||||||
|
|
|
@ -12,6 +12,9 @@ $spec = @{
|
||||||
state = @{ type = "str"; choices = "absent", "present"; default = "present" }
|
state = @{ type = "str"; choices = "absent", "present"; default = "present" }
|
||||||
value = @{ type = "str" }
|
value = @{ type = "str" }
|
||||||
}
|
}
|
||||||
|
required_by = @{
|
||||||
|
present = @("value")
|
||||||
|
}
|
||||||
required_if = @(,@("state", "present", @("value")))
|
required_if = @(,@("state", "present", @("value")))
|
||||||
supports_check_mode = $true
|
supports_check_mode = $true
|
||||||
}
|
}
|
||||||
|
|
|
@ -708,6 +708,206 @@ test_no_log - Invoked with:
|
||||||
$actual | Assert-DictionaryEquals -Expected $expected
|
$actual | Assert-DictionaryEquals -Expected $expected
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"Required by - single value" = {
|
||||||
|
$spec = @{
|
||||||
|
options = @{
|
||||||
|
option1 = @{type = "str"}
|
||||||
|
option2 = @{type = "str"}
|
||||||
|
option3 = @{type = "str"}
|
||||||
|
}
|
||||||
|
required_by = @{
|
||||||
|
option1 = "option2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$complex_args = @{
|
||||||
|
option1 = "option1"
|
||||||
|
option2 = "option2"
|
||||||
|
}
|
||||||
|
|
||||||
|
$m = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
|
||||||
|
|
||||||
|
$failed = $false
|
||||||
|
try {
|
||||||
|
$m.ExitJson()
|
||||||
|
} catch [System.Management.Automation.RuntimeException] {
|
||||||
|
$failed = $true
|
||||||
|
$_.Exception.Message | Assert-Equals -Expected "exit: 0"
|
||||||
|
$actual = [Ansible.Basic.AnsibleModule]::FromJson($_test_out)
|
||||||
|
}
|
||||||
|
$failed | Assert-Equals -Expected $true
|
||||||
|
|
||||||
|
$expected = @{
|
||||||
|
changed = $false
|
||||||
|
invocation = @{
|
||||||
|
module_args = @{
|
||||||
|
option1 = "option1"
|
||||||
|
option2 = "option2"
|
||||||
|
option3 = $null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$actual | Assert-DictionaryEquals -Expected $expected
|
||||||
|
}
|
||||||
|
|
||||||
|
"Required by - multiple values" = {
|
||||||
|
$spec = @{
|
||||||
|
options = @{
|
||||||
|
option1 = @{type = "str"}
|
||||||
|
option2 = @{type = "str"}
|
||||||
|
option3 = @{type = "str"}
|
||||||
|
}
|
||||||
|
required_by = @{
|
||||||
|
option1 = "option2", "option3"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$complex_args = @{
|
||||||
|
option1 = "option1"
|
||||||
|
option2 = "option2"
|
||||||
|
option3 = "option3"
|
||||||
|
}
|
||||||
|
|
||||||
|
$m = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
|
||||||
|
|
||||||
|
$failed = $false
|
||||||
|
try {
|
||||||
|
$m.ExitJson()
|
||||||
|
} catch [System.Management.Automation.RuntimeException] {
|
||||||
|
$failed = $true
|
||||||
|
$_.Exception.Message | Assert-Equals -Expected "exit: 0"
|
||||||
|
$actual = [Ansible.Basic.AnsibleModule]::FromJson($_test_out)
|
||||||
|
}
|
||||||
|
$failed | Assert-Equals -Expected $true
|
||||||
|
|
||||||
|
$expected = @{
|
||||||
|
changed = $false
|
||||||
|
invocation = @{
|
||||||
|
module_args = @{
|
||||||
|
option1 = "option1"
|
||||||
|
option2 = "option2"
|
||||||
|
option3 = "option3"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$actual | Assert-DictionaryEquals -Expected $expected
|
||||||
|
}
|
||||||
|
|
||||||
|
"Required by explicit null" = {
|
||||||
|
$spec = @{
|
||||||
|
options = @{
|
||||||
|
option1 = @{type = "str"}
|
||||||
|
option2 = @{type = "str"}
|
||||||
|
option3 = @{type = "str"}
|
||||||
|
}
|
||||||
|
required_by = @{
|
||||||
|
option1 = "option2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$complex_args = @{
|
||||||
|
option1 = "option1"
|
||||||
|
option2 = $null
|
||||||
|
}
|
||||||
|
|
||||||
|
$m = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
|
||||||
|
|
||||||
|
$failed = $false
|
||||||
|
try {
|
||||||
|
$m.ExitJson()
|
||||||
|
} catch [System.Management.Automation.RuntimeException] {
|
||||||
|
$failed = $true
|
||||||
|
$_.Exception.Message | Assert-Equals -Expected "exit: 0"
|
||||||
|
$actual = [Ansible.Basic.AnsibleModule]::FromJson($_test_out)
|
||||||
|
}
|
||||||
|
$failed | Assert-Equals -Expected $true
|
||||||
|
|
||||||
|
$expected = @{
|
||||||
|
changed = $false
|
||||||
|
invocation = @{
|
||||||
|
module_args = @{
|
||||||
|
option1 = "option1"
|
||||||
|
option2 = $null
|
||||||
|
option3 = $null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$actual | Assert-DictionaryEquals -Expected $expected
|
||||||
|
}
|
||||||
|
|
||||||
|
"Required by failed - single value" = {
|
||||||
|
$spec = @{
|
||||||
|
options = @{
|
||||||
|
option1 = @{type = "str"}
|
||||||
|
option2 = @{type = "str"}
|
||||||
|
option3 = @{type = "str"}
|
||||||
|
}
|
||||||
|
required_by = @{
|
||||||
|
option1 = "option2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$complex_args = @{
|
||||||
|
option1 = "option1"
|
||||||
|
}
|
||||||
|
|
||||||
|
$failed = $false
|
||||||
|
try {
|
||||||
|
$m = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
|
||||||
|
} catch [System.Management.Automation.RuntimeException] {
|
||||||
|
$failed = $true
|
||||||
|
$_.Exception.Message | Assert-Equals -Expected "exit: 1"
|
||||||
|
$actual = [Ansible.Basic.AnsibleModule]::FromJson($_test_out)
|
||||||
|
}
|
||||||
|
$failed | Assert-Equals -Expected $true
|
||||||
|
|
||||||
|
$expected = @{
|
||||||
|
changed = $false
|
||||||
|
failed = $true
|
||||||
|
invocation = @{
|
||||||
|
module_args = @{
|
||||||
|
option1 = "option1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
msg = "missing parameter(s) required by 'option1': option2"
|
||||||
|
}
|
||||||
|
$actual | Assert-DictionaryEquals -Expected $expected
|
||||||
|
}
|
||||||
|
|
||||||
|
"Required by failed - multiple values" = {
|
||||||
|
$spec = @{
|
||||||
|
options = @{
|
||||||
|
option1 = @{type = "str"}
|
||||||
|
option2 = @{type = "str"}
|
||||||
|
option3 = @{type = "str"}
|
||||||
|
}
|
||||||
|
required_by = @{
|
||||||
|
option1 = "option2", "option3"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$complex_args = @{
|
||||||
|
option1 = "option1"
|
||||||
|
}
|
||||||
|
|
||||||
|
$failed = $false
|
||||||
|
try {
|
||||||
|
$m = [Ansible.Basic.AnsibleModule]::Create(@(), $spec)
|
||||||
|
} catch [System.Management.Automation.RuntimeException] {
|
||||||
|
$failed = $true
|
||||||
|
$_.Exception.Message | Assert-Equals -Expected "exit: 1"
|
||||||
|
$actual = [Ansible.Basic.AnsibleModule]::FromJson($_test_out)
|
||||||
|
}
|
||||||
|
$failed | Assert-Equals -Expected $true
|
||||||
|
|
||||||
|
$expected = @{
|
||||||
|
changed = $false
|
||||||
|
failed = $true
|
||||||
|
invocation = @{
|
||||||
|
module_args = @{
|
||||||
|
option1 = "option1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
msg = "missing parameter(s) required by 'option1': option2, option3"
|
||||||
|
}
|
||||||
|
$actual | Assert-DictionaryEquals -Expected $expected
|
||||||
|
}
|
||||||
|
|
||||||
"Debug without debug set" = {
|
"Debug without debug set" = {
|
||||||
$complex_args = @{
|
$complex_args = @{
|
||||||
_ansible_debug = $false
|
_ansible_debug = $false
|
||||||
|
@ -1184,7 +1384,7 @@ test_no_log - Invoked with:
|
||||||
|
|
||||||
$expected_msg = "internal error: argument spec entry contains an invalid key 'invalid', valid keys: apply_defaults, "
|
$expected_msg = "internal error: argument spec entry contains an invalid key 'invalid', valid keys: apply_defaults, "
|
||||||
$expected_msg += "aliases, choices, default, elements, mutually_exclusive, no_log, options, removed_in_version, "
|
$expected_msg += "aliases, choices, default, elements, mutually_exclusive, no_log, options, removed_in_version, "
|
||||||
$expected_msg += "required, required_if, required_one_of, required_together, supports_check_mode, type"
|
$expected_msg += "required, required_by, required_if, required_one_of, required_together, supports_check_mode, type"
|
||||||
|
|
||||||
$actual.Keys.Count | Assert-Equals -Expected 3
|
$actual.Keys.Count | Assert-Equals -Expected 3
|
||||||
$actual.failed | Assert-Equals -Expected $true
|
$actual.failed | Assert-Equals -Expected $true
|
||||||
|
@ -1216,7 +1416,7 @@ test_no_log - Invoked with:
|
||||||
|
|
||||||
$expected_msg = "internal error: argument spec entry contains an invalid key 'invalid', valid keys: apply_defaults, "
|
$expected_msg = "internal error: argument spec entry contains an invalid key 'invalid', valid keys: apply_defaults, "
|
||||||
$expected_msg += "aliases, choices, default, elements, mutually_exclusive, no_log, options, removed_in_version, "
|
$expected_msg += "aliases, choices, default, elements, mutually_exclusive, no_log, options, removed_in_version, "
|
||||||
$expected_msg += "required, required_if, required_one_of, required_together, supports_check_mode, type - "
|
$expected_msg += "required, required_by, required_if, required_one_of, required_together, supports_check_mode, type - "
|
||||||
$expected_msg += "found in option_key -> sub_option_key"
|
$expected_msg += "found in option_key -> sub_option_key"
|
||||||
|
|
||||||
$actual.Keys.Count | Assert-Equals -Expected 3
|
$actual.Keys.Count | Assert-Equals -Expected 3
|
||||||
|
|
Loading…
Reference in a new issue