ansible-core 2.18.0rc1__py3-none-any.whl → 2.18.1rc1__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.

Files changed (26) hide show
  1. ansible/executor/task_executor.py +3 -2
  2. ansible/module_utils/ansible_release.py +1 -1
  3. ansible/module_utils/csharp/Ansible.AccessToken.cs +33 -18
  4. ansible/module_utils/csharp/Ansible.Become.cs +87 -17
  5. ansible/modules/command.py +6 -7
  6. ansible/modules/dnf5.py +18 -0
  7. ansible/modules/user.py +15 -2
  8. ansible/plugins/action/include_vars.py +2 -1
  9. ansible/plugins/lookup/varnames.py +10 -4
  10. ansible/plugins/lookup/vars.py +10 -3
  11. ansible/release.py +1 -1
  12. ansible/template/__init__.py +1 -30
  13. ansible/template/native_helpers.py +118 -4
  14. ansible/vars/hostvars.py +4 -2
  15. {ansible_core-2.18.0rc1.dist-info → ansible_core-2.18.1rc1.dist-info}/METADATA +4 -4
  16. {ansible_core-2.18.0rc1.dist-info → ansible_core-2.18.1rc1.dist-info}/RECORD +26 -26
  17. {ansible_core-2.18.0rc1.dist-info → ansible_core-2.18.1rc1.dist-info}/WHEEL +1 -1
  18. ansible_test/_internal/containers.py +7 -4
  19. ansible_test/_internal/util.py +3 -3
  20. {ansible_core-2.18.0rc1.dist-info → ansible_core-2.18.1rc1.dist-info}/Apache-License.txt +0 -0
  21. {ansible_core-2.18.0rc1.dist-info → ansible_core-2.18.1rc1.dist-info}/COPYING +0 -0
  22. {ansible_core-2.18.0rc1.dist-info → ansible_core-2.18.1rc1.dist-info}/MIT-license.txt +0 -0
  23. {ansible_core-2.18.0rc1.dist-info → ansible_core-2.18.1rc1.dist-info}/PSF-license.txt +0 -0
  24. {ansible_core-2.18.0rc1.dist-info → ansible_core-2.18.1rc1.dist-info}/entry_points.txt +0 -0
  25. {ansible_core-2.18.0rc1.dist-info → ansible_core-2.18.1rc1.dist-info}/simplified_bsd.txt +0 -0
  26. {ansible_core-2.18.0rc1.dist-info → ansible_core-2.18.1rc1.dist-info}/top_level.txt +0 -0
@@ -150,6 +150,7 @@ class TaskExecutor:
150
150
  if 'unreachable' in item and item['unreachable']:
151
151
  item_ignore_unreachable = item.pop('_ansible_ignore_unreachable')
152
152
  if not res.get('unreachable'):
153
+ res['unreachable'] = True
153
154
  self._task.ignore_unreachable = item_ignore_unreachable
154
155
  elif self._task.ignore_unreachable and not item_ignore_unreachable:
155
156
  self._task.ignore_unreachable = item_ignore_unreachable
@@ -684,8 +685,8 @@ class TaskExecutor:
684
685
  self._handler.cleanup()
685
686
  display.debug("handler run complete")
686
687
 
687
- # preserve no log
688
- result["_ansible_no_log"] = no_log
688
+ # propagate no log to result- the action can set this, so only overwrite it with the task's value if missing or falsey
689
+ result["_ansible_no_log"] = bool(no_log or result.get('_ansible_no_log', False))
689
690
 
690
691
  if self._task.action not in C._ACTION_WITH_CLEAN_FACTS:
691
692
  result = wrap_var(result)
@@ -17,6 +17,6 @@
17
17
 
18
18
  from __future__ import annotations
19
19
 
20
- __version__ = '2.18.0rc1'
20
+ __version__ = '2.18.1rc1'
21
21
  __author__ = 'Ansible, Inc.'
22
22
  __codename__ = "Fool in the Rain"
@@ -339,19 +339,47 @@ namespace Ansible.AccessToken
339
339
  public static IEnumerable<SafeNativeHandle> EnumerateUserTokens(SecurityIdentifier sid,
340
340
  TokenAccessLevels access = TokenAccessLevels.Query)
341
341
  {
342
+ return EnumerateUserTokens(sid, access, (p, h) => true);
343
+ }
344
+
345
+ public static IEnumerable<SafeNativeHandle> EnumerateUserTokens(
346
+ SecurityIdentifier sid,
347
+ TokenAccessLevels access,
348
+ Func<System.Diagnostics.Process, SafeNativeHandle, bool> processFilter)
349
+ {
350
+ // We always need the Query access level so we can query the TokenUser
351
+ access |= TokenAccessLevels.Query;
352
+
342
353
  foreach (System.Diagnostics.Process process in System.Diagnostics.Process.GetProcesses())
343
354
  {
344
- // We always need the Query access level so we can query the TokenUser
345
355
  using (process)
346
- using (SafeNativeHandle hToken = TryOpenAccessToken(process, access | TokenAccessLevels.Query))
356
+ using (SafeNativeHandle processHandle = NativeMethods.OpenProcess(ProcessAccessFlags.QueryInformation, false, (UInt32)process.Id))
347
357
  {
348
- if (hToken == null)
358
+ if (processHandle.IsInvalid)
359
+ {
349
360
  continue;
361
+ }
350
362
 
351
- if (!sid.Equals(GetTokenUser(hToken)))
363
+ if (!processFilter(process, processHandle))
364
+ {
352
365
  continue;
366
+ }
367
+
368
+ SafeNativeHandle accessToken;
369
+ if (!NativeMethods.OpenProcessToken(processHandle, access, out accessToken))
370
+ {
371
+ continue;
372
+ }
373
+
374
+ using (accessToken)
375
+ {
376
+ if (!sid.Equals(GetTokenUser(accessToken)))
377
+ {
378
+ continue;
379
+ }
353
380
 
354
- yield return hToken;
381
+ yield return accessToken;
382
+ }
355
383
  }
356
384
  }
357
385
  }
@@ -440,18 +468,5 @@ namespace Ansible.AccessToken
440
468
  for (int i = 0; i < array.Length; i++, ptrOffset = IntPtr.Add(ptrOffset, Marshal.SizeOf(typeof(T))))
441
469
  array[i] = (T)Marshal.PtrToStructure(ptrOffset, typeof(T));
442
470
  }
443
-
444
- private static SafeNativeHandle TryOpenAccessToken(System.Diagnostics.Process process, TokenAccessLevels access)
445
- {
446
- try
447
- {
448
- using (SafeNativeHandle hProcess = OpenProcess(process.Id, ProcessAccessFlags.QueryInformation, false))
449
- return OpenProcessToken(hProcess, access);
450
- }
451
- catch (Win32Exception)
452
- {
453
- return null;
454
- }
455
- }
456
471
  }
457
472
  }
@@ -93,10 +93,21 @@ namespace Ansible.Become
93
93
  CachedRemoteInteractive,
94
94
  CachedUnlock
95
95
  }
96
+
97
+ [Flags]
98
+ public enum ProcessChildProcessPolicyFlags
99
+ {
100
+ None = 0x0,
101
+ NoChildProcessCreation = 0x1,
102
+ AuditNoChildProcessCreation = 0x2,
103
+ AllowSecureProcessCreation = 0x4,
104
+ }
96
105
  }
97
106
 
98
107
  internal class NativeMethods
99
108
  {
109
+ public const int ProcessChildProcessPolicy = 13;
110
+
100
111
  [DllImport("advapi32.dll", SetLastError = true)]
101
112
  public static extern bool AllocateLocallyUniqueId(
102
113
  out Luid Luid);
@@ -116,6 +127,13 @@ namespace Ansible.Become
116
127
  [DllImport("kernel32.dll")]
117
128
  public static extern UInt32 GetCurrentThreadId();
118
129
 
130
+ [DllImport("kernel32.dll", SetLastError = true)]
131
+ public static extern bool GetProcessMitigationPolicy(
132
+ SafeNativeHandle hProcess,
133
+ int MitigationPolicy,
134
+ ref NativeHelpers.ProcessChildProcessPolicyFlags lpBuffer,
135
+ IntPtr dwLength);
136
+
119
137
  [DllImport("user32.dll", SetLastError = true)]
120
138
  public static extern NoopSafeHandle GetProcessWindowStation();
121
139
 
@@ -217,6 +235,7 @@ namespace Ansible.Become
217
235
  };
218
236
  private static int WINDOWS_STATION_ALL_ACCESS = 0x000F037F;
219
237
  private static int DESKTOP_RIGHTS_ALL_ACCESS = 0x000F01FF;
238
+ private static bool _getProcessMitigationPolicySupported = true;
220
239
 
221
240
  public static Result CreateProcessAsUser(string username, string password, string command)
222
241
  {
@@ -333,12 +352,13 @@ namespace Ansible.Become
333
352
  // Grant access to the current Windows Station and Desktop to the become user
334
353
  GrantAccessToWindowStationAndDesktop(account);
335
354
 
336
- // Try and impersonate a SYSTEM token. We need the SeTcbPrivilege for
337
- // - LogonUser for a service SID
338
- // - S4U logon
339
- // - Token elevation
355
+ // Try and impersonate a SYSTEM token, we need a SYSTEM token to either become a well known service
356
+ // account or have administrative rights on the become access token.
357
+ // If we ultimately are becoming the SYSTEM account we want the token with the most privileges available.
358
+ // https://github.com/ansible/ansible/issues/71453
359
+ bool usedForProcess = becomeSid == "S-1-5-18";
340
360
  systemToken = GetPrimaryTokenForUser(new SecurityIdentifier("S-1-5-18"),
341
- new List<string>() { "SeTcbPrivilege" });
361
+ new List<string>() { "SeTcbPrivilege" }, usedForProcess);
342
362
  if (systemToken != null)
343
363
  {
344
364
  try
@@ -356,9 +376,11 @@ namespace Ansible.Become
356
376
 
357
377
  try
358
378
  {
379
+ if (becomeSid == "S-1-5-18")
380
+ userTokens.Add(systemToken);
359
381
  // Cannot use String.IsEmptyOrNull() as an empty string is an account that doesn't have a pass.
360
382
  // We only use S4U if no password was defined or it was null
361
- if (!SERVICE_SIDS.Contains(becomeSid) && password == null && logonType != LogonType.NewCredentials)
383
+ else if (!SERVICE_SIDS.Contains(becomeSid) && password == null && logonType != LogonType.NewCredentials)
362
384
  {
363
385
  // If no password was specified, try and duplicate an existing token for that user or use S4U to
364
386
  // generate one without network credentials
@@ -381,11 +403,6 @@ namespace Ansible.Become
381
403
  string domain = null;
382
404
  switch (becomeSid)
383
405
  {
384
- case "S-1-5-18":
385
- logonType = LogonType.Service;
386
- domain = "NT AUTHORITY";
387
- username = "SYSTEM";
388
- break;
389
406
  case "S-1-5-19":
390
407
  logonType = LogonType.Service;
391
408
  domain = "NT AUTHORITY";
@@ -427,8 +444,10 @@ namespace Ansible.Become
427
444
  return userTokens;
428
445
  }
429
446
 
430
- private static SafeNativeHandle GetPrimaryTokenForUser(SecurityIdentifier sid,
431
- List<string> requiredPrivileges = null)
447
+ private static SafeNativeHandle GetPrimaryTokenForUser(
448
+ SecurityIdentifier sid,
449
+ List<string> requiredPrivileges = null,
450
+ bool usedForProcess = false)
432
451
  {
433
452
  // According to CreateProcessWithTokenW we require a token with
434
453
  // TOKEN_QUERY, TOKEN_DUPLICATE and TOKEN_ASSIGN_PRIMARY
@@ -438,7 +457,19 @@ namespace Ansible.Become
438
457
  TokenAccessLevels.AssignPrimary |
439
458
  TokenAccessLevels.Impersonate;
440
459
 
441
- foreach (SafeNativeHandle hToken in TokenUtil.EnumerateUserTokens(sid, dwAccess))
460
+ SafeNativeHandle userToken = null;
461
+ int privilegeCount = 0;
462
+
463
+ // If we are using this token for the process, we need to check the
464
+ // process mitigation policy allows child processes to be created.
465
+ var processFilter = usedForProcess
466
+ ? (Func<System.Diagnostics.Process, SafeNativeHandle, bool>)((p, t) =>
467
+ {
468
+ return GetProcessChildProcessPolicyFlags(t) == NativeHelpers.ProcessChildProcessPolicyFlags.None;
469
+ })
470
+ : ((p, t) => true);
471
+
472
+ foreach (SafeNativeHandle hToken in TokenUtil.EnumerateUserTokens(sid, dwAccess, processFilter))
442
473
  {
443
474
  // Filter out any Network logon tokens, using become with that is useless when S4U
444
475
  // can give us a Batch logon
@@ -448,6 +479,10 @@ namespace Ansible.Become
448
479
 
449
480
  List<string> actualPrivileges = TokenUtil.GetTokenPrivileges(hToken).Select(x => x.Name).ToList();
450
481
 
482
+ // If the token has less or the same number of privileges than the current token, skip it.
483
+ if (usedForProcess && privilegeCount >= actualPrivileges.Count)
484
+ continue;
485
+
451
486
  // Check that the required privileges are on the token
452
487
  if (requiredPrivileges != null)
453
488
  {
@@ -459,16 +494,22 @@ namespace Ansible.Become
459
494
  // Duplicate the token to convert it to a primary token with the access level required.
460
495
  try
461
496
  {
462
- return TokenUtil.DuplicateToken(hToken, TokenAccessLevels.MaximumAllowed,
497
+ userToken = TokenUtil.DuplicateToken(hToken, TokenAccessLevels.MaximumAllowed,
463
498
  SecurityImpersonationLevel.Anonymous, TokenType.Primary);
499
+ privilegeCount = actualPrivileges.Count;
464
500
  }
465
501
  catch (Process.Win32Exception)
466
502
  {
467
503
  continue;
468
504
  }
505
+
506
+ // If we don't care about getting the token with the most privileges, escape the loop as we already
507
+ // have a token.
508
+ if (!usedForProcess)
509
+ break;
469
510
  }
470
511
 
471
- return null;
512
+ return userToken;
472
513
  }
473
514
 
474
515
  private static SafeNativeHandle GetS4UTokenForUser(SecurityIdentifier sid, LogonType logonType)
@@ -581,6 +622,35 @@ namespace Ansible.Become
581
622
  return null;
582
623
  }
583
624
 
625
+ private static NativeHelpers.ProcessChildProcessPolicyFlags GetProcessChildProcessPolicyFlags(SafeNativeHandle processHandle)
626
+ {
627
+ // Because this is only used to check the policy, we ignore any
628
+ // errors and pretend that the policy is None.
629
+ NativeHelpers.ProcessChildProcessPolicyFlags policy = NativeHelpers.ProcessChildProcessPolicyFlags.None;
630
+
631
+ if (_getProcessMitigationPolicySupported)
632
+ {
633
+ try
634
+ {
635
+ if (NativeMethods.GetProcessMitigationPolicy(
636
+ processHandle,
637
+ NativeMethods.ProcessChildProcessPolicy,
638
+ ref policy,
639
+ (IntPtr)4))
640
+ {
641
+ return policy;
642
+ }
643
+ }
644
+ catch (EntryPointNotFoundException)
645
+ {
646
+ // If the function is not available, we won't try to call it again
647
+ _getProcessMitigationPolicySupported = false;
648
+ }
649
+ }
650
+
651
+ return policy;
652
+ }
653
+
584
654
  private static NativeHelpers.SECURITY_LOGON_TYPE GetTokenLogonType(SafeNativeHandle hToken)
585
655
  {
586
656
  TokenStatistics stats = TokenUtil.GetTokenStatistics(hToken);
@@ -637,4 +707,4 @@ namespace Ansible.Become
637
707
  { }
638
708
  }
639
709
  }
640
- }
710
+ }
@@ -15,12 +15,11 @@ version_added: historical
15
15
  description:
16
16
  - The M(ansible.builtin.command) module takes the command name followed by a list of space-delimited arguments.
17
17
  - The given command will be executed on all selected nodes.
18
- - The command(s) will not be
19
- processed through the shell, so variables like C($HOSTNAME) and operations
20
- like C("*"), C("<"), C(">"), C("|"), C(";") and C("&") will not work.
18
+ - The command(s) will not be processed through the shell, so operations like C("*"), C("<"), C(">"), C("|"), C(";") and C("&") will not work.
19
+ Also, environment variables are resolved via Python, not shell, see O(expand_argument_vars) and are left unchanged if not matched.
21
20
  Use the M(ansible.builtin.shell) module if you need these features.
22
- - To create C(command) tasks that are easier to read than the ones using space-delimited
23
- arguments, pass parameters using the C(args) L(task keyword,https://docs.ansible.com/ansible/latest/reference_appendices/playbooks_keywords.html#task)
21
+ - To create C(command) tasks that are easier to read than the ones using space-delimited arguments,
22
+ pass parameters using the C(args) L(task keyword,https://docs.ansible.com/ansible/latest/reference_appendices/playbooks_keywords.html#task)
24
23
  or use O(cmd) parameter.
25
24
  - Either a free form command or O(cmd) parameter is required, see the examples.
26
25
  - For Windows targets, use the M(ansible.windows.win_command) module instead.
@@ -41,8 +40,8 @@ attributes:
41
40
  options:
42
41
  expand_argument_vars:
43
42
  description:
44
- - Expands the arguments that are variables, for example C($HOME) will be expanded before being passed to the
45
- command to run.
43
+ - Expands the arguments that are variables, for example C($HOME) will be expanded before being passed to the command to run.
44
+ - If a variable is not matched, it is left unchanged, unlike shell substitution which would remove it.
46
45
  - Set to V(false) to disable expansion and treat the value as a literal argument.
47
46
  type: bool
48
47
  default: true
ansible/modules/dnf5.py CHANGED
@@ -358,6 +358,21 @@ libdnf5 = None
358
358
 
359
359
  def is_installed(base, spec):
360
360
  settings = libdnf5.base.ResolveSpecSettings()
361
+ try:
362
+ settings.set_group_with_name(True)
363
+ # Disable checking whether SPEC is a binary -> `/usr/(s)bin/<SPEC>`,
364
+ # this prevents scenarios like the following:
365
+ # * the `sssd-common` package is installed and provides `/usr/sbin/sssd` binary
366
+ # * the `sssd` package is NOT installed
367
+ # * due to `set_with_binaries(True)` being default `is_installed(base, "sssd")` would "unexpectedly" return True
368
+ # If users wish to target the `sssd` binary they can by specifying the full path `name=/usr/sbin/sssd` explicitly
369
+ # due to settings.set_with_filenames(True) being default.
370
+ settings.set_with_binaries(False)
371
+ except AttributeError:
372
+ # dnf5 < 5.2.0.0
373
+ settings.group_with_name = True
374
+ settings.with_binaries = False
375
+
361
376
  installed_query = libdnf5.rpm.PackageQuery(base)
362
377
  installed_query.filter_installed()
363
378
  match, nevra = installed_query.resolve_pkg_spec(spec, settings, True)
@@ -646,9 +661,12 @@ class Dnf5Module(YumDnf):
646
661
  settings = libdnf5.base.GoalJobSettings()
647
662
  try:
648
663
  settings.set_group_with_name(True)
664
+ settings.set_with_binaries(False)
649
665
  except AttributeError:
650
666
  # dnf5 < 5.2.0.0
651
667
  settings.group_with_name = True
668
+ settings.with_binaries = False
669
+
652
670
  if self.bugfix or self.security:
653
671
  advisory_query = libdnf5.advisory.AdvisoryQuery(base)
654
672
  types = []
ansible/modules/user.py CHANGED
@@ -1220,9 +1220,11 @@ class User(object):
1220
1220
  overwrite = None
1221
1221
  try:
1222
1222
  ssh_key_file = self.get_ssh_key_path()
1223
+ pub_file = f'{ssh_key_file}.pub'
1223
1224
  except Exception as e:
1224
1225
  return (1, '', to_native(e))
1225
1226
  ssh_dir = os.path.dirname(ssh_key_file)
1227
+
1226
1228
  if not os.path.exists(ssh_dir):
1227
1229
  if self.module.check_mode:
1228
1230
  return (0, '', '')
@@ -1231,12 +1233,23 @@ class User(object):
1231
1233
  os.chown(ssh_dir, info[2], info[3])
1232
1234
  except OSError as e:
1233
1235
  return (1, '', 'Failed to create %s: %s' % (ssh_dir, to_native(e)))
1236
+
1234
1237
  if os.path.exists(ssh_key_file):
1235
1238
  if self.force:
1236
- # ssh-keygen doesn't support overwriting the key interactively, so send 'y' to confirm
1239
+ self.module.warn(f'Overwriting existing ssh key private file "{ssh_key_file}"')
1237
1240
  overwrite = 'y'
1238
1241
  else:
1242
+ self.module.warn(f'Found existing ssh key private file "{ssh_key_file}", no force, so skipping ssh-keygen generation')
1239
1243
  return (None, 'Key already exists, use "force: yes" to overwrite', '')
1244
+
1245
+ if os.path.exists(pub_file):
1246
+ if self.force:
1247
+ self.module.warn(f'Overwriting existing ssh key public file "{pub_file}"')
1248
+ os.unlink(pub_file)
1249
+ else:
1250
+ self.module.warn(f'Found existing ssh key public file "{pub_file}", no force, so skipping ssh-keygen generation')
1251
+ return (None, 'Public key already exists, use "force: yes" to overwrite', '')
1252
+
1240
1253
  cmd = [self.module.get_bin_path('ssh-keygen', True)]
1241
1254
  cmd.append('-t')
1242
1255
  cmd.append(self.ssh_type)
@@ -1303,7 +1316,7 @@ class User(object):
1303
1316
  # If the keys were successfully created, we should be able
1304
1317
  # to tweak ownership.
1305
1318
  os.chown(ssh_key_file, info[2], info[3])
1306
- os.chown('%s.pub' % ssh_key_file, info[2], info[3])
1319
+ os.chown(pub_file, info[2], info[3])
1307
1320
  return (rc, out, err)
1308
1321
 
1309
1322
  def ssh_key_fingerprint(self):
@@ -237,7 +237,8 @@ class ActionModule(ActionBase):
237
237
  b_data, show_content = self._loader._get_file_contents(filename)
238
238
  data = to_text(b_data, errors='surrogate_or_strict')
239
239
 
240
- self.show_content = show_content
240
+ self.show_content &= show_content # mask all results if any file was encrypted
241
+
241
242
  data = self._loader.load(data, file_name=filename, show_content=show_content)
242
243
  if not data:
243
244
  data = dict()
@@ -13,11 +13,14 @@ DOCUMENTATION = """
13
13
  _terms:
14
14
  description: List of Python regex patterns to search for in variable names.
15
15
  required: True
16
+ seealso:
17
+ - plugin_type: lookup
18
+ plugin: ansible.builtin.vars
16
19
  """
17
20
 
18
21
  EXAMPLES = """
19
22
  - name: List variables that start with qz_
20
- ansible.builtin.debug: msg="{{ lookup('ansible.builtin.varnames', '^qz_.+')}}"
23
+ ansible.builtin.debug: msg="{{ lookup('ansible.builtin.varnames', '^qz_.+') }}"
21
24
  vars:
22
25
  qz_1: hello
23
26
  qz_2: world
@@ -25,13 +28,16 @@ EXAMPLES = """
25
28
  qz_: "I won't show either"
26
29
 
27
30
  - name: Show all variables
28
- ansible.builtin.debug: msg="{{ lookup('ansible.builtin.varnames', '.+')}}"
31
+ ansible.builtin.debug: msg="{{ lookup('ansible.builtin.varnames', '.+') }}"
29
32
 
30
33
  - name: Show variables with 'hosts' in their names
31
- ansible.builtin.debug: msg="{{ lookup('ansible.builtin.varnames', 'hosts')}}"
34
+ ansible.builtin.debug: msg="{{ q('varnames', 'hosts') }}"
32
35
 
33
36
  - name: Find several related variables that end specific way
34
- ansible.builtin.debug: msg="{{ lookup('ansible.builtin.varnames', '.+_zone$', '.+_location$') }}"
37
+ ansible.builtin.debug: msg="{{ query('ansible.builtin.varnames', '.+_zone$', '.+_location$') }}"
38
+
39
+ - name: display values from variables found via varnames (note "*" is used to dereference the list to a 'list of arguments')
40
+ debug: msg="{{ lookup('vars', *lookup('varnames', 'ansible_play_.+')) }}"
35
41
 
36
42
  """
37
43
 
@@ -17,6 +17,10 @@ DOCUMENTATION = """
17
17
  description:
18
18
  - What to return if a variable is undefined.
19
19
  - If no default is set, it will result in an error if any of the variables is undefined.
20
+ seealso:
21
+ - plugin_type: lookup
22
+ plugin: ansible.builtin.varnames
23
+
20
24
  """
21
25
 
22
26
  EXAMPLES = """
@@ -27,20 +31,23 @@ EXAMPLES = """
27
31
  myvar: ename
28
32
 
29
33
  - name: Show default empty since i dont have 'variablnotename'
30
- ansible.builtin.debug: msg="{{ lookup('ansible.builtin.vars', 'variabl' + myvar, default='')}}"
34
+ ansible.builtin.debug: msg="{{ lookup('ansible.builtin.vars', 'variabl' + myvar, default='') }}"
31
35
  vars:
32
36
  variablename: hello
33
37
  myvar: notename
34
38
 
35
39
  - name: Produce an error since i dont have 'variablnotename'
36
- ansible.builtin.debug: msg="{{ lookup('ansible.builtin.vars', 'variabl' + myvar)}}"
40
+ ansible.builtin.debug: msg="{{ q('vars', 'variabl' + myvar) }}"
37
41
  ignore_errors: True
38
42
  vars:
39
43
  variablename: hello
40
44
  myvar: notename
41
45
 
42
46
  - name: find several related variables
43
- ansible.builtin.debug: msg="{{ lookup('ansible.builtin.vars', 'ansible_play_hosts', 'ansible_play_batch', 'ansible_play_hosts_all') }}"
47
+ ansible.builtin.debug: msg="{{ query('ansible.builtin.vars', 'ansible_play_hosts', 'ansible_play_batch', 'ansible_play_hosts_all') }}"
48
+
49
+ - name: show values from variables found via varnames (note "*" is used to dereference the list to a 'list of arguments')
50
+ debug: msg="{{ q('vars', *q('varnames', 'ansible_play_.+')) }}"
44
51
 
45
52
  - name: Access nested variables
46
53
  ansible.builtin.debug: msg="{{ lookup('ansible.builtin.vars', 'variabl' + myvar).sub_var }}"
ansible/release.py CHANGED
@@ -17,6 +17,6 @@
17
17
 
18
18
  from __future__ import annotations
19
19
 
20
- __version__ = '2.18.0rc1'
20
+ __version__ = '2.18.1rc1'
21
21
  __author__ = 'Ansible, Inc.'
22
22
  __codename__ = "Fool in the Rain"
@@ -48,7 +48,7 @@ from ansible.module_utils.six import string_types
48
48
  from ansible.module_utils.common.text.converters import to_native, to_text, to_bytes
49
49
  from ansible.module_utils.common.collections import is_sequence
50
50
  from ansible.plugins.loader import filter_loader, lookup_loader, test_loader
51
- from ansible.template.native_helpers import ansible_native_concat, ansible_eval_concat, ansible_concat
51
+ from ansible.template.native_helpers import AnsibleUndefined, ansible_native_concat, ansible_eval_concat, ansible_concat
52
52
  from ansible.template.template import AnsibleJ2Template
53
53
  from ansible.template.vars import AnsibleJ2Vars
54
54
  from ansible.utils.display import Display
@@ -312,35 +312,6 @@ def _wrap_native_text(func):
312
312
  return functools.update_wrapper(wrapper, func)
313
313
 
314
314
 
315
- class AnsibleUndefined(StrictUndefined):
316
- '''
317
- A custom Undefined class, which returns further Undefined objects on access,
318
- rather than throwing an exception.
319
- '''
320
- def __getattr__(self, name):
321
- if name == '__UNSAFE__':
322
- # AnsibleUndefined should never be assumed to be unsafe
323
- # This prevents ``hasattr(val, '__UNSAFE__')`` from evaluating to ``True``
324
- raise AttributeError(name)
325
- # Return original Undefined object to preserve the first failure context
326
- return self
327
-
328
- def __getitem__(self, key):
329
- # Return original Undefined object to preserve the first failure context
330
- return self
331
-
332
- def __repr__(self):
333
- return 'AnsibleUndefined(hint={0!r}, obj={1!r}, name={2!r})'.format(
334
- self._undefined_hint,
335
- self._undefined_obj,
336
- self._undefined_name
337
- )
338
-
339
- def __contains__(self, item):
340
- # Return original Undefined object to preserve the first failure context
341
- return self
342
-
343
-
344
315
  class AnsibleContext(Context):
345
316
  '''
346
317
  A custom context, which intercepts resolve_or_missing() calls and sets a flag
@@ -5,13 +5,19 @@ from __future__ import annotations
5
5
 
6
6
 
7
7
  import ast
8
+ from collections.abc import Mapping
8
9
  from itertools import islice, chain
9
10
  from types import GeneratorType
10
11
 
12
+ from ansible.module_utils.common.collections import is_sequence
11
13
  from ansible.module_utils.common.text.converters import to_text
12
14
  from ansible.module_utils.six import string_types
13
15
  from ansible.parsing.yaml.objects import AnsibleVaultEncryptedUnicode
14
16
  from ansible.utils.native_jinja import NativeJinjaText
17
+ from ansible.utils.unsafe_proxy import wrap_var
18
+ import ansible.module_utils.compat.typing as t
19
+
20
+ from jinja2.runtime import StrictUndefined
15
21
 
16
22
 
17
23
  _JSON_MAP = {
@@ -28,6 +34,40 @@ class Json2Python(ast.NodeTransformer):
28
34
  return ast.Constant(value=_JSON_MAP[node.id])
29
35
 
30
36
 
37
+ def _is_unsafe(value: t.Any) -> bool:
38
+ """
39
+ Our helper function, which will also recursively check dict and
40
+ list entries due to the fact that they may be repr'd and contain
41
+ a key or value which contains jinja2 syntax and would otherwise
42
+ lose the AnsibleUnsafe value.
43
+ """
44
+ to_check = [value]
45
+ seen = set()
46
+
47
+ while True:
48
+ if not to_check:
49
+ break
50
+
51
+ val = to_check.pop(0)
52
+ val_id = id(val)
53
+
54
+ if val_id in seen:
55
+ continue
56
+ seen.add(val_id)
57
+
58
+ if isinstance(val, AnsibleUndefined):
59
+ continue
60
+ if isinstance(val, Mapping):
61
+ to_check.extend(val.keys())
62
+ to_check.extend(val.values())
63
+ elif is_sequence(val):
64
+ to_check.extend(val)
65
+ elif getattr(val, '__UNSAFE__', False):
66
+ return True
67
+
68
+ return False
69
+
70
+
31
71
  def ansible_eval_concat(nodes):
32
72
  """Return a string of concatenated compiled nodes. Throw an undefined error
33
73
  if any of the nodes is undefined.
@@ -43,17 +83,28 @@ def ansible_eval_concat(nodes):
43
83
  if not head:
44
84
  return ''
45
85
 
86
+ unsafe = False
87
+
46
88
  if len(head) == 1:
47
89
  out = head[0]
48
90
 
49
91
  if isinstance(out, NativeJinjaText):
50
92
  return out
51
93
 
94
+ unsafe = _is_unsafe(out)
52
95
  out = to_text(out)
53
96
  else:
54
97
  if isinstance(nodes, GeneratorType):
55
98
  nodes = chain(head, nodes)
56
- out = ''.join([to_text(v) for v in nodes])
99
+
100
+ out_values = []
101
+ for v in nodes:
102
+ if not unsafe and _is_unsafe(v):
103
+ unsafe = True
104
+
105
+ out_values.append(to_text(v))
106
+
107
+ out = ''.join(out_values)
57
108
 
58
109
  # if this looks like a dictionary, list or bool, convert it to such
59
110
  if out.startswith(('{', '[')) or out in ('True', 'False'):
@@ -68,6 +119,9 @@ def ansible_eval_concat(nodes):
68
119
  except (TypeError, ValueError, SyntaxError, MemoryError):
69
120
  pass
70
121
 
122
+ if unsafe:
123
+ out = wrap_var(out)
124
+
71
125
  return out
72
126
 
73
127
 
@@ -78,7 +132,19 @@ def ansible_concat(nodes):
78
132
 
79
133
  Used in Templar.template() when jinja2_native=False and convert_data=False.
80
134
  """
81
- return ''.join([to_text(v) for v in nodes])
135
+ unsafe = False
136
+ values = []
137
+ for v in nodes:
138
+ if not unsafe and _is_unsafe(v):
139
+ unsafe = True
140
+
141
+ values.append(to_text(v))
142
+
143
+ out = ''.join(values)
144
+ if unsafe:
145
+ out = wrap_var(out)
146
+
147
+ return out
82
148
 
83
149
 
84
150
  def ansible_native_concat(nodes):
@@ -95,6 +161,8 @@ def ansible_native_concat(nodes):
95
161
  if not head:
96
162
  return None
97
163
 
164
+ unsafe = False
165
+
98
166
  if len(head) == 1:
99
167
  out = head[0]
100
168
 
@@ -115,10 +183,21 @@ def ansible_native_concat(nodes):
115
183
  # short-circuit literal_eval for anything other than strings
116
184
  if not isinstance(out, string_types):
117
185
  return out
186
+
187
+ unsafe = _is_unsafe(out)
188
+
118
189
  else:
119
190
  if isinstance(nodes, GeneratorType):
120
191
  nodes = chain(head, nodes)
121
- out = ''.join([to_text(v) for v in nodes])
192
+
193
+ out_values = []
194
+ for v in nodes:
195
+ if not unsafe and _is_unsafe(v):
196
+ unsafe = True
197
+
198
+ out_values.append(to_text(v))
199
+
200
+ out = ''.join(out_values)
122
201
 
123
202
  try:
124
203
  evaled = ast.literal_eval(
@@ -128,10 +207,45 @@ def ansible_native_concat(nodes):
128
207
  ast.parse(out, mode='eval')
129
208
  )
130
209
  except (TypeError, ValueError, SyntaxError, MemoryError):
210
+ if unsafe:
211
+ out = wrap_var(out)
212
+
131
213
  return out
132
214
 
133
215
  if isinstance(evaled, string_types):
134
216
  quote = out[0]
135
- return f'{quote}{evaled}{quote}'
217
+ evaled = f'{quote}{evaled}{quote}'
218
+
219
+ if unsafe:
220
+ evaled = wrap_var(evaled)
136
221
 
137
222
  return evaled
223
+
224
+
225
+ class AnsibleUndefined(StrictUndefined):
226
+ """
227
+ A custom Undefined class, which returns further Undefined objects on access,
228
+ rather than throwing an exception.
229
+ """
230
+ def __getattr__(self, name):
231
+ if name == '__UNSAFE__':
232
+ # AnsibleUndefined should never be assumed to be unsafe
233
+ # This prevents ``hasattr(val, '__UNSAFE__')`` from evaluating to ``True``
234
+ raise AttributeError(name)
235
+ # Return original Undefined object to preserve the first failure context
236
+ return self
237
+
238
+ def __getitem__(self, key):
239
+ # Return original Undefined object to preserve the first failure context
240
+ return self
241
+
242
+ def __repr__(self):
243
+ return 'AnsibleUndefined(hint={0!r}, obj={1!r}, name={2!r})'.format(
244
+ self._undefined_hint,
245
+ self._undefined_obj,
246
+ self._undefined_name
247
+ )
248
+
249
+ def __contains__(self, item):
250
+ # Return original Undefined object to preserve the first failure context
251
+ return self
ansible/vars/hostvars.py CHANGED
@@ -92,10 +92,12 @@ class HostVars(Mapping):
92
92
  return self._find_host(host_name) is not None
93
93
 
94
94
  def __iter__(self):
95
- yield from self._inventory.hosts
95
+ # include implicit localhost only if it has variables set
96
+ yield from self._inventory.hosts | {'localhost': self._inventory.localhost} if self._inventory.localhost else {}
96
97
 
97
98
  def __len__(self):
98
- return len(self._inventory.hosts)
99
+ # include implicit localhost only if it has variables set
100
+ return len(self._inventory.hosts) + (1 if self._inventory.localhost else 0)
99
101
 
100
102
  def __repr__(self):
101
103
  out = {}
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ansible-core
3
- Version: 2.18.0rc1
3
+ Version: 2.18.1rc1
4
4
  Summary: Radically simple IT automation
5
5
  Author: Ansible Project
6
6
  Project-URL: Homepage, https://ansible.com/
@@ -32,11 +32,11 @@ License-File: licenses/Apache-License.txt
32
32
  License-File: licenses/MIT-license.txt
33
33
  License-File: licenses/PSF-license.txt
34
34
  License-File: licenses/simplified_bsd.txt
35
- Requires-Dist: jinja2 >=3.0.0
36
- Requires-Dist: PyYAML >=5.1
35
+ Requires-Dist: jinja2>=3.0.0
36
+ Requires-Dist: PyYAML>=5.1
37
37
  Requires-Dist: cryptography
38
38
  Requires-Dist: packaging
39
- Requires-Dist: resolvelib <1.1.0,>=0.5.3
39
+ Requires-Dist: resolvelib<1.1.0,>=0.5.3
40
40
 
41
41
  [![PyPI version](https://img.shields.io/pypi/v/ansible-core.svg)](https://pypi.org/project/ansible-core)
42
42
  [![Docs badge](https://img.shields.io/badge/docs-latest-brightgreen.svg)](https://docs.ansible.com/ansible/latest/)
@@ -3,7 +3,7 @@ ansible/__main__.py,sha256=24j-7-YT4lZ2fmV80JD-VRoYBnxR7YoP_VP-orJtDt0,796
3
3
  ansible/constants.py,sha256=dSgbrzNsmhYc4GQOWZvRm4XKgf--_MUWcMa_9_7l5Pc,9757
4
4
  ansible/context.py,sha256=oKYyfjfWpy8vDeProtqfnqSmuij_t75_5e5t0U_hQ1g,1933
5
5
  ansible/keyword_desc.yml,sha256=xD-MRMB8mSRaj2ADwRnjIEbOwJKbc6BYadouGPfS0mI,7462
6
- ansible/release.py,sha256=Lm7Ed7E3swl8p4poj0NRx76RtYuhXMYNO4FJtAFNKsY,839
6
+ ansible/release.py,sha256=0ZMgTOTRKMyYnQWknncQyldpI2t8HSHVuMmm_U-K_sE,839
7
7
  ansible/_vendor/__init__.py,sha256=2QBeBwT7uG7M3Aw-pIdCpt6XPtHMCpbEKfACYKA7xIg,2033
8
8
  ansible/cli/__init__.py,sha256=e0KjeLfG1Ketbwl-uOmQ-zXoq3_El80LnHTGu80d1gs,28111
9
9
  ansible/cli/adhoc.py,sha256=quJ9WzRzf3dz_dtDGmahNMffqyNVy1jzQCMo21YL5Qg,8194
@@ -37,7 +37,7 @@ ansible/executor/module_common.py,sha256=4pVfjMgCle9ttAZTeuwSx3Kdi0rljagyHC11i4V
37
37
  ansible/executor/play_iterator.py,sha256=Oazd9aWy4zB67uy27sYg4BveFx_Uf_tIsbhD76hMc28,32496
38
38
  ansible/executor/playbook_executor.py,sha256=qurZBiWjAWWRYcHb3ti4sBI8_WotOYvTRDar4JC3leE,14764
39
39
  ansible/executor/stats.py,sha256=gcBhJQrZTgE95737d6lArJ3FpTlbAfVt6GMhEqs5ZPU,3180
40
- ansible/executor/task_executor.py,sha256=5kekBuuw4Na5o1fpaErkLKxxTGLMBUnDxkCjCJfgmd0,61133
40
+ ansible/executor/task_executor.py,sha256=99oxT0B787aRpw7B8OssXWuzltcfiraGISnIpHTo7U4,61338
41
41
  ansible/executor/task_queue_manager.py,sha256=fC404XkveICb7hRwIVu4PvRNgFkFLCNihsGsUDHVFzg,18640
42
42
  ansible/executor/task_result.py,sha256=48zZWpxCiM0Z_MVG9zGQGCxHLNzs1horT68Qmfa-v_8,5696
43
43
  ansible/executor/discovery/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -141,7 +141,7 @@ ansible/inventory/host.py,sha256=PDb5OTplhfpUIvdHiP2BckUOB1gUl302N-3sW0_sTyg,503
141
141
  ansible/inventory/manager.py,sha256=45mHgZTAkQ3IjAtrgsNzJXvynC-HIEor-JJE-V3xXN4,29454
142
142
  ansible/module_utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
143
143
  ansible/module_utils/_text.py,sha256=VkWgAnSNVCbTQqZgllUObBFsH3uM4EUW5srl1UR9t1g,544
144
- ansible/module_utils/ansible_release.py,sha256=Lm7Ed7E3swl8p4poj0NRx76RtYuhXMYNO4FJtAFNKsY,839
144
+ ansible/module_utils/ansible_release.py,sha256=0ZMgTOTRKMyYnQWknncQyldpI2t8HSHVuMmm_U-K_sE,839
145
145
  ansible/module_utils/api.py,sha256=r4wd6XZGhUnxMF416Ry6ebgq8BIhjCPSPOvO2ZtrYxE,5785
146
146
  ansible/module_utils/basic.py,sha256=fogfpo_l7JtS34WvgwwOebmPfMhFjQaJN5CwjKgUJVE,86291
147
147
  ansible/module_utils/connection.py,sha256=8TviwCucQ7d_JILwaUHE4tCuNfR3U1WFkmxLMxWa8Rw,7671
@@ -184,9 +184,9 @@ ansible/module_utils/compat/selectors.py,sha256=OcR8ACS6Cr9ShlFlv8sC3QQZ7qUaP5Wh
184
184
  ansible/module_utils/compat/selinux.py,sha256=9bq2UMTE_PILEHdvUsXPk_84oWfKiMppyrZs7qH4jdI,3488
185
185
  ansible/module_utils/compat/typing.py,sha256=J_K9Ru1-f0KSKO_WhWGRCh0WBNWl6jUmQK1_0yYYZOs,736
186
186
  ansible/module_utils/compat/version.py,sha256=ifck3MH9LhxMENpZXOrntEqX6b7X8shknt3Fweg6AzQ,12734
187
- ansible/module_utils/csharp/Ansible.AccessToken.cs,sha256=4HzIFQKGG3ZTg8tehVcM_ukMi057wxxLdYFZoqsij5I,15871
187
+ ansible/module_utils/csharp/Ansible.AccessToken.cs,sha256=TIOpRx4lv9FlhMyWfHS30CIMRtM3uqR8-u6Rv0X1HWE,16390
188
188
  ansible/module_utils/csharp/Ansible.Basic.cs,sha256=xp1pMZNhib3vhR3Wdc5JlDv1SORo4dIGjc17LmnLr1g,78845
189
- ansible/module_utils/csharp/Ansible.Become.cs,sha256=E2z2LFYv5bC7hzLP6GfZt8oYZEGN8bryOzbJfumpZ4U,29716
189
+ ansible/module_utils/csharp/Ansible.Become.cs,sha256=g0FyAMO3kl186IGhgACQhNAMP8o2UHNzXVUv0Enau2w,32793
190
190
  ansible/module_utils/csharp/Ansible.Privilege.cs,sha256=7e46na6k6ygdRwN53bzfIS8O-IwfM1TF_q5DeFH2Z80,19398
191
191
  ansible/module_utils/csharp/Ansible.Process.cs,sha256=bON6hExhSB-SR2p2ryFZj6kU3a5TxonFv498wg2Je98,19452
192
192
  ansible/module_utils/csharp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -289,14 +289,14 @@ ansible/modules/assert.py,sha256=EAfJEQ21pgdKqhqQAI_pfJIQPyKX0XEP2BP6kJgLCgU,295
289
289
  ansible/modules/async_status.py,sha256=iuNMObp1piLIaoO-7ZiueUNitk-5CDW93l9iVPqd53I,4574
290
290
  ansible/modules/async_wrapper.py,sha256=OjA0E7lm8mvlxXA76-HVvjc_H1fHae0Euh25exY6gEc,11619
291
291
  ansible/modules/blockinfile.py,sha256=A_6b-ZHKWIB8G5mgn6rFJeZfoUtqUO80orHx5j5TBPY,15451
292
- ansible/modules/command.py,sha256=dxhIvyeOwQG_SkVSHN0h652z6a3sTAR532HB_zwM4hc,13962
292
+ ansible/modules/command.py,sha256=ItnYqvij_n4UOR344EetJ9xn_abCAj5_qklJ-rzlN8A,14155
293
293
  ansible/modules/copy.py,sha256=2A1rh9Mpl8oFgTeS7xmVSUZ1v920I60jMEgrkwfBwyg,32106
294
294
  ansible/modules/cron.py,sha256=qR5ePdI3GZQeBSCn9YqxTxWlNEblCaTFnBRZqLjtnPo,26353
295
295
  ansible/modules/deb822_repository.py,sha256=SLJM8bBLc70WYu3-OA67wd5hMft3pznYAMIidYOtmUU,15791
296
296
  ansible/modules/debconf.py,sha256=Y49U5pM6UpKvYAvDbOhYe6kmQFAaxjl7YoYnPrOaGGU,9362
297
297
  ansible/modules/debug.py,sha256=BFbzrU_vl-Try5DuLV20_sLgqxEJlPV9uOrgAtby2e8,2908
298
298
  ansible/modules/dnf.py,sha256=rsb28kjMMnTu-rPW0Pdnbs2RPyvfdhWgXeUORUSgzEI,52288
299
- ansible/modules/dnf5.py,sha256=9nM6v8C4icI0vQ_5HIpmCY_XtYPOGHUhBwKabt7pz2Q,28230
299
+ ansible/modules/dnf5.py,sha256=csebof_G-_9HPDRJw65bnWv492jXL5Om7O4adw5w0Xg,29128
300
300
  ansible/modules/dpkg_selections.py,sha256=lTWBhmVFrf6PsV4_BoR23wVTJOloCH1YNPcAn0m7DTY,2805
301
301
  ansible/modules/expect.py,sha256=O4emRoJ09i3OLmVX5j84WHkGKWg6bMytYpZlExOrSmc,9369
302
302
  ansible/modules/fail.py,sha256=95z8jFyVaizwwupSce04kj1wwnOmbM0ooUX7mXluoyU,1659
@@ -347,7 +347,7 @@ ansible/modules/tempfile.py,sha256=lA9e8lyFXf9J5ud0R6Jkt8sIFyRcOwzhc9Jz-5_HOZQ,3
347
347
  ansible/modules/template.py,sha256=D1sm36GB_mEimH0CfWq1cJ4w1eRvpcsHwZ-ufVzC_Gs,4537
348
348
  ansible/modules/unarchive.py,sha256=wdSOFKhZqbAFq5j_tBZtUSamfr_EEW6_cTZDw-erMjs,45405
349
349
  ansible/modules/uri.py,sha256=rhybLSwlciwEvTbxTOvgRTZgdrCrmPaLSfoSdiIu0Xo,28390
350
- ansible/modules/user.py,sha256=KQshXIlg0ktsvGS1leZl_7Mlg76iC5WDqQtMs8P9o58,122706
350
+ ansible/modules/user.py,sha256=w0RVtz89EA2JpAhwav_J-2mGFKnEfW7MxCTKeug-r14,123301
351
351
  ansible/modules/validate_argument_spec.py,sha256=XbWlUr4ElgLfdxo3qCN7M-IES_X2iTl3AgawzCOMQpo,3042
352
352
  ansible/modules/wait_for.py,sha256=VXFFcYG88EJVXnrJfa0fzh9rD_2luSty__qdzRuTAQE,27322
353
353
  ansible/modules/wait_for_connection.py,sha256=8ySz5bhK7LGSaT_7Jzk3jvACcnngyR2g_ziyFLKk6Rk,3367
@@ -410,7 +410,7 @@ ansible/plugins/action/fail.py,sha256=_1JuS0Z8Y8EB4FKG1u7KdP6xMuLobRHJsmtzmvN2Ck
410
410
  ansible/plugins/action/fetch.py,sha256=cQAmUWEGMDjfVfHGviNtsT4i06rnoubL3EgrOlUZbLw,10188
411
411
  ansible/plugins/action/gather_facts.py,sha256=JFkrn3_78A7eUw0I3DsfUoe3Pu3wVLkhLUiOJ7Oyk0A,7863
412
412
  ansible/plugins/action/group_by.py,sha256=97d4TF9o7vS5y0s1HfGgvh70l2gkQ2uUGxy0knlok5Y,1894
413
- ansible/plugins/action/include_vars.py,sha256=XnX2mgcZpK4NHn7Bktc8IUjzRkVbjA4xHsiJ_vap9U0,11525
413
+ ansible/plugins/action/include_vars.py,sha256=_xPrP_BeGqbhvpJYQFDUkREL9UzZ6Y4q_AnCshvsn1A,11573
414
414
  ansible/plugins/action/normal.py,sha256=cCHrZ3z2kB_wnnSNkmJHJWcJNRgdoxnLUNeHex-P8DE,1854
415
415
  ansible/plugins/action/package.py,sha256=UWk7T-hG6GoqixgUzz1iDT16hUQ2-bM26IZ-w52xoS4,5014
416
416
  ansible/plugins/action/pause.py,sha256=A_U8FhGeFdaOXUadEM-Mv42v9lwsjnxPOE7ExvEfl1s,5674
@@ -576,8 +576,8 @@ ansible/plugins/lookup/template.py,sha256=xFYWKY808hHPj7nJbaLM2mZro79p6TjpFXyAcR
576
576
  ansible/plugins/lookup/together.py,sha256=T4J2miqHTnrDP6-CrlJ3wgI0UgyZyYVRVrDTWx3olpY,2110
577
577
  ansible/plugins/lookup/unvault.py,sha256=5LU8Lf7Gx0yRh8z0u1giSXkd93pkSZ34ibkoQnHCsyw,2049
578
578
  ansible/plugins/lookup/url.py,sha256=8JzzKOuWNI4st--CwmJZx-16ykFZKhcZdj4wb1za0Tk,9583
579
- ansible/plugins/lookup/varnames.py,sha256=h5ZAHOx8MlEvv466AirXCaGZ5DeH95evGb2he8_aKqA,2330
580
- ansible/plugins/lookup/vars.py,sha256=eXVZdwumdcp3ajaDX7JyIYeGvQ6L-HxHGfnob9Pnkg8,3424
579
+ ansible/plugins/lookup/varnames.py,sha256=4WKSH-u0ZnQD47r20c5OenaG6Vlamp6nweIZSFOJ0b8,2595
580
+ ansible/plugins/lookup/vars.py,sha256=W9at2j3xBk6z4IXfSutTEHqdzbiTI1B1tuYOLdd1BMQ,3672
581
581
  ansible/plugins/netconf/__init__.py,sha256=50w1g2rhUo6L-xtiMT20jbR8WyOnhwNSRd2IRNSjNX4,17094
582
582
  ansible/plugins/shell/__init__.py,sha256=8pc3ab91OGbnvzk3oQ-sU8uXMXNoNbjtdCbdXmZYTWY,8985
583
583
  ansible/plugins/shell/cmd.py,sha256=kPCSKrJJFH5XTkmteEI3P1Da6WfPSXxDnV39VFpgD-A,2170
@@ -645,8 +645,8 @@ ansible/plugins/test/version.yml,sha256=2d55HZGIniPu53z6_bV4C26_1sqRAHJqCwesOU3m
645
645
  ansible/plugins/test/version_compare.yml,sha256=2d55HZGIniPu53z6_bV4C26_1sqRAHJqCwesOU3ma38,3283
646
646
  ansible/plugins/vars/__init__.py,sha256=D3YwVKABesBwag9e7GsLOxlRWqEO5NgfHDmYSq0z_1k,1331
647
647
  ansible/plugins/vars/host_group_vars.py,sha256=Qouyds_KOEuqaz4GlTYQnQUxXyTyyjFMj7maRnH8miU,6284
648
- ansible/template/__init__.py,sha256=PZCt_0sZreC4AAg-XSdwoo1VG237Z_B9NElxU1Yu1oU,40686
649
- ansible/template/native_helpers.py,sha256=XjaTCQFSq0X6xTVENviRKYRVqmgI7IXCx70DeZ0C7F4,4333
648
+ ansible/template/__init__.py,sha256=Raj_xvUtICZnReq0UhYTiJXQJVcC16Q9q2iQ4_KbUeo,39682
649
+ ansible/template/native_helpers.py,sha256=l-Jo2-ExemCheK-Yi-NRIUYkJyHqCGjRsW2ANTrqmuQ,7213
650
650
  ansible/template/template.py,sha256=47dvX9AqSKlp6-n2QRPrHyhI3bboVyOpQekmQYryUB4,1583
651
651
  ansible/template/vars.py,sha256=YUCVqNLS3wjYHACSei7f5uwZMZRBTwiyjGge09EP00E,2854
652
652
  ansible/utils/__init__.py,sha256=mRvbCJPA-_veSG5ka3v04G5vsarLVDeB3EWFsu6geSI,749
@@ -683,7 +683,7 @@ ansible/utils/collection_loader/_collection_meta.py,sha256=L8NWlDs5KBMASIKRGoFTN
683
683
  ansible/vars/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
684
684
  ansible/vars/clean.py,sha256=X2WMksJMWITQ9FsM-Fb_YxT_hAGDqJ3urSTJzYBEdAk,5999
685
685
  ansible/vars/fact_cache.py,sha256=M57vMhkQ2DrzvNaZkfaCmKQJUqP1Rn_A31_X-5YBfzQ,1903
686
- ansible/vars/hostvars.py,sha256=ggUQ5luCajjX7sEvFCHpIuB_stWPRb089cZ3I1v1Vmo,5070
686
+ ansible/vars/hostvars.py,sha256=o11xrzDVYn23renGbb3lx3R-nH9qOjLFju5IYJanDxg,5324
687
687
  ansible/vars/manager.py,sha256=nFJcvbhanUQO7loAUiFHEnGCuRpxsmYXCj8e9XVJTYc,30972
688
688
  ansible/vars/plugins.py,sha256=PocWZPMqFl1LoNgWlGFNxwg9nZnUzhQmlXO4g7bcP2A,4503
689
689
  ansible/vars/reserved.py,sha256=kZiQMPvaFin35006gLwDpX16w-9xlu6EaL4LSTKP40U,2531
@@ -744,7 +744,7 @@ ansible_test/_internal/completion.py,sha256=G_4MfyNoISRBEM3Adgh66KBJH2e0mVRS8Ice
744
744
  ansible_test/_internal/config.py,sha256=phNYKVGNOJKuzt0zWOpe2rLrqANlwwfNePPx3V2ZVEk,12131
745
745
  ansible_test/_internal/connections.py,sha256=-gK9FqvmpsjENdYNkvWgFgqYHJSS_F2XkvQzH2_s86E,7855
746
746
  ansible_test/_internal/constants.py,sha256=Zwgp8wtUuge_8xMPg0pDUt58fBd9KA7YEPTQqAQv8ac,1969
747
- ansible_test/_internal/containers.py,sha256=8uRbrDtQKJznPYHbrCDuxZI0teyhcL8qT3mAO2M_DU8,33905
747
+ ansible_test/_internal/containers.py,sha256=TrkHL4ntmb7HrmD55BzSdqF35s7oFKu0vCywpWfRt-k,34137
748
748
  ansible_test/_internal/content_config.py,sha256=QKR_XVBgYRNZL-XawF2pN2ERTZ6lSm1AJg9ZQRD6IHE,5588
749
749
  ansible_test/_internal/core_ci.py,sha256=pyiwFG_TgDSQw34qW-PG8T2VYS6XxiF0zOEWGYXRRek,17309
750
750
  ansible_test/_internal/coverage_util.py,sha256=_SPR0sqkgPoGw2bzuRS5gr4XOyIU8MQ4a9U8FgyWHho,9283
@@ -773,7 +773,7 @@ ansible_test/_internal/target.py,sha256=Whtb_n0jn4zbiMmX7je5jewgzsRczfXRm_ndYtjT
773
773
  ansible_test/_internal/test.py,sha256=znQmGjKACqDU8T0EAPqcv2qyy0J7M2w4OmyYhwHLqT0,14515
774
774
  ansible_test/_internal/thread.py,sha256=WQoZ2q2ljmEkKHRDkIqwxW7eZbkCKDrG3YZfcaxHzHw,2596
775
775
  ansible_test/_internal/timeout.py,sha256=X8LxoMSU85lw4MszfEljaG6V7Aff4Btveg3r89Fe25U,4052
776
- ansible_test/_internal/util.py,sha256=iv1RD99PvhMixH0wxinwqkaM0POv-zrVxwbkbruHEDI,38868
776
+ ansible_test/_internal/util.py,sha256=o8efNGzuieD3mf3B7Ash1r25dEcePB7L-HOwu5EPFto,38908
777
777
  ansible_test/_internal/util_common.py,sha256=wSygjQRWlkFNpqQnPSSXnz_3Cr0pWrPvCP-6lMdfuAk,17490
778
778
  ansible_test/_internal/venv.py,sha256=k7L9_Ocpsdwp4kQFLF59BVguymd2nqJ-bLHH1NlMET0,5521
779
779
  ansible_test/_internal/ci/__init__.py,sha256=QOaC_8_wUzqFEbsFCXYAnElWoUo6gB40CXvP9RJ-Iyo,7738
@@ -980,13 +980,13 @@ ansible_test/config/cloud-config-vultr.ini.template,sha256=XLKHk3lg_8ReQMdWfZzhh
980
980
  ansible_test/config/config.yml,sha256=1zdGucnIl6nIecZA7ISIANvqXiHWqq6Dthsk_6MUwNc,2642
981
981
  ansible_test/config/inventory.networking.template,sha256=bFNSk8zNQOaZ_twaflrY0XZ9mLwUbRLuNT0BdIFwvn4,1335
982
982
  ansible_test/config/inventory.winrm.template,sha256=1QU8W-GFLnYEw8yY9bVIvUAVvJYPM3hyoijf6-M7T00,1098
983
- ansible_core-2.18.0rc1.dist-info/Apache-License.txt,sha256=y16Ofl9KOYjhBjwULGDcLfdWBfTEZRXnduOspt-XbhQ,11325
984
- ansible_core-2.18.0rc1.dist-info/COPYING,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
985
- ansible_core-2.18.0rc1.dist-info/METADATA,sha256=DKeA6eyLrlMK2hduqbMVi7APLYgrQZbsqyNSV1N27_M,7674
986
- ansible_core-2.18.0rc1.dist-info/MIT-license.txt,sha256=jLXp2XurnyZKbye40g9tfmLGtVlxh3pPD4n8xNqX8xc,1023
987
- ansible_core-2.18.0rc1.dist-info/PSF-license.txt,sha256=g7BC_H1qyg8Q1o5F76Vrm8ChSWYI5-dyj-CdGlNKBUo,2484
988
- ansible_core-2.18.0rc1.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
989
- ansible_core-2.18.0rc1.dist-info/entry_points.txt,sha256=S9yJij5Im6FgRQxzkqSCnPQokC7PcWrDW_NSygZczJU,451
990
- ansible_core-2.18.0rc1.dist-info/simplified_bsd.txt,sha256=8R5R7R7sOa0h1Fi6RNgFgHowHBfun-OVOMzJ4rKAk2w,1237
991
- ansible_core-2.18.0rc1.dist-info/top_level.txt,sha256=IFbRLjAvih1DYzJWg3_F6t4sCzEMxRO7TOMNs6GkYHo,21
992
- ansible_core-2.18.0rc1.dist-info/RECORD,,
983
+ ansible_core-2.18.1rc1.dist-info/Apache-License.txt,sha256=y16Ofl9KOYjhBjwULGDcLfdWBfTEZRXnduOspt-XbhQ,11325
984
+ ansible_core-2.18.1rc1.dist-info/COPYING,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
985
+ ansible_core-2.18.1rc1.dist-info/METADATA,sha256=xerWczD5k1pQDFa0-MXY3saZd-FsBLqEmJDkPzkktsM,7671
986
+ ansible_core-2.18.1rc1.dist-info/MIT-license.txt,sha256=jLXp2XurnyZKbye40g9tfmLGtVlxh3pPD4n8xNqX8xc,1023
987
+ ansible_core-2.18.1rc1.dist-info/PSF-license.txt,sha256=g7BC_H1qyg8Q1o5F76Vrm8ChSWYI5-dyj-CdGlNKBUo,2484
988
+ ansible_core-2.18.1rc1.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
989
+ ansible_core-2.18.1rc1.dist-info/entry_points.txt,sha256=S9yJij5Im6FgRQxzkqSCnPQokC7PcWrDW_NSygZczJU,451
990
+ ansible_core-2.18.1rc1.dist-info/simplified_bsd.txt,sha256=8R5R7R7sOa0h1Fi6RNgFgHowHBfun-OVOMzJ4rKAk2w,1237
991
+ ansible_core-2.18.1rc1.dist-info/top_level.txt,sha256=IFbRLjAvih1DYzJWg3_F6t4sCzEMxRO7TOMNs6GkYHo,21
992
+ ansible_core-2.18.1rc1.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.1.0)
2
+ Generator: setuptools (75.6.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -292,10 +292,13 @@ def get_docker_preferred_network_name(args: EnvironmentConfig) -> t.Optional[str
292
292
  current_container_id = get_docker_container_id()
293
293
 
294
294
  if current_container_id:
295
- # Make sure any additional containers we launch use the same network as the current container we're running in.
296
- # This is needed when ansible-test is running in a container that is not connected to Docker's default network.
297
- container = docker_inspect(args, current_container_id, always=True)
298
- network = container.get_network_name()
295
+ try:
296
+ # Make sure any additional containers we launch use the same network as the current container we're running in.
297
+ # This is needed when ansible-test is running in a container that is not connected to Docker's default network.
298
+ container = docker_inspect(args, current_container_id, always=True)
299
+ network = container.get_network_name()
300
+ except ContainerNotFoundError:
301
+ display.warning('Unable to detect the network for the current container. Use the `--docker-network` option if containers are unreachable.')
299
302
 
300
303
  # The default docker behavior puts containers on the same network.
301
304
  # The default podman behavior puts containers on isolated networks which don't allow communication between containers or network disconnect.
@@ -1014,15 +1014,15 @@ class HostConnectionError(ApplicationError):
1014
1014
  self._callback()
1015
1015
 
1016
1016
 
1017
- def format_command_output(stdout: str, stderr: str) -> str:
1017
+ def format_command_output(stdout: str | None, stderr: str | None) -> str:
1018
1018
  """Return a formatted string containing the given stdout and stderr (if any)."""
1019
1019
  message = ''
1020
1020
 
1021
- if stderr := stderr.strip():
1021
+ if stderr and (stderr := stderr.strip()):
1022
1022
  message += '>>> Standard Error\n'
1023
1023
  message += f'{stderr}{Display.clear}\n'
1024
1024
 
1025
- if stdout := stdout.strip():
1025
+ if stdout and (stdout := stdout.strip()):
1026
1026
  message += '>>> Standard Output\n'
1027
1027
  message += f'{stdout}{Display.clear}\n'
1028
1028