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
|
||||
- ``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
|
||||
- ``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;
|
||||
* 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
|
||||
|
@ -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
|
||||
- ``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_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_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) } },
|
||||
{ "removed_in_version", new List<object>() { null, typeof(string) } },
|
||||
{ "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_one_of", 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"]);
|
||||
CheckRequiredOneOf(param, (IList)spec["required_one_of"]);
|
||||
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
|
||||
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)
|
||||
{
|
||||
string type;
|
||||
|
|
|
@ -12,6 +12,9 @@ $spec = @{
|
|||
state = @{ type = "str"; choices = "absent", "present"; default = "present" }
|
||||
value = @{ type = "str" }
|
||||
}
|
||||
required_by = @{
|
||||
present = @("value")
|
||||
}
|
||||
required_if = @(,@("state", "present", @("value")))
|
||||
supports_check_mode = $true
|
||||
}
|
||||
|
|
|
@ -708,6 +708,206 @@ test_no_log - Invoked with:
|
|||
$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" = {
|
||||
$complex_args = @{
|
||||
_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 += "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.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 += "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"
|
||||
|
||||
$actual.Keys.Count | Assert-Equals -Expected 3
|
||||
|
|
Loading…
Reference in a new issue