Ansible.Basic - add required_by to module spec (#51407)

* Ansible.Basic - add required_by to module spec

* fix typo in docs
pull/52198/head
Jordan Borean 6 years ago committed by GitHub
parent 994063bbf9
commit de118734e9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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…
Cancel
Save