mirror of
https://github.com/ansible-collections/community.general.git
synced 2026-06-10 18:15:39 +00:00
Merge b4f9830862 into 877f20f278
This commit is contained in:
commit
8ba0e8f25b
3 changed files with 116 additions and 28 deletions
3
changelogs/fragments/random_string_avoid.yml
Normal file
3
changelogs/fragments/random_string_avoid.yml
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
---
|
||||
minor_changes:
|
||||
- random_string lookup plugin - allow user to set ``avoid_special_first`` and ``avoid_special_last`` (https://github.com/ansible-collections/community.general/issues/11816, https://github.com/ansible-collections/community.general/pull/11819).
|
||||
|
|
@ -106,6 +106,18 @@ options:
|
|||
B(Do not use the generated string as a password or a secure token when using this option!)
|
||||
type: str
|
||||
version_added: 11.3.0
|
||||
avoid_special_first:
|
||||
description:
|
||||
- Avoid special characters at the first position of the string.
|
||||
type: bool
|
||||
default: false
|
||||
version_added: 12.6.0
|
||||
avoid_special_last:
|
||||
description:
|
||||
- Avoid special characters at the last position of the string.
|
||||
type: bool
|
||||
default: false
|
||||
version_added: 12.6.0
|
||||
"""
|
||||
|
||||
EXAMPLES = r"""
|
||||
|
|
@ -155,6 +167,16 @@ EXAMPLES = r"""
|
|||
vars:
|
||||
hex_chars: '0123456789ABCDEF'
|
||||
# Example result: ['D2A40737']
|
||||
|
||||
- name: Generate random string with avoid_special_first
|
||||
debug:
|
||||
var: query('community.general.random_string', avoid_special_first=true)
|
||||
# Example result: ['U@n0hUiX5kVG']
|
||||
|
||||
- name: Generate random string with avoid_special_last
|
||||
debug:
|
||||
var: query('community.general.random_string', avoid_special_last=true)
|
||||
# Example result: ['U@n0hUiX5kVG']
|
||||
"""
|
||||
|
||||
RETURN = r"""
|
||||
|
|
@ -177,12 +199,6 @@ from ansible_collections.community.general.plugins.plugin_utils._lookup import c
|
|||
|
||||
|
||||
class LookupModule(LookupBase):
|
||||
@staticmethod
|
||||
def get_random(random_generator, chars, length):
|
||||
if not chars:
|
||||
raise AnsibleLookupError("Available characters cannot be None, please change constraints")
|
||||
return "".join(random_generator.choice(chars) for dummy in range(length))
|
||||
|
||||
@staticmethod
|
||||
def b64encode(string_value, encoding="utf-8"):
|
||||
return to_text(base64.b64encode(to_bytes(string_value, encoding=encoding, errors="surrogate_or_strict")))
|
||||
|
|
@ -199,8 +215,11 @@ class LookupModule(LookupBase):
|
|||
length = self.get_option("length")
|
||||
base64_flag = self.get_option("base64")
|
||||
override_all = self.get_option("override_all")
|
||||
override_special = self.get_option("override_special")
|
||||
ignore_similar_chars = self.get_option("ignore_similar_chars")
|
||||
similar_chars = self.get_option("similar_chars")
|
||||
avoid_special_first = self.get_option("avoid_special_first")
|
||||
avoid_special_last = self.get_option("avoid_special_last")
|
||||
seed = self.get_option("seed")
|
||||
|
||||
if seed is None:
|
||||
|
|
@ -208,7 +227,7 @@ class LookupModule(LookupBase):
|
|||
else:
|
||||
random_generator = random.Random(seed)
|
||||
|
||||
values = ""
|
||||
result = []
|
||||
available_chars_set = ""
|
||||
|
||||
if ignore_similar_chars:
|
||||
|
|
@ -225,7 +244,6 @@ class LookupModule(LookupBase):
|
|||
lower = self.get_option("lower")
|
||||
numbers = self.get_option("numbers")
|
||||
special = self.get_option("special")
|
||||
override_special = self.get_option("override_special")
|
||||
|
||||
if override_special:
|
||||
special_chars = override_special
|
||||
|
|
@ -239,27 +257,60 @@ class LookupModule(LookupBase):
|
|||
if special:
|
||||
available_chars_set += special_chars
|
||||
|
||||
mapping = {
|
||||
"min_numeric": number_chars,
|
||||
"min_lower": lower_chars,
|
||||
"min_upper": upper_chars,
|
||||
"min_special": special_chars,
|
||||
}
|
||||
if len(available_chars_set) == 0:
|
||||
raise AnsibleLookupError("Available characters cannot be empty, please change constraints")
|
||||
|
||||
for m in mapping:
|
||||
if self.get_option(m):
|
||||
values += self.get_random(random_generator, mapping[m], self.get_option(m))
|
||||
min_numeric = self.get_option("min_numeric")
|
||||
min_lower = self.get_option("min_lower")
|
||||
min_upper = self.get_option("min_upper")
|
||||
min_special = self.get_option("min_special")
|
||||
min_non_special = min_numeric + min_lower + min_upper
|
||||
if avoid_special_first and avoid_special_last:
|
||||
min_non_special = max(min_non_special, 2)
|
||||
elif avoid_special_first or avoid_special_last:
|
||||
min_non_special = max(min_non_special, 1)
|
||||
|
||||
remaining_pass_len = length - len(values)
|
||||
values += self.get_random(random_generator, available_chars_set, remaining_pass_len)
|
||||
if length < min_special + min_non_special:
|
||||
raise AnsibleLookupError("Minimum requirements exceed total length, please increase the length")
|
||||
|
||||
shuffled_values = list(values)
|
||||
if seed is None:
|
||||
# Get pseudo randomization
|
||||
# Randomize the order
|
||||
random.shuffle(shuffled_values)
|
||||
# Ensure minimum requirements
|
||||
result += [random_generator.choice(number_chars) for dummy in range(min_numeric)]
|
||||
result += [random_generator.choice(lower_chars) for dummy in range(min_lower)]
|
||||
result += [random_generator.choice(upper_chars) for dummy in range(min_upper)]
|
||||
if override_special:
|
||||
result += [random_generator.choice(override_special) for dummy in range(min_special)]
|
||||
else:
|
||||
result += [random_generator.choice(special_chars) for dummy in range(min_special)]
|
||||
|
||||
# Fill remaining length
|
||||
remaining_length = length - len(result)
|
||||
if min_non_special <= remaining_length:
|
||||
# atleast one non-special character
|
||||
available_chars_set = available_chars_set.replace(special_chars, "")
|
||||
|
||||
result += [random_generator.choice(available_chars_set) for dummy in range(remaining_length)]
|
||||
|
||||
# Shuffle to avoid predictable pattern
|
||||
random_generator.shuffle(result)
|
||||
|
||||
# Handle first/last character constraints
|
||||
if avoid_special_first and result[0] in special_chars:
|
||||
for i in range(1, len(result)):
|
||||
if result[i] not in special_chars:
|
||||
result[0], result[i] = result[i], result[0]
|
||||
break
|
||||
else:
|
||||
raise AssertionError("No character satisfies the constraints for avoid_special_first")
|
||||
|
||||
if avoid_special_last and result[-1] in special_chars:
|
||||
for i in range(len(result) - 2, -1, -1):
|
||||
if result[i] not in special_chars:
|
||||
result[-1], result[i] = result[i], result[-1]
|
||||
break
|
||||
else:
|
||||
raise AssertionError("No character satisfies the constraints for avoid_special_last")
|
||||
|
||||
if base64_flag:
|
||||
return [self.b64encode("".join(shuffled_values))]
|
||||
return [self.b64encode("".join(result))]
|
||||
|
||||
return ["".join(shuffled_values)]
|
||||
return ["".join(result)]
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
result1: "{{ query('community.general.random_string') }}"
|
||||
result2: "{{ query('community.general.random_string', length=0) }}"
|
||||
result3: "{{ query('community.general.random_string', length=10) }}"
|
||||
result4: "{{ query('community.general.random_string', length=-1) }}"
|
||||
result4: "{{ query('community.general.random_string', length=6) }}"
|
||||
result5: "{{ query('community.general.random_string', override_special='_', min_special=1) }}"
|
||||
result6: "{{ query('community.general.random_string', upper=false, special=false) }}" # lower case only
|
||||
result7: "{{ query('community.general.random_string', lower=false, special=false) }}" # upper case only
|
||||
|
|
@ -23,6 +23,9 @@
|
|||
result13: "{{ query('community.general.random_string', override_all='0', length=2) }}"
|
||||
result14: "{{ query('community.general.random_string', seed='1234') }}"
|
||||
result15: "{{ query('community.general.random_string', seed='1234') }}"
|
||||
result16: "{{ query('community.general.random_string', avoid_special_first=true) }}"
|
||||
result17: "{{ query('community.general.random_string', avoid_special_last=true) }}"
|
||||
result18: "{{ query('community.general.random_string', length=10, avoid_special_first=true, avoid_special_last=true, min_special=8) }}"
|
||||
|
||||
- name: Raise error when impossible constraints are provided
|
||||
set_fact:
|
||||
|
|
@ -30,13 +33,33 @@
|
|||
ignore_errors: true
|
||||
register: impossible_result
|
||||
|
||||
- name: Raise error when override_all is provided with special characters and avoid_special_first is provided
|
||||
set_fact:
|
||||
error: "{{ query('community.general.random_string', override_all='!@#$%^&*()', avoid_special_first=true) }}"
|
||||
ignore_errors: true
|
||||
register: override_all_special_characters_avoid_special_first_error
|
||||
|
||||
- name: Raise error when override_all is provided with special characters and avoid_special_last is provided
|
||||
set_fact:
|
||||
error: "{{ query('community.general.random_string', override_all='!@#$%^&*()', avoid_special_last=true) }}"
|
||||
ignore_errors: true
|
||||
register: override_all_special_characters_avoid_special_last_error
|
||||
|
||||
- name: Raise error when impossible constraints are provided
|
||||
set_fact:
|
||||
error: "{{ query('community.general.random_string', length=10, avoid_special_first=true, avoid_special_last=true, min_special=9) }}"
|
||||
ignore_errors: true
|
||||
register: len_10_avoid_special_first_avoid_special_last_min_special_9
|
||||
|
||||
- debug:
|
||||
var: len_10_avoid_special_first_avoid_special_last_min_special_9
|
||||
- name: Check results
|
||||
assert:
|
||||
that:
|
||||
- result1[0] | length == 8
|
||||
- result2[0] | length == 0
|
||||
- result3[0] | length == 10
|
||||
- result4[0] | length == 0
|
||||
- result4[0] | length == 6
|
||||
- result5[0] | length == 8
|
||||
- "'_' in result5[0]"
|
||||
- result6[0] is lower
|
||||
|
|
@ -54,3 +77,14 @@
|
|||
- impossible_result is failed
|
||||
- "'Available characters cannot' in impossible_result.msg"
|
||||
- result15[0] == result14[0]
|
||||
- result16[0][0] not in '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~'
|
||||
- result17[0][-1] not in '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~'
|
||||
- result18[0] | length == 10
|
||||
- result18[0][0] not in '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~'
|
||||
- result18[0][-1] not in '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~'
|
||||
- override_all_special_characters_avoid_special_first_error is failed
|
||||
- "'No character satisfies the constraints for avoid_special_first' in override_all_special_characters_avoid_special_first_error.msg"
|
||||
- override_all_special_characters_avoid_special_last_error is failed
|
||||
- "'No character satisfies the constraints for avoid_special_last' in override_all_special_characters_avoid_special_last_error.msg"
|
||||
- len_10_avoid_special_first_avoid_special_last_min_special_9 is failed
|
||||
- "'Minimum requirements exceed total length, please increase the length' in len_10_avoid_special_first_avoid_special_last_min_special_9.msg"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue