ansible-core 2.16.5rc1__py3-none-any.whl → 2.16.7__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 (40) hide show
  1. ansible/cli/config.py +5 -3
  2. ansible/cli/inventory.py +1 -21
  3. ansible/config/manager.py +12 -11
  4. ansible/constants.py +40 -0
  5. ansible/executor/play_iterator.py +1 -1
  6. ansible/executor/task_executor.py +8 -2
  7. ansible/galaxy/role.py +10 -12
  8. ansible/module_utils/ansible_release.py +1 -1
  9. ansible/module_utils/facts/virtual/linux.py +1 -1
  10. ansible/modules/blockinfile.py +1 -1
  11. ansible/modules/dnf.py +25 -143
  12. ansible/modules/dnf5.py +42 -21
  13. ansible/modules/find.py +7 -4
  14. ansible/modules/unarchive.py +1 -1
  15. ansible/modules/uri.py +8 -7
  16. ansible/modules/user.py +0 -6
  17. ansible/parsing/mod_args.py +8 -4
  18. ansible/playbook/role/__init__.py +1 -1
  19. ansible/plugins/action/__init__.py +1 -1
  20. ansible/plugins/action/fetch.py +4 -0
  21. ansible/plugins/cache/__init__.py +1 -0
  22. ansible/plugins/connection/psrp.py +1 -1
  23. ansible/plugins/connection/winrm.py +14 -2
  24. ansible/plugins/lookup/url.py +9 -1
  25. ansible/plugins/strategy/free.py +1 -1
  26. ansible/plugins/strategy/linear.py +1 -1
  27. ansible/release.py +1 -1
  28. ansible/template/__init__.py +10 -10
  29. ansible/vars/hostvars.py +6 -22
  30. {ansible_core-2.16.5rc1.dist-info → ansible_core-2.16.7.dist-info}/METADATA +1 -1
  31. {ansible_core-2.16.5rc1.dist-info → ansible_core-2.16.7.dist-info}/RECORD +40 -40
  32. ansible_test/_data/requirements/constraints.txt +1 -0
  33. ansible_test/_internal/commands/sanity/ansible_doc.py +1 -1
  34. ansible_test/_internal/pypi_proxy.py +8 -1
  35. ansible_test/_util/target/setup/bootstrap.sh +9 -0
  36. {ansible_core-2.16.5rc1.data → ansible_core-2.16.7.data}/scripts/ansible-test +0 -0
  37. {ansible_core-2.16.5rc1.dist-info → ansible_core-2.16.7.dist-info}/COPYING +0 -0
  38. {ansible_core-2.16.5rc1.dist-info → ansible_core-2.16.7.dist-info}/WHEEL +0 -0
  39. {ansible_core-2.16.5rc1.dist-info → ansible_core-2.16.7.dist-info}/entry_points.txt +0 -0
  40. {ansible_core-2.16.5rc1.dist-info → ansible_core-2.16.7.dist-info}/top_level.txt +0 -0
ansible/cli/config.py CHANGED
@@ -270,7 +270,7 @@ class ConfigCLI(CLI):
270
270
  if not settings[setting].get('description'):
271
271
  continue
272
272
 
273
- default = settings[setting].get('default', '')
273
+ default = self.config.template_default(settings[setting].get('default', ''), get_constants())
274
274
  if subkey == 'env':
275
275
  stype = settings[setting].get('type', '')
276
276
  if stype == 'boolean':
@@ -352,7 +352,7 @@ class ConfigCLI(CLI):
352
352
  if entry['key'] not in seen[entry['section']]:
353
353
  seen[entry['section']].append(entry['key'])
354
354
 
355
- default = opt.get('default', '')
355
+ default = self.config.template_default(opt.get('default', ''), get_constants())
356
356
  if opt.get('type', '') == 'list' and not isinstance(default, string_types):
357
357
  # python lists are not valid ini ones
358
358
  default = ', '.join(default)
@@ -414,14 +414,16 @@ class ConfigCLI(CLI):
414
414
  if context.CLIARGS['format'] == 'display':
415
415
  if isinstance(config[setting], Setting):
416
416
  # proceed normally
417
+ value = config[setting].value
417
418
  if config[setting].origin == 'default':
418
419
  color = 'green'
420
+ value = self.config.template_default(value, get_constants())
419
421
  elif config[setting].origin == 'REQUIRED':
420
422
  # should include '_terms', '_input', etc
421
423
  color = 'red'
422
424
  else:
423
425
  color = 'yellow'
424
- msg = "%s(%s) = %s" % (setting, config[setting].origin, config[setting].value)
426
+ msg = "%s(%s) = %s" % (setting, config[setting].origin, value)
425
427
  else:
426
428
  color = 'green'
427
429
  msg = "%s(%s) = %s" % (setting, 'default', config[setting].get('default'))
ansible/cli/inventory.py CHANGED
@@ -25,26 +25,6 @@ from ansible.vars.plugins import get_vars_from_inventory_sources, get_vars_from_
25
25
 
26
26
  display = Display()
27
27
 
28
- INTERNAL_VARS = frozenset(['ansible_diff_mode',
29
- 'ansible_config_file',
30
- 'ansible_facts',
31
- 'ansible_forks',
32
- 'ansible_inventory_sources',
33
- 'ansible_limit',
34
- 'ansible_playbook_python',
35
- 'ansible_run_tags',
36
- 'ansible_skip_tags',
37
- 'ansible_verbosity',
38
- 'ansible_version',
39
- 'inventory_dir',
40
- 'inventory_file',
41
- 'inventory_hostname',
42
- 'inventory_hostname_short',
43
- 'groups',
44
- 'group_names',
45
- 'omit',
46
- 'playbook_dir', ])
47
-
48
28
 
49
29
  class InventoryCLI(CLI):
50
30
  ''' used to display or dump the configured inventory as Ansible sees it '''
@@ -247,7 +227,7 @@ class InventoryCLI(CLI):
247
227
  @staticmethod
248
228
  def _remove_internal(dump):
249
229
 
250
- for internal in INTERNAL_VARS:
230
+ for internal in C.INTERNAL_STATIC_VARS:
251
231
  if internal in dump:
252
232
  del dump[internal]
253
233
 
ansible/config/manager.py CHANGED
@@ -305,6 +305,17 @@ class ConfigManager(object):
305
305
  # ensure we always have config def entry
306
306
  self._base_defs['CONFIG_FILE'] = {'default': None, 'type': 'path'}
307
307
 
308
+ def template_default(self, value, variables):
309
+ if isinstance(value, string_types) and (value.startswith('{{') and value.endswith('}}')) and variables is not None:
310
+ # template default values if possible
311
+ # NOTE: cannot use is_template due to circular dep
312
+ try:
313
+ t = NativeEnvironment().from_string(value)
314
+ value = t.render(variables)
315
+ except Exception:
316
+ pass # not templatable
317
+ return value
318
+
308
319
  def _read_config_yaml_file(self, yml_file):
309
320
  # TODO: handle relative paths as relative to the directory containing the current playbook instead of CWD
310
321
  # Currently this is only used with absolute paths to the `ansible/config` directory
@@ -548,17 +559,7 @@ class ConfigManager(object):
548
559
  to_native(_get_entry(plugin_type, plugin_name, config)))
549
560
  else:
550
561
  origin = 'default'
551
- value = defs[config].get('default')
552
- if isinstance(value, string_types) and (value.startswith('{{') and value.endswith('}}')) and variables is not None:
553
- # template default values if possible
554
- # NOTE: cannot use is_template due to circular dep
555
- try:
556
- t = NativeEnvironment().from_string(value)
557
- value = t.render(variables)
558
- except Exception:
559
- pass # not templatable
560
-
561
- # ensure correct type, can raise exceptions on mismatched types
562
+ value = self.template_default(defs[config].get('default'), variables)
562
563
  try:
563
564
  value = ensure_type(value, defs[config].get('type'), origin=origin)
564
565
  except ValueError as e:
ansible/constants.py CHANGED
@@ -112,6 +112,46 @@ CONFIGURABLE_PLUGINS = ('become', 'cache', 'callback', 'cliconf', 'connection',
112
112
  DOCUMENTABLE_PLUGINS = CONFIGURABLE_PLUGINS + ('module', 'strategy', 'test', 'filter')
113
113
  IGNORE_FILES = ("COPYING", "CONTRIBUTING", "LICENSE", "README", "VERSION", "GUIDELINES", "MANIFEST", "Makefile") # ignore during module search
114
114
  INTERNAL_RESULT_KEYS = ('add_host', 'add_group')
115
+ INTERNAL_STATIC_VARS = frozenset(
116
+ [
117
+ "ansible_async_path",
118
+ "ansible_collection_name",
119
+ "ansible_config_file",
120
+ "ansible_dependent_role_names",
121
+ "ansible_diff_mode",
122
+ "ansible_config_file",
123
+ "ansible_facts",
124
+ "ansible_forks",
125
+ "ansible_inventory_sources",
126
+ "ansible_limit",
127
+ "ansible_play_batch",
128
+ "ansible_play_hosts",
129
+ "ansible_play_hosts_all",
130
+ "ansible_play_role_names",
131
+ "ansible_playbook_python",
132
+ "ansible_role_name",
133
+ "ansible_role_names",
134
+ "ansible_run_tags",
135
+ "ansible_skip_tags",
136
+ "ansible_verbosity",
137
+ "ansible_version",
138
+ "inventory_dir",
139
+ "inventory_file",
140
+ "inventory_hostname",
141
+ "inventory_hostname_short",
142
+ "groups",
143
+ "group_names",
144
+ "omit",
145
+ "hostvars",
146
+ "playbook_dir",
147
+ "play_hosts",
148
+ "role_name",
149
+ "role_names",
150
+ "role_path",
151
+ "role_uuid",
152
+ "role_names",
153
+ ]
154
+ )
115
155
  LOCALHOST = ('127.0.0.1', 'localhost', '::1')
116
156
  MODULE_REQUIRE_ARGS = tuple(add_internal_fqcns(('command', 'win_command', 'ansible.windows.win_command', 'shell', 'win_shell',
117
157
  'ansible.windows.win_shell', 'raw', 'script')))
@@ -429,13 +429,13 @@ class PlayIterator:
429
429
  # might be there from previous flush
430
430
  state.handlers = self.handlers[:]
431
431
  state.update_handlers = False
432
- state.cur_handlers_task = 0
433
432
 
434
433
  while True:
435
434
  try:
436
435
  task = state.handlers[state.cur_handlers_task]
437
436
  except IndexError:
438
437
  task = None
438
+ state.cur_handlers_task = 0
439
439
  state.run_state = state.pre_flushing_run_state
440
440
  state.update_handlers = True
441
441
  break
@@ -841,7 +841,12 @@ class TaskExecutor:
841
841
  # that (with a sleep for "poll" seconds between each retry) until the
842
842
  # async time limit is exceeded.
843
843
 
844
- async_task = Task.load(dict(action='async_status', args={'jid': async_jid}, environment=self._task.environment))
844
+ async_task = Task.load(dict(
845
+ action='async_status',
846
+ args={'jid': async_jid},
847
+ check_mode=self._task.check_mode,
848
+ environment=self._task.environment,
849
+ ))
845
850
 
846
851
  # FIXME: this is no longer the case, normal takes care of all, see if this can just be generalized
847
852
  # Because this is an async task, the action handler is async. However,
@@ -913,6 +918,7 @@ class TaskExecutor:
913
918
  'jid': async_jid,
914
919
  'mode': 'cleanup',
915
920
  },
921
+ 'check_mode': self._task.check_mode,
916
922
  'environment': self._task.environment,
917
923
  }
918
924
  )
@@ -1086,7 +1092,7 @@ class TaskExecutor:
1086
1092
 
1087
1093
  # deals with networking sub_plugins (network_cli/httpapi/netconf)
1088
1094
  sub = getattr(self._connection, '_sub_plugin', None)
1089
- if sub is not None and sub.get('type') != 'external':
1095
+ if sub and sub.get('type') != 'external':
1090
1096
  plugin_type = get_plugin_class(sub.get("obj"))
1091
1097
  varnames.extend(self._set_plugin_options(plugin_type, variables, templar, task_keys))
1092
1098
  sub_conn = getattr(self._connection, 'ssh_type_conn', None)
ansible/galaxy/role.py CHANGED
@@ -387,6 +387,8 @@ class GalaxyRole(object):
387
387
  else:
388
388
  os.makedirs(self.path)
389
389
 
390
+ resolved_archive = unfrackpath(archive_parent_dir, follow=False)
391
+
390
392
  # We strip off any higher-level directories for all of the files
391
393
  # contained within the tar file here. The default is 'github_repo-target'.
392
394
  # Gerrit instances, on the other hand, does not have a parent directory at all.
@@ -401,33 +403,29 @@ class GalaxyRole(object):
401
403
  if not (attr_value := getattr(member, attr, None)):
402
404
  continue
403
405
 
404
- if attr_value.startswith(os.sep) and not is_subpath(attr_value, archive_parent_dir):
405
- err = f"Invalid {attr} for tarfile member: path {attr_value} is not a subpath of the role {archive_parent_dir}"
406
- raise AnsibleError(err)
407
-
408
406
  if attr == 'linkname':
409
407
  # Symlinks are relative to the link
410
- relative_to_archive_dir = os.path.dirname(getattr(member, 'name', ''))
411
- archive_dir_path = os.path.join(archive_parent_dir, relative_to_archive_dir, attr_value)
408
+ relative_to = os.path.dirname(getattr(member, 'name', ''))
412
409
  else:
413
410
  # Normalize paths that start with the archive dir
414
411
  attr_value = attr_value.replace(archive_parent_dir, "", 1)
415
412
  attr_value = os.path.join(*attr_value.split(os.sep)) # remove leading os.sep
416
- archive_dir_path = os.path.join(archive_parent_dir, attr_value)
413
+ relative_to = ''
417
414
 
418
- resolved_archive = unfrackpath(archive_parent_dir)
419
- resolved_path = unfrackpath(archive_dir_path)
420
- if not is_subpath(resolved_path, resolved_archive):
421
- err = f"Invalid {attr} for tarfile member: path {resolved_path} is not a subpath of the role {resolved_archive}"
415
+ full_path = os.path.join(resolved_archive, relative_to, attr_value)
416
+ if not is_subpath(full_path, resolved_archive, real=True):
417
+ err = f"Invalid {attr} for tarfile member: path {full_path} is not a subpath of the role {resolved_archive}"
422
418
  raise AnsibleError(err)
423
419
 
424
- relative_path = os.path.join(*resolved_path.replace(resolved_archive, "", 1).split(os.sep)) or '.'
420
+ relative_path_dir = os.path.join(resolved_archive, relative_to)
421
+ relative_path = os.path.join(*full_path.replace(relative_path_dir, "", 1).split(os.sep))
425
422
  setattr(member, attr, relative_path)
426
423
 
427
424
  if _check_working_data_filter():
428
425
  # deprecated: description='extract fallback without filter' python_version='3.11'
429
426
  role_tar_file.extract(member, to_native(self.path), filter='data') # type: ignore[call-arg]
430
427
  else:
428
+ # Remove along with manual path filter once Python 3.12 is minimum supported version
431
429
  role_tar_file.extract(member, to_native(self.path))
432
430
 
433
431
  # write out the install info file for later use
@@ -19,6 +19,6 @@
19
19
  from __future__ import (absolute_import, division, print_function)
20
20
  __metaclass__ = type
21
21
 
22
- __version__ = '2.16.5rc1'
22
+ __version__ = '2.16.7'
23
23
  __author__ = 'Ansible, Inc.'
24
24
  __codename__ = "All My Love"
@@ -176,7 +176,7 @@ class LinuxVirtual(Virtual):
176
176
  virtual_facts['virtualization_type'] = 'RHEV'
177
177
  found_virt = True
178
178
 
179
- if product_name in ('VMware Virtual Platform', 'VMware7,1'):
179
+ if product_name and product_name.startswith(("VMware",)):
180
180
  guest_tech.add('VMware')
181
181
  if not found_virt:
182
182
  virtual_facts['virtualization_type'] = 'VMware'
@@ -269,7 +269,7 @@ def main():
269
269
  module.fail_json(rc=257,
270
270
  msg='Path %s does not exist !' % path)
271
271
  destpath = os.path.dirname(path)
272
- if not os.path.exists(destpath) and not module.check_mode:
272
+ if destpath and not os.path.exists(destpath) and not module.check_mode:
273
273
  try:
274
274
  os.makedirs(destpath)
275
275
  except OSError as e:
ansible/modules/dnf.py CHANGED
@@ -380,7 +380,6 @@ EXAMPLES = '''
380
380
  '''
381
381
 
382
382
  import os
383
- import re
384
383
  import sys
385
384
 
386
385
  from ansible.module_utils.common.text.converters import to_native, to_text
@@ -482,94 +481,6 @@ class DnfModule(YumDnf):
482
481
 
483
482
  return result
484
483
 
485
- def _split_package_arch(self, packagename):
486
- # This list was auto generated on a Fedora 28 system with the following one-liner
487
- # printf '[ '; for arch in $(ls /usr/lib/rpm/platform); do printf '"%s", ' ${arch%-linux}; done; printf ']\n'
488
- redhat_rpm_arches = [
489
- "aarch64", "alphaev56", "alphaev5", "alphaev67", "alphaev6", "alpha",
490
- "alphapca56", "amd64", "armv3l", "armv4b", "armv4l", "armv5tejl", "armv5tel",
491
- "armv5tl", "armv6hl", "armv6l", "armv7hl", "armv7hnl", "armv7l", "athlon",
492
- "geode", "i386", "i486", "i586", "i686", "ia32e", "ia64", "m68k", "mips64el",
493
- "mips64", "mips64r6el", "mips64r6", "mipsel", "mips", "mipsr6el", "mipsr6",
494
- "noarch", "pentium3", "pentium4", "ppc32dy4", "ppc64iseries", "ppc64le", "ppc64",
495
- "ppc64p7", "ppc64pseries", "ppc8260", "ppc8560", "ppciseries", "ppc", "ppcpseries",
496
- "riscv64", "s390", "s390x", "sh3", "sh4a", "sh4", "sh", "sparc64", "sparc64v",
497
- "sparc", "sparcv8", "sparcv9", "sparcv9v", "x86_64"
498
- ]
499
-
500
- name, delimiter, arch = packagename.rpartition('.')
501
- if name and arch and arch in redhat_rpm_arches:
502
- return name, arch
503
- return packagename, None
504
-
505
- def _packagename_dict(self, packagename):
506
- """
507
- Return a dictionary of information for a package name string or None
508
- if the package name doesn't contain at least all NVR elements
509
- """
510
-
511
- if packagename[-4:] == '.rpm':
512
- packagename = packagename[:-4]
513
-
514
- rpm_nevr_re = re.compile(r'(\S+)-(?:(\d*):)?(.*)-(~?\w+[\w.+]*)')
515
- try:
516
- arch = None
517
- nevr, arch = self._split_package_arch(packagename)
518
- if arch:
519
- packagename = nevr
520
- rpm_nevr_match = rpm_nevr_re.match(packagename)
521
- if rpm_nevr_match:
522
- name, epoch, version, release = rpm_nevr_re.match(packagename).groups()
523
- if not version or not version.split('.')[0].isdigit():
524
- return None
525
- else:
526
- return None
527
- except AttributeError as e:
528
- self.module.fail_json(
529
- msg='Error attempting to parse package: %s, %s' % (packagename, to_native(e)),
530
- rc=1,
531
- results=[]
532
- )
533
-
534
- if not epoch:
535
- epoch = "0"
536
-
537
- if ':' in name:
538
- epoch_name = name.split(":")
539
-
540
- epoch = epoch_name[0]
541
- name = ''.join(epoch_name[1:])
542
-
543
- result = {
544
- 'name': name,
545
- 'epoch': epoch,
546
- 'release': release,
547
- 'version': version,
548
- }
549
-
550
- return result
551
-
552
- # Original implementation from yum.rpmUtils.miscutils (GPLv2+)
553
- # http://yum.baseurl.org/gitweb?p=yum.git;a=blob;f=rpmUtils/miscutils.py
554
- def _compare_evr(self, e1, v1, r1, e2, v2, r2):
555
- # return 1: a is newer than b
556
- # 0: a and b are the same version
557
- # -1: b is newer than a
558
- if e1 is None:
559
- e1 = '0'
560
- else:
561
- e1 = str(e1)
562
- v1 = str(v1)
563
- r1 = str(r1)
564
- if e2 is None:
565
- e2 = '0'
566
- else:
567
- e2 = str(e2)
568
- v2 = str(v2)
569
- r2 = str(r2)
570
- rc = dnf.rpm.rpm.labelCompare((e1, v1, r1), (e2, v2, r2))
571
- return rc
572
-
573
484
  def _ensure_dnf(self):
574
485
  locale = get_best_parsable_locale(self.module)
575
486
  os.environ['LC_ALL'] = os.environ['LC_MESSAGES'] = locale
@@ -578,7 +489,6 @@ class DnfModule(YumDnf):
578
489
  global dnf
579
490
  try:
580
491
  import dnf
581
- import dnf.cli
582
492
  import dnf.const
583
493
  import dnf.exceptions
584
494
  import dnf.package
@@ -809,43 +719,22 @@ class DnfModule(YumDnf):
809
719
  self.module.exit_json(msg="", results=results)
810
720
 
811
721
  def _is_installed(self, pkg):
812
- installed = self.base.sack.query().installed()
813
-
814
- package_spec = {}
815
- name, arch = self._split_package_arch(pkg)
816
- if arch:
817
- package_spec['arch'] = arch
818
-
819
- package_details = self._packagename_dict(pkg)
820
- if package_details:
821
- package_details['epoch'] = int(package_details['epoch'])
822
- package_spec.update(package_details)
823
- else:
824
- package_spec['name'] = name
825
-
826
- return bool(installed.filter(**package_spec))
722
+ return bool(
723
+ dnf.subject.Subject(pkg).get_best_query(sack=self.base.sack).installed().run()
724
+ )
827
725
 
828
726
  def _is_newer_version_installed(self, pkg_name):
829
- candidate_pkg = self._packagename_dict(pkg_name)
830
- if not candidate_pkg:
831
- # The user didn't provide a versioned rpm, so version checking is
832
- # not required
833
- return False
834
-
835
- installed = self.base.sack.query().installed()
836
- installed_pkg = installed.filter(name=candidate_pkg['name']).run()
837
- if installed_pkg:
838
- installed_pkg = installed_pkg[0]
839
-
840
- # this looks weird but one is a dict and the other is a dnf.Package
841
- evr_cmp = self._compare_evr(
842
- installed_pkg.epoch, installed_pkg.version, installed_pkg.release,
843
- candidate_pkg['epoch'], candidate_pkg['version'], candidate_pkg['release'],
844
- )
845
-
846
- return evr_cmp == 1
847
- else:
727
+ try:
728
+ if isinstance(pkg_name, dnf.package.Package):
729
+ available = pkg_name
730
+ else:
731
+ available = sorted(
732
+ dnf.subject.Subject(pkg_name).get_best_query(sack=self.base.sack).available().run()
733
+ )[-1]
734
+ installed = sorted(self.base.sack.query().installed().filter(name=available.name).run())[-1]
735
+ except IndexError:
848
736
  return False
737
+ return installed > available
849
738
 
850
739
  def _mark_package_install(self, pkg_spec, upgrade=False):
851
740
  """Mark the package for install."""
@@ -917,17 +806,6 @@ class DnfModule(YumDnf):
917
806
  "results": []
918
807
  }
919
808
 
920
- def _whatprovides(self, filepath):
921
- self.base.read_all_repos()
922
- available = self.base.sack.query().available()
923
- # Search in file
924
- files_filter = available.filter(file=filepath)
925
- # And Search in provides
926
- pkg_spec = files_filter.union(available.filter(provides=filepath)).run()
927
-
928
- if pkg_spec:
929
- return pkg_spec[0].name
930
-
931
809
  def _parse_spec_group_file(self):
932
810
  pkg_specs, grp_specs, module_specs, filenames = [], [], [], []
933
811
  already_loaded_comps = False # Only load this if necessary, it's slow
@@ -939,11 +817,13 @@ class DnfModule(YumDnf):
939
817
  elif name.endswith(".rpm"):
940
818
  filenames.append(name)
941
819
  elif name.startswith('/'):
942
- # like "dnf install /usr/bin/vi"
943
- pkg_spec = self._whatprovides(name)
944
- if pkg_spec:
945
- pkg_specs.append(pkg_spec)
946
- continue
820
+ # dnf install /usr/bin/vi
821
+ installed = self.base.sack.query().filter(provides=name, file=name).installed().run()
822
+ if installed:
823
+ pkg_specs.append(installed[0].name) # should be only one?
824
+ elif not self.update_only:
825
+ # not installed, pass the filename for dnf to process
826
+ pkg_specs.append(name)
947
827
  elif name.startswith("@") or ('/' in name):
948
828
  if not already_loaded_comps:
949
829
  self.base.read_comps()
@@ -1005,7 +885,7 @@ class DnfModule(YumDnf):
1005
885
  else:
1006
886
  for pkg in pkgs:
1007
887
  try:
1008
- if self._is_newer_version_installed(self._package_dict(pkg)['nevra']):
888
+ if self._is_newer_version_installed(pkg):
1009
889
  if self.allow_downgrade:
1010
890
  self.base.package_install(pkg, strict=self.base.conf.strict)
1011
891
  else:
@@ -1441,8 +1321,10 @@ class DnfModule(YumDnf):
1441
1321
 
1442
1322
  if self.with_modules:
1443
1323
  self.module_base = dnf.module.module_base.ModuleBase(self.base)
1444
-
1445
- self.ensure()
1324
+ try:
1325
+ self.ensure()
1326
+ finally:
1327
+ self.base.close()
1446
1328
 
1447
1329
 
1448
1330
  def main():
ansible/modules/dnf5.py CHANGED
@@ -361,19 +361,37 @@ def is_newer_version_installed(base, spec):
361
361
  spec_nevra = next(iter(libdnf5.rpm.Nevra.parse(spec)))
362
362
  except RuntimeError:
363
363
  return False
364
- spec_name = spec_nevra.get_name()
365
- v = spec_nevra.get_version()
366
- r = spec_nevra.get_release()
367
- if not v or not r:
364
+
365
+ spec_version = spec_nevra.get_version()
366
+ if not spec_version:
368
367
  return False
369
- spec_evr = "{}:{}-{}".format(spec_nevra.get_epoch() or "0", v, r)
370
368
 
371
- query = libdnf5.rpm.PackageQuery(base)
372
- query.filter_installed()
373
- query.filter_name([spec_name])
374
- query.filter_evr([spec_evr], libdnf5.common.QueryCmp_GT)
369
+ installed = libdnf5.rpm.PackageQuery(base)
370
+ installed.filter_installed()
371
+ installed.filter_name([spec_nevra.get_name()])
372
+ installed.filter_latest_evr()
373
+ try:
374
+ installed_package = list(installed)[-1]
375
+ except IndexError:
376
+ return False
375
377
 
376
- return query.size() > 0
378
+ target = libdnf5.rpm.PackageQuery(base)
379
+ target.filter_name([spec_nevra.get_name()])
380
+ target.filter_version([spec_version])
381
+ spec_release = spec_nevra.get_release()
382
+ if spec_release:
383
+ target.filter_release([spec_release])
384
+ spec_epoch = spec_nevra.get_epoch()
385
+ if spec_epoch:
386
+ target.filter_epoch([spec_epoch])
387
+ target.filter_latest_evr()
388
+ try:
389
+ target_package = list(target)[-1]
390
+ except IndexError:
391
+ return False
392
+
393
+ # FIXME https://github.com/rpm-software-management/dnf5/issues/1104
394
+ return libdnf5.rpm.rpmvercmp(installed_package.get_evr(), target_package.get_evr()) == 1
377
395
 
378
396
 
379
397
  def package_to_dict(package):
@@ -484,7 +502,7 @@ class Dnf5Module(YumDnf):
484
502
  conf.config_file_path = self.conf_file
485
503
 
486
504
  try:
487
- base.load_config_from_file()
505
+ base.load_config()
488
506
  except RuntimeError as e:
489
507
  self.module.fail_json(
490
508
  msg=str(e),
@@ -520,7 +538,8 @@ class Dnf5Module(YumDnf):
520
538
  log_router = base.get_logger()
521
539
  global_logger = libdnf5.logger.GlobalLogger()
522
540
  global_logger.set(log_router.get(), libdnf5.logger.Logger.Level_DEBUG)
523
- logger = libdnf5.logger.create_file_logger(base)
541
+ # FIXME hardcoding the filename does not seem right, should libdnf5 expose the default file name?
542
+ logger = libdnf5.logger.create_file_logger(base, "dnf5.log")
524
543
  log_router.add_logger(logger)
525
544
 
526
545
  if self.update_cache:
@@ -545,7 +564,11 @@ class Dnf5Module(YumDnf):
545
564
  for repo in repo_query:
546
565
  repo.enable()
547
566
 
548
- sack.update_and_load_enabled_repos(True)
567
+ try:
568
+ sack.load_repos()
569
+ except AttributeError:
570
+ # dnf5 < 5.2.0.0
571
+ sack.update_and_load_enabled_repos(True)
549
572
 
550
573
  if self.update_cache and not self.names and not self.list:
551
574
  self.module.exit_json(
@@ -577,7 +600,11 @@ class Dnf5Module(YumDnf):
577
600
  self.module.exit_json(msg="", results=results, rc=0)
578
601
 
579
602
  settings = libdnf5.base.GoalJobSettings()
580
- settings.group_with_name = True
603
+ try:
604
+ settings.set_group_with_name(True)
605
+ except AttributeError:
606
+ # dnf5 < 5.2.0.0
607
+ settings.group_with_name = True
581
608
  if self.bugfix or self.security:
582
609
  advisory_query = libdnf5.advisory.AdvisoryQuery(base)
583
610
  types = []
@@ -597,13 +624,7 @@ class Dnf5Module(YumDnf):
597
624
  for spec in self.names:
598
625
  if is_newer_version_installed(base, spec):
599
626
  if self.allow_downgrade:
600
- if upgrade:
601
- if is_installed(base, spec):
602
- goal.add_upgrade(spec, settings)
603
- else:
604
- goal.add_install(spec, settings)
605
- else:
606
- goal.add_install(spec, settings)
627
+ goal.add_install(spec, settings)
607
628
  elif is_installed(base, spec):
608
629
  if upgrade:
609
630
  goal.add_upgrade(spec, settings)
ansible/modules/find.py CHANGED
@@ -258,6 +258,7 @@ skipped_paths:
258
258
  version_added: '2.12'
259
259
  '''
260
260
 
261
+ import errno
261
262
  import fnmatch
262
263
  import grp
263
264
  import os
@@ -434,10 +435,6 @@ def statinfo(st):
434
435
  }
435
436
 
436
437
 
437
- def handle_walk_errors(e):
438
- raise e
439
-
440
-
441
438
  def main():
442
439
  module = AnsibleModule(
443
440
  argument_spec=dict(
@@ -482,6 +479,12 @@ def main():
482
479
  filelist = []
483
480
  skipped = {}
484
481
 
482
+ def handle_walk_errors(e):
483
+ if e.errno in (errno.EPERM, errno.EACCES):
484
+ skipped[e.filename] = to_text(e)
485
+ return
486
+ raise e
487
+
485
488
  if params['age'] is None:
486
489
  age = None
487
490
  else:
@@ -969,7 +969,7 @@ class TarZstdArchive(TgzArchive):
969
969
  class ZipZArchive(ZipArchive):
970
970
  def __init__(self, src, b_dest, file_args, module):
971
971
  super(ZipZArchive, self).__init__(src, b_dest, file_args, module)
972
- self.zipinfoflag = '-Z'
972
+ self.zipinfoflag = '-Zl'
973
973
  self.binaries = (
974
974
  ('unzip', 'cmd_path'),
975
975
  ('unzip', 'zipinfo_cmd_path'),