ansible-core 2.19.4rc1__py3-none-any.whl → 2.20.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of ansible-core might be problematic. Click here for more details.
- ansible/_internal/__init__.py +1 -4
- ansible/_internal/_ansiballz/_builder.py +1 -3
- ansible/_internal/_collection_proxy.py +7 -9
- ansible/_internal/_json/__init__.py +3 -4
- ansible/_internal/_templating/_engine.py +1 -1
- ansible/_internal/_templating/_jinja_plugins.py +1 -2
- ansible/_internal/_wrapt.py +105 -301
- ansible/cli/__init__.py +11 -10
- ansible/cli/adhoc.py +1 -2
- ansible/cli/arguments/option_helpers.py +1 -1
- ansible/cli/config.py +5 -6
- ansible/cli/doc.py +67 -67
- ansible/cli/galaxy.py +15 -24
- ansible/cli/inventory.py +0 -1
- ansible/cli/playbook.py +0 -1
- ansible/cli/pull.py +0 -1
- ansible/cli/scripts/ansible_connection_cli_stub.py +1 -1
- ansible/config/base.yml +1 -25
- ansible/config/manager.py +0 -2
- ansible/executor/play_iterator.py +42 -20
- ansible/executor/playbook_executor.py +0 -9
- ansible/executor/task_executor.py +26 -18
- ansible/executor/task_queue_manager.py +1 -3
- ansible/galaxy/api.py +33 -80
- ansible/galaxy/collection/__init__.py +11 -21
- ansible/galaxy/dependency_resolution/__init__.py +10 -9
- ansible/galaxy/dependency_resolution/dataclasses.py +86 -70
- ansible/galaxy/dependency_resolution/providers.py +54 -134
- ansible/galaxy/dependency_resolution/versioning.py +2 -4
- ansible/galaxy/role.py +1 -33
- ansible/inventory/manager.py +2 -3
- ansible/keyword_desc.yml +0 -3
- ansible/module_utils/_internal/_datatag/__init__.py +2 -10
- ansible/module_utils/_internal/_no_six.py +86 -0
- ansible/module_utils/_text.py +28 -8
- ansible/module_utils/ansible_release.py +2 -2
- ansible/module_utils/basic.py +26 -23
- ansible/module_utils/common/_collections_compat.py +11 -2
- ansible/module_utils/common/collections.py +8 -3
- ansible/module_utils/common/dict_transformations.py +1 -2
- ansible/module_utils/common/network.py +4 -2
- ansible/module_utils/common/parameters.py +32 -41
- ansible/module_utils/common/text/converters.py +109 -23
- ansible/module_utils/common/text/formatters.py +6 -2
- ansible/module_utils/common/validation.py +11 -9
- ansible/module_utils/connection.py +8 -3
- ansible/module_utils/facts/hardware/linux.py +23 -7
- ansible/module_utils/facts/hardware/netbsd.py +1 -1
- ansible/module_utils/facts/hardware/sunos.py +2 -1
- ansible/module_utils/facts/packages.py +6 -2
- ansible/module_utils/facts/system/distribution.py +2 -1
- ansible/module_utils/facts/system/env.py +6 -3
- ansible/module_utils/facts/system/local.py +3 -1
- ansible/module_utils/parsing/convert_bool.py +6 -2
- ansible/module_utils/service.py +2 -3
- ansible/module_utils/six/__init__.py +11 -6
- ansible/module_utils/yumdnf.py +0 -5
- ansible/modules/apt.py +18 -13
- ansible/modules/apt_repository.py +1 -1
- ansible/modules/assemble.py +5 -9
- ansible/modules/blockinfile.py +39 -23
- ansible/modules/cron.py +26 -35
- ansible/modules/deb822_repository.py +83 -12
- ansible/modules/dnf.py +3 -7
- ansible/modules/dnf5.py +4 -6
- ansible/modules/expect.py +0 -3
- ansible/modules/find.py +1 -2
- ansible/modules/get_url.py +1 -1
- ansible/modules/git.py +4 -5
- ansible/modules/include_vars.py +1 -1
- ansible/modules/known_hosts.py +7 -1
- ansible/modules/lineinfile.py +71 -63
- ansible/modules/package_facts.py +1 -1
- ansible/modules/pip.py +8 -2
- ansible/modules/replace.py +6 -6
- ansible/modules/service.py +3 -4
- ansible/modules/stat.py +20 -0
- ansible/modules/uri.py +9 -10
- ansible/modules/user.py +1 -2
- ansible/modules/wait_for.py +2 -2
- ansible/modules/wait_for_connection.py +2 -1
- ansible/modules/yum_repository.py +1 -16
- ansible/parsing/dataloader.py +24 -31
- ansible/parsing/vault/__init__.py +1 -2
- ansible/playbook/base.py +8 -56
- ansible/playbook/block.py +0 -60
- ansible/playbook/collectionsearch.py +1 -2
- ansible/playbook/handler.py +1 -7
- ansible/playbook/helpers.py +0 -7
- ansible/playbook/included_file.py +1 -1
- ansible/playbook/play.py +102 -36
- ansible/playbook/play_context.py +4 -0
- ansible/playbook/role/__init__.py +10 -65
- ansible/playbook/role/definition.py +3 -4
- ansible/playbook/role/include.py +2 -3
- ansible/playbook/role/metadata.py +1 -12
- ansible/playbook/role/requirement.py +1 -2
- ansible/playbook/role_include.py +1 -2
- ansible/playbook/taggable.py +16 -5
- ansible/playbook/task.py +11 -50
- ansible/plugins/action/__init__.py +20 -19
- ansible/plugins/action/add_host.py +1 -2
- ansible/plugins/action/fetch.py +3 -5
- ansible/plugins/action/group_by.py +1 -2
- ansible/plugins/action/include_vars.py +20 -22
- ansible/plugins/action/script.py +1 -3
- ansible/plugins/action/template.py +1 -2
- ansible/plugins/action/uri.py +4 -2
- ansible/plugins/cache/__init__.py +1 -0
- ansible/plugins/callback/__init__.py +13 -6
- ansible/plugins/connection/__init__.py +3 -7
- ansible/plugins/connection/local.py +2 -3
- ansible/plugins/connection/psrp.py +0 -2
- ansible/plugins/connection/ssh.py +2 -7
- ansible/plugins/connection/winrm.py +0 -2
- ansible/plugins/doc_fragments/result_format_callback.py +15 -0
- ansible/plugins/filter/core.py +4 -5
- ansible/plugins/filter/encryption.py +3 -27
- ansible/plugins/filter/mathstuff.py +1 -2
- ansible/plugins/filter/to_nice_yaml.yml +31 -3
- ansible/plugins/filter/to_yaml.yml +29 -12
- ansible/plugins/inventory/__init__.py +1 -2
- ansible/plugins/inventory/toml.py +3 -6
- ansible/plugins/inventory/yaml.py +1 -2
- ansible/plugins/loader.py +3 -4
- ansible/plugins/lookup/password.py +1 -2
- ansible/plugins/lookup/subelements.py +2 -3
- ansible/plugins/lookup/url.py +1 -1
- ansible/plugins/lookup/varnames.py +1 -2
- ansible/plugins/shell/__init__.py +9 -4
- ansible/plugins/shell/powershell.py +8 -24
- ansible/plugins/strategy/__init__.py +5 -2
- ansible/plugins/test/core.py +4 -1
- ansible/plugins/test/falsy.yml +1 -1
- ansible/plugins/test/regex.yml +18 -6
- ansible/plugins/test/truthy.yml +1 -1
- ansible/release.py +2 -2
- ansible/template/__init__.py +3 -7
- ansible/utils/collection_loader/_collection_config.py +5 -0
- ansible/utils/collection_loader/_collection_finder.py +11 -14
- ansible/utils/context_objects.py +7 -4
- ansible/utils/display.py +7 -6
- ansible/utils/encrypt.py +0 -5
- ansible/utils/helpers.py +6 -2
- ansible/utils/jsonrpc.py +7 -3
- ansible/utils/plugin_docs.py +49 -38
- ansible/utils/ssh_functions.py +0 -19
- ansible/utils/unsafe_proxy.py +7 -7
- ansible/vars/clean.py +2 -3
- ansible/vars/manager.py +28 -22
- ansible/vars/plugins.py +1 -31
- {ansible_core-2.19.4rc1.dist-info → ansible_core-2.20.0.dist-info}/METADATA +4 -4
- {ansible_core-2.19.4rc1.dist-info → ansible_core-2.20.0.dist-info}/RECORD +213 -214
- ansible_test/_data/completion/docker.txt +7 -7
- ansible_test/_data/completion/network.txt +0 -1
- ansible_test/_data/completion/remote.txt +4 -4
- ansible_test/_data/requirements/ansible-test.txt +1 -1
- ansible_test/_data/requirements/ansible.txt +1 -1
- ansible_test/_data/requirements/sanity.ansible-doc.txt +2 -2
- ansible_test/_data/requirements/sanity.changelog.txt +2 -2
- ansible_test/_data/requirements/sanity.import.plugin.txt +2 -2
- ansible_test/_data/requirements/sanity.import.txt +1 -1
- ansible_test/_data/requirements/sanity.integration-aliases.txt +1 -1
- ansible_test/_data/requirements/sanity.pep8.txt +1 -1
- ansible_test/_data/requirements/sanity.pylint.txt +6 -6
- ansible_test/_data/requirements/sanity.runtime-metadata.txt +1 -1
- ansible_test/_data/requirements/sanity.validate-modules.txt +2 -2
- ansible_test/_data/requirements/sanity.yamllint.txt +1 -1
- ansible_test/_internal/cache.py +2 -5
- ansible_test/_internal/cli/compat.py +1 -1
- ansible_test/_internal/commands/coverage/combine.py +1 -3
- ansible_test/_internal/commands/integration/__init__.py +3 -7
- ansible_test/_internal/commands/integration/cloud/httptester.py +1 -1
- ansible_test/_internal/commands/integration/coverage.py +1 -3
- ansible_test/_internal/commands/integration/filters.py +5 -10
- ansible_test/_internal/commands/sanity/pylint.py +11 -0
- ansible_test/_internal/commands/sanity/validate_modules.py +1 -5
- ansible_test/_internal/commands/units/__init__.py +1 -13
- ansible_test/_internal/compat/packaging.py +2 -2
- ansible_test/_internal/compat/yaml.py +2 -2
- ansible_test/_internal/completion.py +2 -5
- ansible_test/_internal/config.py +2 -7
- ansible_test/_internal/coverage_util.py +1 -1
- ansible_test/_internal/delegation.py +2 -0
- ansible_test/_internal/docker_util.py +1 -1
- ansible_test/_internal/host_profiles.py +6 -11
- ansible_test/_internal/provider/__init__.py +2 -5
- ansible_test/_internal/provisioning.py +2 -5
- ansible_test/_internal/pypi_proxy.py +1 -1
- ansible_test/_internal/python_requirements.py +1 -1
- ansible_test/_internal/target.py +2 -6
- ansible_test/_internal/thread.py +1 -4
- ansible_test/_internal/util.py +9 -14
- ansible_test/_util/controller/sanity/code-smell/runtime-metadata.py +14 -19
- ansible_test/_util/controller/sanity/pylint/plugins/deprecated_calls.py +48 -45
- ansible_test/_util/controller/sanity/pylint/plugins/string_format.py +9 -7
- ansible_test/_util/controller/sanity/pylint/plugins/unwanted.py +51 -37
- ansible_test/_util/controller/sanity/validate-modules/validate_modules/main.py +31 -18
- ansible_test/_util/controller/sanity/validate-modules/validate_modules/module_args.py +1 -2
- ansible_test/_util/controller/sanity/validate-modules/validate_modules/schema.py +59 -71
- ansible_test/_util/controller/sanity/validate-modules/validate_modules/utils.py +1 -2
- ansible_test/_util/target/cli/ansible_test_cli_stub.py +4 -2
- ansible_test/_util/target/common/constants.py +2 -2
- ansible_test/_util/target/setup/bootstrap.sh +0 -6
- ansible/utils/py3compat.py +0 -27
- ansible_test/_data/pytest/config/legacy.ini +0 -4
- {ansible_core-2.19.4rc1.dist-info → ansible_core-2.20.0.dist-info}/WHEEL +0 -0
- {ansible_core-2.19.4rc1.dist-info → ansible_core-2.20.0.dist-info}/entry_points.txt +0 -0
- {ansible_core-2.19.4rc1.dist-info → ansible_core-2.20.0.dist-info}/licenses/COPYING +0 -0
- {ansible_core-2.19.4rc1.dist-info → ansible_core-2.20.0.dist-info}/licenses/licenses/Apache-License.txt +0 -0
- {ansible_core-2.19.4rc1.dist-info → ansible_core-2.20.0.dist-info}/licenses/licenses/BSD-3-Clause.txt +0 -0
- {ansible_core-2.19.4rc1.dist-info → ansible_core-2.20.0.dist-info}/licenses/licenses/MIT-license.txt +0 -0
- {ansible_core-2.19.4rc1.dist-info → ansible_core-2.20.0.dist-info}/licenses/licenses/PSF-license.txt +0 -0
- {ansible_core-2.19.4rc1.dist-info → ansible_core-2.20.0.dist-info}/licenses/licenses/simplified_bsd.txt +0 -0
- {ansible_core-2.19.4rc1.dist-info → ansible_core-2.20.0.dist-info}/top_level.txt +0 -0
ansible/modules/lineinfile.py
CHANGED
|
@@ -123,6 +123,13 @@ options:
|
|
|
123
123
|
type: bool
|
|
124
124
|
default: no
|
|
125
125
|
version_added: "2.5"
|
|
126
|
+
encoding:
|
|
127
|
+
description:
|
|
128
|
+
- The character set in which the target file is encoded.
|
|
129
|
+
- For a list of available built-in encodings, see U(https://docs.python.org/3/library/codecs.html#standard-encodings)
|
|
130
|
+
type: str
|
|
131
|
+
default: utf-8
|
|
132
|
+
version_added: "2.20"
|
|
126
133
|
extends_documentation_fragment:
|
|
127
134
|
- action_common_attributes
|
|
128
135
|
- action_common_attributes.files
|
|
@@ -250,11 +257,11 @@ from ansible.module_utils.basic import AnsibleModule
|
|
|
250
257
|
from ansible.module_utils.common.text.converters import to_bytes, to_native, to_text
|
|
251
258
|
|
|
252
259
|
|
|
253
|
-
def write_changes(module,
|
|
260
|
+
def write_changes(module, lines, dest, encoding=None):
|
|
254
261
|
|
|
255
262
|
tmpfd, tmpfile = tempfile.mkstemp(dir=module.tmpdir)
|
|
256
|
-
with os.fdopen(tmpfd, '
|
|
257
|
-
f.writelines(
|
|
263
|
+
with os.fdopen(tmpfd, 'w', encoding=encoding) as f:
|
|
264
|
+
f.writelines(lines)
|
|
258
265
|
|
|
259
266
|
validate = module.params.get('validate', None)
|
|
260
267
|
valid = not validate
|
|
@@ -293,6 +300,7 @@ def present(module, dest, regexp, search_string, line, insertafter, insertbefore
|
|
|
293
300
|
'before_header': '%s (content)' % dest,
|
|
294
301
|
'after_header': '%s (content)' % dest}
|
|
295
302
|
|
|
303
|
+
encoding = module.params.get('encoding', None)
|
|
296
304
|
b_dest = to_bytes(dest, errors='surrogate_or_strict')
|
|
297
305
|
if not os.path.exists(b_dest):
|
|
298
306
|
if not create:
|
|
@@ -304,30 +312,29 @@ def present(module, dest, regexp, search_string, line, insertafter, insertbefore
|
|
|
304
312
|
except Exception as e:
|
|
305
313
|
module.fail_json(msg='Error creating %s (%s)' % (to_text(b_destpath), to_text(e)))
|
|
306
314
|
|
|
307
|
-
|
|
315
|
+
lines = []
|
|
308
316
|
else:
|
|
309
|
-
with open(b_dest, '
|
|
310
|
-
|
|
317
|
+
with open(b_dest, 'r', encoding=encoding) as f:
|
|
318
|
+
lines = f.readlines()
|
|
311
319
|
|
|
312
320
|
if module._diff:
|
|
313
|
-
diff['before'] =
|
|
321
|
+
diff['before'] = ''.join(lines)
|
|
314
322
|
|
|
315
323
|
if regexp is not None:
|
|
316
|
-
|
|
324
|
+
re_m = re.compile(regexp)
|
|
317
325
|
|
|
318
326
|
if insertafter not in (None, 'BOF', 'EOF'):
|
|
319
|
-
|
|
327
|
+
re_ins = re.compile(insertafter)
|
|
320
328
|
elif insertbefore not in (None, 'BOF'):
|
|
321
|
-
|
|
329
|
+
re_ins = re.compile(insertbefore)
|
|
322
330
|
else:
|
|
323
|
-
|
|
331
|
+
re_ins = None
|
|
324
332
|
|
|
325
333
|
# index[0] is the line num where regexp has been found
|
|
326
334
|
# index[1] is the line num where insertafter/insertbefore has been found
|
|
327
335
|
index = [-1, -1]
|
|
328
336
|
match = None
|
|
329
337
|
exact_line_match = False
|
|
330
|
-
b_line = to_bytes(line, errors='surrogate_or_strict')
|
|
331
338
|
|
|
332
339
|
# The module's doc says
|
|
333
340
|
# "If regular expressions are passed to both regexp and
|
|
@@ -339,8 +346,8 @@ def present(module, dest, regexp, search_string, line, insertafter, insertbefore
|
|
|
339
346
|
# Given the above:
|
|
340
347
|
# 1. First check that there is no match for regexp:
|
|
341
348
|
if regexp is not None:
|
|
342
|
-
for lineno,
|
|
343
|
-
match_found =
|
|
349
|
+
for lineno, cur_line in enumerate(lines):
|
|
350
|
+
match_found = re_m.search(cur_line)
|
|
344
351
|
if match_found:
|
|
345
352
|
index[0] = lineno
|
|
346
353
|
match = match_found
|
|
@@ -349,8 +356,8 @@ def present(module, dest, regexp, search_string, line, insertafter, insertbefore
|
|
|
349
356
|
|
|
350
357
|
# 2. Second check that there is no match for search_string:
|
|
351
358
|
if search_string is not None:
|
|
352
|
-
for lineno,
|
|
353
|
-
match_found =
|
|
359
|
+
for lineno, cur_line in enumerate(lines):
|
|
360
|
+
match_found = search_string in cur_line
|
|
354
361
|
if match_found:
|
|
355
362
|
index[0] = lineno
|
|
356
363
|
match = match_found
|
|
@@ -360,12 +367,12 @@ def present(module, dest, regexp, search_string, line, insertafter, insertbefore
|
|
|
360
367
|
# 3. When no match found on the previous step,
|
|
361
368
|
# parse for searching insertafter/insertbefore:
|
|
362
369
|
if not match:
|
|
363
|
-
for lineno,
|
|
364
|
-
if
|
|
370
|
+
for lineno, cur_line in enumerate(lines):
|
|
371
|
+
if line == cur_line.rstrip('\r\n'):
|
|
365
372
|
index[0] = lineno
|
|
366
373
|
exact_line_match = True
|
|
367
374
|
|
|
368
|
-
elif
|
|
375
|
+
elif re_ins is not None and re_ins.search(cur_line):
|
|
369
376
|
if insertafter:
|
|
370
377
|
# + 1 for the next line
|
|
371
378
|
index[1] = lineno + 1
|
|
@@ -380,17 +387,17 @@ def present(module, dest, regexp, search_string, line, insertafter, insertbefore
|
|
|
380
387
|
|
|
381
388
|
msg = ''
|
|
382
389
|
changed = False
|
|
383
|
-
|
|
390
|
+
linesep = os.linesep
|
|
384
391
|
# Exact line or Regexp matched a line in the file
|
|
385
392
|
if index[0] != -1:
|
|
386
393
|
if backrefs and match:
|
|
387
|
-
|
|
394
|
+
new_line = match.expand(line)
|
|
388
395
|
else:
|
|
389
396
|
# Don't do backref expansion if not asked.
|
|
390
|
-
|
|
397
|
+
new_line = line
|
|
391
398
|
|
|
392
|
-
if not
|
|
393
|
-
|
|
399
|
+
if not new_line.endswith(linesep):
|
|
400
|
+
new_line += linesep
|
|
394
401
|
|
|
395
402
|
# If no regexp or search_string was given and no line match is found anywhere in the file,
|
|
396
403
|
# insert the line appropriately if using insertbefore or insertafter
|
|
@@ -400,18 +407,18 @@ def present(module, dest, regexp, search_string, line, insertafter, insertbefore
|
|
|
400
407
|
if insertafter and insertafter != 'EOF':
|
|
401
408
|
# Ensure there is a line separator after the found string
|
|
402
409
|
# at the end of the file.
|
|
403
|
-
if
|
|
404
|
-
|
|
410
|
+
if lines and not lines[-1][-1:] in ('\n', '\r'):
|
|
411
|
+
lines[-1] = lines[-1] + linesep
|
|
405
412
|
|
|
406
413
|
# If the line to insert after is at the end of the file
|
|
407
414
|
# use the appropriate index value.
|
|
408
|
-
if len(
|
|
409
|
-
if
|
|
410
|
-
|
|
415
|
+
if len(lines) == index[1]:
|
|
416
|
+
if lines[index[1] - 1].rstrip('\r\n') != line:
|
|
417
|
+
lines.append(line + linesep)
|
|
411
418
|
msg = 'line added'
|
|
412
419
|
changed = True
|
|
413
|
-
elif
|
|
414
|
-
|
|
420
|
+
elif lines[index[1]].rstrip('\r\n') != line:
|
|
421
|
+
lines.insert(index[1], line + linesep)
|
|
415
422
|
msg = 'line added'
|
|
416
423
|
changed = True
|
|
417
424
|
|
|
@@ -419,18 +426,18 @@ def present(module, dest, regexp, search_string, line, insertafter, insertbefore
|
|
|
419
426
|
# If the line to insert before is at the beginning of the file
|
|
420
427
|
# use the appropriate index value.
|
|
421
428
|
if index[1] <= 0:
|
|
422
|
-
if
|
|
423
|
-
|
|
429
|
+
if lines[index[1]].rstrip('\r\n') != line:
|
|
430
|
+
lines.insert(index[1], line + linesep)
|
|
424
431
|
msg = 'line added'
|
|
425
432
|
changed = True
|
|
426
433
|
|
|
427
|
-
elif
|
|
428
|
-
|
|
434
|
+
elif lines[index[1] - 1].rstrip('\r\n') != line:
|
|
435
|
+
lines.insert(index[1], line + linesep)
|
|
429
436
|
msg = 'line added'
|
|
430
437
|
changed = True
|
|
431
438
|
|
|
432
|
-
elif
|
|
433
|
-
|
|
439
|
+
elif lines[index[0]] != new_line:
|
|
440
|
+
lines[index[0]] = new_line
|
|
434
441
|
msg = 'line replaced'
|
|
435
442
|
changed = True
|
|
436
443
|
|
|
@@ -440,7 +447,7 @@ def present(module, dest, regexp, search_string, line, insertafter, insertbefore
|
|
|
440
447
|
pass
|
|
441
448
|
# Add it to the beginning of the file
|
|
442
449
|
elif insertbefore == 'BOF' or insertafter == 'BOF':
|
|
443
|
-
|
|
450
|
+
lines.insert(0, line + linesep)
|
|
444
451
|
msg = 'line added'
|
|
445
452
|
changed = True
|
|
446
453
|
# Add it to the end of the file if requested or
|
|
@@ -449,10 +456,10 @@ def present(module, dest, regexp, search_string, line, insertafter, insertbefore
|
|
|
449
456
|
elif insertafter == 'EOF' or index[1] == -1:
|
|
450
457
|
|
|
451
458
|
# If the file is not empty then ensure there's a newline before the added line
|
|
452
|
-
if
|
|
453
|
-
|
|
459
|
+
if lines and not lines[-1][-1:] in ('\n', '\r'):
|
|
460
|
+
lines.append(linesep)
|
|
454
461
|
|
|
455
|
-
|
|
462
|
+
lines.append(line + linesep)
|
|
456
463
|
msg = 'line added'
|
|
457
464
|
changed = True
|
|
458
465
|
|
|
@@ -460,30 +467,30 @@ def present(module, dest, regexp, search_string, line, insertafter, insertbefore
|
|
|
460
467
|
|
|
461
468
|
# Don't insert the line if it already matches at the index.
|
|
462
469
|
# If the line to insert after is at the end of the file use the appropriate index value.
|
|
463
|
-
if len(
|
|
464
|
-
if
|
|
465
|
-
|
|
470
|
+
if len(lines) == index[1]:
|
|
471
|
+
if lines[index[1] - 1].rstrip('\r\n') != line:
|
|
472
|
+
lines.append(line + linesep)
|
|
466
473
|
msg = 'line added'
|
|
467
474
|
changed = True
|
|
468
|
-
elif
|
|
469
|
-
|
|
475
|
+
elif line != lines[index[1]].rstrip('\n\r'):
|
|
476
|
+
lines.insert(index[1], line + linesep)
|
|
470
477
|
msg = 'line added'
|
|
471
478
|
changed = True
|
|
472
479
|
|
|
473
480
|
# insert matched, but not the regexp or search_string
|
|
474
481
|
else:
|
|
475
|
-
|
|
482
|
+
lines.insert(index[1], line + linesep)
|
|
476
483
|
msg = 'line added'
|
|
477
484
|
changed = True
|
|
478
485
|
|
|
479
486
|
if module._diff:
|
|
480
|
-
diff['after'] =
|
|
487
|
+
diff['after'] = ''.join(lines)
|
|
481
488
|
|
|
482
489
|
backupdest = ""
|
|
483
490
|
if changed and not module.check_mode:
|
|
484
491
|
if backup and os.path.exists(b_dest):
|
|
485
492
|
backupdest = module.backup_local(dest)
|
|
486
|
-
write_changes(module,
|
|
493
|
+
write_changes(module, lines, dest, encoding)
|
|
487
494
|
|
|
488
495
|
if module.check_mode and not os.path.exists(b_dest):
|
|
489
496
|
module.exit_json(changed=changed, msg=msg, backup=backupdest, diff=diff)
|
|
@@ -510,40 +517,40 @@ def absent(module, dest, regexp, search_string, line, backup):
|
|
|
510
517
|
'before_header': '%s (content)' % dest,
|
|
511
518
|
'after_header': '%s (content)' % dest}
|
|
512
519
|
|
|
513
|
-
|
|
514
|
-
|
|
520
|
+
encoding = module.params['encoding']
|
|
521
|
+
|
|
522
|
+
with open(b_dest, 'r', encoding=encoding) as f:
|
|
523
|
+
lines = f.readlines()
|
|
515
524
|
|
|
516
525
|
if module._diff:
|
|
517
|
-
diff['before'] =
|
|
526
|
+
diff['before'] = ''.join(lines)
|
|
518
527
|
|
|
519
528
|
if regexp is not None:
|
|
520
|
-
|
|
529
|
+
re_c = re.compile(regexp)
|
|
521
530
|
found = []
|
|
522
531
|
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
def matcher(b_cur_line):
|
|
532
|
+
def matcher(cur_line):
|
|
526
533
|
if regexp is not None:
|
|
527
|
-
match_found =
|
|
534
|
+
match_found = re_c.search(cur_line)
|
|
528
535
|
elif search_string is not None:
|
|
529
|
-
match_found =
|
|
536
|
+
match_found = search_string in cur_line
|
|
530
537
|
else:
|
|
531
|
-
match_found =
|
|
538
|
+
match_found = line == cur_line.rstrip('\r\n')
|
|
532
539
|
if match_found:
|
|
533
|
-
found.append(
|
|
540
|
+
found.append(cur_line)
|
|
534
541
|
return not match_found
|
|
535
542
|
|
|
536
|
-
|
|
543
|
+
lines = [l for l in lines if matcher(l)]
|
|
537
544
|
changed = len(found) > 0
|
|
538
545
|
|
|
539
546
|
if module._diff:
|
|
540
|
-
diff['after'] =
|
|
547
|
+
diff['after'] = ''.join(lines)
|
|
541
548
|
|
|
542
549
|
backupdest = ""
|
|
543
550
|
if changed and not module.check_mode:
|
|
544
551
|
if backup:
|
|
545
552
|
backupdest = module.backup_local(dest)
|
|
546
|
-
write_changes(module,
|
|
553
|
+
write_changes(module, lines, dest, encoding)
|
|
547
554
|
|
|
548
555
|
if changed:
|
|
549
556
|
msg = "%s line(s) removed" % len(found)
|
|
@@ -567,6 +574,7 @@ def main():
|
|
|
567
574
|
regexp=dict(type='str', aliases=['regex']),
|
|
568
575
|
search_string=dict(type='str'),
|
|
569
576
|
line=dict(type='str', aliases=['value']),
|
|
577
|
+
encoding=dict(type='str', default='utf-8'),
|
|
570
578
|
insertafter=dict(type='str'),
|
|
571
579
|
insertbefore=dict(type='str'),
|
|
572
580
|
backrefs=dict(type='bool', default=False),
|
ansible/modules/package_facts.py
CHANGED
|
@@ -18,7 +18,7 @@ options:
|
|
|
18
18
|
This is a list and can support multiple package managers per system, since version 2.8.
|
|
19
19
|
- The V(portage) and V(pkg) options were added in version 2.8.
|
|
20
20
|
- The V(apk) option was added in version 2.11.
|
|
21
|
-
- The V(pkg_info)
|
|
21
|
+
- The V(pkg_info) option was added in version 2.13.
|
|
22
22
|
- Aliases were added in 2.18, to support using C(manager={{ansible_facts['pkg_mgr']}})
|
|
23
23
|
default: ['auto']
|
|
24
24
|
choices:
|
ansible/modules/pip.py
CHANGED
|
@@ -608,7 +608,7 @@ def setup_virtualenv(module, env, chdir, out, err):
|
|
|
608
608
|
err += err_venv
|
|
609
609
|
if rc != 0:
|
|
610
610
|
_fail(module, cmd, out, err)
|
|
611
|
-
return out, err
|
|
611
|
+
return out, err, cmd
|
|
612
612
|
|
|
613
613
|
|
|
614
614
|
class Package:
|
|
@@ -741,11 +741,12 @@ def main():
|
|
|
741
741
|
|
|
742
742
|
err = ''
|
|
743
743
|
out = ''
|
|
744
|
+
venv_cmd = ''
|
|
744
745
|
|
|
745
746
|
if env:
|
|
746
747
|
if not os.path.exists(os.path.join(env, 'bin', 'activate')):
|
|
747
748
|
venv_created = True
|
|
748
|
-
out, err = setup_virtualenv(module, env, chdir, out, err)
|
|
749
|
+
out, err, venv_cmd = setup_virtualenv(module, env, chdir, out, err)
|
|
749
750
|
py_bin = os.path.join(env, 'bin', 'python')
|
|
750
751
|
else:
|
|
751
752
|
py_bin = module.params['executable'] or sys.executable
|
|
@@ -811,6 +812,11 @@ def main():
|
|
|
811
812
|
cmd.extend(to_native(p) for p in packages)
|
|
812
813
|
elif requirements:
|
|
813
814
|
cmd.extend(['-r', requirements])
|
|
815
|
+
elif venv_created and not name and not requirements:
|
|
816
|
+
# ONLY creating an empty venv
|
|
817
|
+
module.exit_json(changed=venv_created, cmd=venv_cmd, name=name, version=version,
|
|
818
|
+
state=state, requirements=requirements, virtualenv=env,
|
|
819
|
+
stdout=out, stderr=err)
|
|
814
820
|
else:
|
|
815
821
|
module.warn("No valid name or requirements file found.")
|
|
816
822
|
module.exit_json(changed=False)
|
ansible/modules/replace.py
CHANGED
|
@@ -183,14 +183,14 @@ import os
|
|
|
183
183
|
import re
|
|
184
184
|
import tempfile
|
|
185
185
|
|
|
186
|
-
from ansible.module_utils.common.text.converters import to_text
|
|
186
|
+
from ansible.module_utils.common.text.converters import to_text
|
|
187
187
|
from ansible.module_utils.basic import AnsibleModule
|
|
188
188
|
|
|
189
189
|
|
|
190
|
-
def write_changes(module, contents, path):
|
|
190
|
+
def write_changes(module, contents, path, encoding='utf-8'):
|
|
191
191
|
|
|
192
192
|
tmpfd, tmpfile = tempfile.mkstemp(dir=module.tmpdir)
|
|
193
|
-
with os.fdopen(tmpfd, '
|
|
193
|
+
with os.fdopen(tmpfd, 'w', encoding=encoding) as f:
|
|
194
194
|
f.write(contents)
|
|
195
195
|
|
|
196
196
|
validate = module.params.get('validate', None)
|
|
@@ -254,8 +254,8 @@ def main():
|
|
|
254
254
|
module.fail_json(rc=257, msg='Path %s does not exist !' % path)
|
|
255
255
|
else:
|
|
256
256
|
try:
|
|
257
|
-
with open(path, '
|
|
258
|
-
contents =
|
|
257
|
+
with open(path, 'r', encoding=encoding) as f:
|
|
258
|
+
contents = f.read()
|
|
259
259
|
except OSError as ex:
|
|
260
260
|
raise Exception(f"Unable to read the contents of {path!r}.") from ex
|
|
261
261
|
|
|
@@ -307,7 +307,7 @@ def main():
|
|
|
307
307
|
res_args['backup_file'] = module.backup_local(path)
|
|
308
308
|
# We should always follow symlinks so that we change the real file
|
|
309
309
|
path = os.path.realpath(path)
|
|
310
|
-
write_changes(module,
|
|
310
|
+
write_changes(module, result[0], path, encoding=encoding)
|
|
311
311
|
|
|
312
312
|
res_args['msg'], res_args['changed'] = check_file_attrs(module, changed, msg)
|
|
313
313
|
module.exit_json(**res_args)
|
ansible/modules/service.py
CHANGED
|
@@ -180,7 +180,6 @@ from ansible.module_utils.basic import AnsibleModule
|
|
|
180
180
|
from ansible.module_utils.common.locale import get_best_parsable_locale
|
|
181
181
|
from ansible.module_utils.common.sys_info import get_platform_subclass
|
|
182
182
|
from ansible.module_utils.service import fail_if_missing, is_systemd_managed
|
|
183
|
-
from ansible.module_utils.six import b
|
|
184
183
|
|
|
185
184
|
|
|
186
185
|
class Service(object):
|
|
@@ -292,8 +291,8 @@ class Service(object):
|
|
|
292
291
|
# chkconfig localizes messages and we're screen scraping so make
|
|
293
292
|
# sure we use the C locale
|
|
294
293
|
p = subprocess.Popen(cmd, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=lang_env, preexec_fn=lambda: os.close(pipe[1]))
|
|
295
|
-
stdout = b
|
|
296
|
-
stderr = b
|
|
294
|
+
stdout = b""
|
|
295
|
+
stderr = b""
|
|
297
296
|
fds = [p.stdout, p.stderr]
|
|
298
297
|
# Wait for all output, or until the main process is dead and its output is done.
|
|
299
298
|
while fds:
|
|
@@ -322,7 +321,7 @@ class Service(object):
|
|
|
322
321
|
os.close(pipe[1])
|
|
323
322
|
os.waitpid(pid, 0)
|
|
324
323
|
# Wait for data from daemon process and process it.
|
|
325
|
-
data = b
|
|
324
|
+
data = b""
|
|
326
325
|
while True:
|
|
327
326
|
rfd, wfd, efd = select.select([pipe[0]], [], [pipe[0]])
|
|
328
327
|
if pipe[0] in rfd:
|
ansible/modules/stat.py
CHANGED
|
@@ -44,6 +44,14 @@ options:
|
|
|
44
44
|
version_added: "2.3"
|
|
45
45
|
get_checksum:
|
|
46
46
|
version_added: "1.8"
|
|
47
|
+
get_selinux_context:
|
|
48
|
+
description:
|
|
49
|
+
- Get file SELinux context in a list V([user, role, type, range]),
|
|
50
|
+
and will get V([None, None, None, None]) if it is not possible to retrieve the context,
|
|
51
|
+
either because it does not exist or some other issue.
|
|
52
|
+
type: bool
|
|
53
|
+
default: no
|
|
54
|
+
version_added: '2.20'
|
|
47
55
|
extends_documentation_fragment:
|
|
48
56
|
- action_common_attributes
|
|
49
57
|
- checksum_common
|
|
@@ -346,6 +354,12 @@ stat:
|
|
|
346
354
|
type: list
|
|
347
355
|
sample: [ immutable, extent ]
|
|
348
356
|
version_added: 2.3
|
|
357
|
+
selinux_context:
|
|
358
|
+
description: The SELinux context of a path
|
|
359
|
+
returned: success, path exists and user can execute the path
|
|
360
|
+
type: list
|
|
361
|
+
sample: [ user, role, type, range ]
|
|
362
|
+
version_added: '2.20'
|
|
349
363
|
version:
|
|
350
364
|
description: The version/generation attribute of a file according to the filesystem
|
|
351
365
|
returned: success, path exists, user can execute the path, lsattr is available and filesystem supports
|
|
@@ -434,6 +448,7 @@ def main():
|
|
|
434
448
|
get_checksum=dict(type='bool', default=True),
|
|
435
449
|
get_mime=dict(type='bool', default=True, aliases=['mime', 'mime_type', 'mime-type']),
|
|
436
450
|
get_attributes=dict(type='bool', default=True, aliases=['attr', 'attributes']),
|
|
451
|
+
get_selinux_context=dict(type='bool', default=False),
|
|
437
452
|
checksum_algorithm=dict(type='str', default='sha1',
|
|
438
453
|
choices=['md5', 'sha1', 'sha224', 'sha256', 'sha384', 'sha512'],
|
|
439
454
|
aliases=['checksum', 'checksum_algo']),
|
|
@@ -448,6 +463,7 @@ def main():
|
|
|
448
463
|
get_attr = module.params.get('get_attributes')
|
|
449
464
|
get_checksum = module.params.get('get_checksum')
|
|
450
465
|
checksum_algorithm = module.params.get('checksum_algorithm')
|
|
466
|
+
get_selinux_context = module.params.get('get_selinux_context')
|
|
451
467
|
|
|
452
468
|
# main stat data
|
|
453
469
|
try:
|
|
@@ -515,6 +531,10 @@ def main():
|
|
|
515
531
|
if x in out:
|
|
516
532
|
output[x] = out[x]
|
|
517
533
|
|
|
534
|
+
# try to get SELinux context
|
|
535
|
+
if get_selinux_context:
|
|
536
|
+
output['selinux_context'] = module.selinux_context(b_path)
|
|
537
|
+
|
|
518
538
|
module.exit_json(changed=False, stat=output)
|
|
519
539
|
|
|
520
540
|
|
ansible/modules/uri.py
CHANGED
|
@@ -438,13 +438,12 @@ import os
|
|
|
438
438
|
import re
|
|
439
439
|
import shutil
|
|
440
440
|
import tempfile
|
|
441
|
+
from collections.abc import Mapping, Sequence
|
|
441
442
|
from datetime import datetime, timezone
|
|
443
|
+
from urllib.parse import urlencode, urljoin
|
|
442
444
|
|
|
443
445
|
from ansible.module_utils.basic import AnsibleModule, sanitize_keys
|
|
444
|
-
from ansible.module_utils.six import binary_type, iteritems, string_types
|
|
445
|
-
from ansible.module_utils.six.moves.urllib.parse import urlencode, urljoin
|
|
446
446
|
from ansible.module_utils.common.text.converters import to_native, to_text
|
|
447
|
-
from ansible.module_utils.six.moves.collections_abc import Mapping, Sequence
|
|
448
447
|
from ansible.module_utils.urls import (
|
|
449
448
|
fetch_url,
|
|
450
449
|
get_response_filename,
|
|
@@ -479,7 +478,7 @@ def write_file(module, dest, content, resp):
|
|
|
479
478
|
try:
|
|
480
479
|
fd, tmpsrc = tempfile.mkstemp(dir=module.tmpdir)
|
|
481
480
|
with os.fdopen(fd, 'wb') as f:
|
|
482
|
-
if isinstance(content,
|
|
481
|
+
if isinstance(content, bytes):
|
|
483
482
|
f.write(content)
|
|
484
483
|
else:
|
|
485
484
|
shutil.copyfileobj(content, f)
|
|
@@ -521,14 +520,14 @@ def kv_list(data):
|
|
|
521
520
|
|
|
522
521
|
def form_urlencoded(body):
|
|
523
522
|
""" Convert data into a form-urlencoded string """
|
|
524
|
-
if isinstance(body,
|
|
523
|
+
if isinstance(body, str):
|
|
525
524
|
return body
|
|
526
525
|
|
|
527
526
|
if isinstance(body, (Mapping, Sequence)):
|
|
528
527
|
result = []
|
|
529
528
|
# Turn a list of lists into a list of tuples that urlencode accepts
|
|
530
529
|
for key, values in kv_list(body):
|
|
531
|
-
if isinstance(values,
|
|
530
|
+
if isinstance(values, str) or not isinstance(values, (Mapping, Sequence)):
|
|
532
531
|
values = [values]
|
|
533
532
|
for value in values:
|
|
534
533
|
if value is not None:
|
|
@@ -641,12 +640,12 @@ def main():
|
|
|
641
640
|
|
|
642
641
|
if body_format == 'json':
|
|
643
642
|
# Encode the body unless its a string, then assume it is pre-formatted JSON
|
|
644
|
-
if not isinstance(body,
|
|
643
|
+
if not isinstance(body, str):
|
|
645
644
|
body = json.dumps(body)
|
|
646
645
|
if 'content-type' not in [header.lower() for header in dict_headers]:
|
|
647
646
|
dict_headers['Content-Type'] = 'application/json'
|
|
648
647
|
elif body_format == 'form-urlencoded':
|
|
649
|
-
if not isinstance(body,
|
|
648
|
+
if not isinstance(body, str):
|
|
650
649
|
try:
|
|
651
650
|
body = form_urlencoded(body)
|
|
652
651
|
except ValueError as e:
|
|
@@ -747,7 +746,7 @@ def main():
|
|
|
747
746
|
# In python3, the headers are title cased. Lowercase them to be
|
|
748
747
|
# compatible with the python2 behaviour.
|
|
749
748
|
uresp = {}
|
|
750
|
-
for key, value in
|
|
749
|
+
for key, value in resp.items():
|
|
751
750
|
ukey = key.replace("-", "_").lower()
|
|
752
751
|
uresp[ukey] = value
|
|
753
752
|
|
|
@@ -755,7 +754,7 @@ def main():
|
|
|
755
754
|
uresp['location'] = urljoin(url, uresp['location'])
|
|
756
755
|
|
|
757
756
|
# Default content_encoding to try
|
|
758
|
-
if isinstance(content,
|
|
757
|
+
if isinstance(content, bytes):
|
|
759
758
|
u_content = to_text(content, encoding=content_encoding)
|
|
760
759
|
if maybe_json:
|
|
761
760
|
try:
|
ansible/modules/user.py
CHANGED
|
@@ -2504,11 +2504,10 @@ class DarwinUser(User):
|
|
|
2504
2504
|
Please note that password must be cleartext.
|
|
2505
2505
|
"""
|
|
2506
2506
|
# some documentation on how is stored passwords on OSX:
|
|
2507
|
-
# http://blog.lostpassword.com/2012/07/cracking-mac-os-x-lion-accounts-passwords/
|
|
2508
2507
|
# http://null-byte.wonderhowto.com/how-to/hack-mac-os-x-lion-passwords-0130036/
|
|
2509
2508
|
# http://pastebin.com/RYqxi7Ca
|
|
2510
2509
|
# on OSX 10.8+ hash is SALTED-SHA512-PBKDF2
|
|
2511
|
-
# https://
|
|
2510
|
+
# https://passlib.readthedocs.io/en/stable/lib/passlib.hash.pbkdf2_digest.html
|
|
2512
2511
|
# https://gist.github.com/nueh/8252572
|
|
2513
2512
|
cmd = self._get_dscl()
|
|
2514
2513
|
if self.password:
|
ansible/modules/wait_for.py
CHANGED
|
@@ -216,13 +216,13 @@ elapsed:
|
|
|
216
216
|
type: int
|
|
217
217
|
sample: 23
|
|
218
218
|
match_groups:
|
|
219
|
-
description: Tuple containing all the subgroups of the match as returned by U(https://docs.python.org/3/library/re.html#re.
|
|
219
|
+
description: Tuple containing all the subgroups of the match as returned by U(https://docs.python.org/3/library/re.html#re.Match.groups)
|
|
220
220
|
returned: always
|
|
221
221
|
type: list
|
|
222
222
|
sample: ['match 1', 'match 2']
|
|
223
223
|
match_groupdict:
|
|
224
224
|
description: Dictionary containing all the named subgroups of the match, keyed by the subgroup name,
|
|
225
|
-
as returned by U(https://docs.python.org/3/library/re.html#re.
|
|
225
|
+
as returned by U(https://docs.python.org/3/library/re.html#re.Match.groupdict)
|
|
226
226
|
returned: always
|
|
227
227
|
type: dict
|
|
228
228
|
sample:
|
|
@@ -104,9 +104,10 @@ EXAMPLES = r"""
|
|
|
104
104
|
- cmd.exe /c winrm.cmd quickconfig -quiet -force
|
|
105
105
|
delegate_to: localhost
|
|
106
106
|
|
|
107
|
-
- name: Wait for system to become reachable over WinRM
|
|
107
|
+
- name: Wait for system to become reachable over WinRM, polling every 10 seconds
|
|
108
108
|
ansible.builtin.wait_for_connection:
|
|
109
109
|
timeout: 900
|
|
110
|
+
sleep: 10
|
|
110
111
|
|
|
111
112
|
- name: Gather facts for first time
|
|
112
113
|
ansible.builtin.setup:
|
|
@@ -183,14 +183,6 @@ options:
|
|
|
183
183
|
- This parameter is deprecated as it has no effect with dnf as an underlying package manager
|
|
184
184
|
and will be removed in ansible-core 2.22.
|
|
185
185
|
type: bool
|
|
186
|
-
keepcache:
|
|
187
|
-
description:
|
|
188
|
-
- Either V(1) or V(0). Determines whether or not yum keeps the cache of
|
|
189
|
-
headers and packages after successful installation.
|
|
190
|
-
- This parameter is deprecated as it is only valid in the main configuration
|
|
191
|
-
and will be removed in ansible-core 2.20.
|
|
192
|
-
choices: ['0', '1']
|
|
193
|
-
type: str
|
|
194
186
|
metadata_expire:
|
|
195
187
|
description:
|
|
196
188
|
- Time (in seconds) after which the metadata will expire.
|
|
@@ -466,13 +458,7 @@ class YumRepo:
|
|
|
466
458
|
for key, value in sorted(self.params.items()):
|
|
467
459
|
if value is None:
|
|
468
460
|
continue
|
|
469
|
-
if key == '
|
|
470
|
-
self.module.deprecate(
|
|
471
|
-
"'keepcache' parameter is deprecated as it is only valid in "
|
|
472
|
-
"the main configuration.",
|
|
473
|
-
version='2.20'
|
|
474
|
-
)
|
|
475
|
-
elif key == 'async':
|
|
461
|
+
if key == 'async':
|
|
476
462
|
self.module.deprecate(
|
|
477
463
|
"'async' parameter is deprecated as it has been removed on systems supported by ansible-core",
|
|
478
464
|
version='2.22',
|
|
@@ -557,7 +543,6 @@ def main():
|
|
|
557
543
|
includepkgs=dict(type='list', elements='str'),
|
|
558
544
|
ip_resolve=dict(choices=['4', '6', 'IPv4', 'IPv6', 'whatever']),
|
|
559
545
|
keepalive=dict(type='bool'),
|
|
560
|
-
keepcache=dict(choices=['0', '1']),
|
|
561
546
|
metadata_expire=dict(),
|
|
562
547
|
metadata_expire_filter=dict(
|
|
563
548
|
choices=[
|