| |
@@ -0,0 +1,237 @@
|
| |
+ import optparse
|
| |
+
|
| |
+ import koji
|
| |
+ from koji_cli.lib import activate_session
|
| |
+
|
| |
+
|
| |
+ class PackageFilterManager(object):
|
| |
+ def __init__(self, koji_profile):
|
| |
+ self.koji_profile = koji_profile
|
| |
+ self._koji_session = None
|
| |
+ self._koji_configuration = None
|
| |
+ self._koji_options = None
|
| |
+
|
| |
+ @property
|
| |
+ def koji_configuration(self):
|
| |
+ if self._koji_configuration:
|
| |
+ return self._koji_configuration
|
| |
+ try:
|
| |
+ self._koji_configuration = koji.read_config(self.koji_profile)
|
| |
+ except koji.ConfigurationError, e:
|
| |
+ raise RuntimeError("Failed to read koji configuration: {}".format(e))
|
| |
+ assert False # pragma: no cover
|
| |
+
|
| |
+ if not self._koji_configuration:
|
| |
+ raise RuntimeError("Configuration is empty: '{}'".repr(self._koji_configuration))
|
| |
+
|
| |
+ return self._koji_configuration
|
| |
+
|
| |
+ def _extend_koji_options(self):
|
| |
+ """These options are present only in koji CLI but are required by
|
| |
+ activate_session"""
|
| |
+ additional_koji_cli_options = {
|
| |
+ 'noauth': False,
|
| |
+ 'user': None,
|
| |
+ 'runas': None,
|
| |
+ }
|
| |
+ for name, value in additional_koji_cli_options.items():
|
| |
+ if getattr(self._koji_options, name, None) is None:
|
| |
+ setattr(self._koji_options, name, value)
|
| |
+
|
| |
+ @property
|
| |
+ def koji_options(self):
|
| |
+ if self._koji_options:
|
| |
+ return self._koji_options
|
| |
+ self._koji_options = optparse.Values(self.koji_configuration)
|
| |
+ self._extend_koji_options()
|
| |
+
|
| |
+ if not self._koji_options:
|
| |
+ raise RuntimeError("Configuration is empty: '{}'".repr(self._koji_options))
|
| |
+
|
| |
+ return self._koji_options
|
| |
+
|
| |
+ @property
|
| |
+ def koji_session(self):
|
| |
+ if self._koji_session:
|
| |
+ return self._koji_session
|
| |
+
|
| |
+ server = self.koji_options.server
|
| |
+ session_opts = koji.grab_session_options(self.koji_options)
|
| |
+ self._koji_session = koji.ClientSession(server, session_opts)
|
| |
+ if not self._koji_session:
|
| |
+ raise RuntimeError("Failed to open koji session: '{}'".repr(self._koji_session))
|
| |
+ self.koji_activate_session()
|
| |
+
|
| |
+ return self._koji_session
|
| |
+
|
| |
+ def koji_activate_session(self):
|
| |
+ return activate_session(self.koji_session, self.koji_options)
|
| |
+
|
| |
+ def inheritance_data(self, tag):
|
| |
+ return self.koji_session.getInheritanceData(tag)
|
| |
+
|
| |
+ def tag_data(self, tag, parent_tag=None):
|
| |
+ parent = None
|
| |
+ if parent_tag:
|
| |
+ parent = self.koji_session.getTag(parent_tag)
|
| |
+ if not parent:
|
| |
+ raise ValueError("Invalid parent tag: %s".format(parent_tag))
|
| |
+
|
| |
+ all_data = data = self.inheritance_data(tag)
|
| |
+ if parent and all_data:
|
| |
+ data = [datum for datum in all_data if datum['parent_id'] == parent['id']]
|
| |
+
|
| |
+ if len(data) == 0:
|
| |
+ raise ValueError("No inheritance with specified arguments")
|
| |
+ elif len(data) > 1:
|
| |
+ parent_names = [t['name'] for t in data]
|
| |
+ if parent:
|
| |
+ # This shouldn't ever happen
|
| |
+ msg = (
|
| |
+ "Inheritance data returned multiple entries "
|
| |
+ "even for specified tag and parent."
|
| |
+ )
|
| |
+ raise RuntimeError(msg)
|
| |
+ assert False # noqa
|
| |
+
|
| |
+ msg = (
|
| |
+ "Tag has multiple parents: {}. "
|
| |
+ "Please specify a parent tag on the command line."
|
| |
+ )
|
| |
+ raise ValueError(msg.format(', '.join(parent_names)))
|
| |
+
|
| |
+ assert len(data) == 1
|
| |
+ return data[0]
|
| |
+
|
| |
+ def _apply_filter_changes(self, tag, old_data, inner_pkg_filter, dry_run,
|
| |
+ parent_tag=None):
|
| |
+ normalized_filter = normalize_filter(inner_pkg_filter)
|
| |
+
|
| |
+ old_pkg_filter = old_data.get('pkg_filter')
|
| |
+ if old_pkg_filter:
|
| |
+ old_inner_pkg_filter = parse_pkg_filter(old_pkg_filter)
|
| |
+ if old_inner_pkg_filter == list(normalized_filter):
|
| |
+ raise RuntimeError("Nothing has changed.")
|
| |
+
|
| |
+ all_data = self.inheritance_data(tag)
|
| |
+ # Get position of tag data which will be modified.
|
| |
+ # I'm not really sure if I ever get non-zero index here.
|
| |
+ index = all_data.index(old_data)
|
| |
+
|
| |
+ new_data = old_data.copy()
|
| |
+ new_all_data = list(all_data)
|
| |
+
|
| |
+ new_data['pkg_filter'] = construct_pkg_filter_regex(normalized_filter)
|
| |
+ new_all_data[index] = new_data
|
| |
+ self._print_and_action(tag, new_all_data, dry_run, parent_tag)
|
| |
+
|
| |
+ def _print_and_action(self, tag, new_data, dry_run, parent_tag):
|
| |
+ print_action(dry_run)
|
| |
+ print_cli_command(tag, new_data, self.koji_profile, parent_tag)
|
| |
+ if not dry_run:
|
| |
+ self.koji_session.setInheritanceData(tag, new_data)
|
| |
+
|
| |
+ def cmd_list(self, tag, parent_tag=None):
|
| |
+ data = self.tag_data(tag, parent_tag)
|
| |
+ pkg_filter = data.get('pkg_filter')
|
| |
+ if pkg_filter:
|
| |
+ return parse_pkg_filter(pkg_filter)
|
| |
+ return ()
|
| |
+
|
| |
+ def cmd_remove(self, tag, package, parent_tag=None, dry_run=False):
|
| |
+ data = self.tag_data(tag, parent_tag)
|
| |
+ pkg_filter = data.get('pkg_filter')
|
| |
+ if not pkg_filter:
|
| |
+ raise ValueError("Nothing to do. Filter is already empty.")
|
| |
+
|
| |
+ inner_pkg_filter = parse_pkg_filter(pkg_filter)
|
| |
+ if package not in inner_pkg_filter:
|
| |
+ raise ValueError("Nothing to do, is already missing from filter")
|
| |
+
|
| |
+ inner_pkg_filter.remove(package)
|
| |
+
|
| |
+ self._apply_filter_changes(tag, data, inner_pkg_filter, dry_run,
|
| |
+ parent_tag)
|
| |
+
|
| |
+ def cmd_add(self, tag, packages, parent_tag=None, dry_run=False):
|
| |
+ data = self.tag_data(tag, parent_tag)
|
| |
+ pkg_filter = data.get('pkg_filter')
|
| |
+ inner_pkg_filter = []
|
| |
+ if pkg_filter:
|
| |
+ inner_pkg_filter = parse_pkg_filter(pkg_filter)
|
| |
+ already_present = []
|
| |
+ to_be_added = []
|
| |
+ for package in packages:
|
| |
+ if package in inner_pkg_filter:
|
| |
+ already_present.append(package)
|
| |
+ else:
|
| |
+ to_be_added.append(package)
|
| |
+ #raise ValueError("Nothing to do, package already exists in filter")
|
| |
+ if already_present:
|
| |
+ print "These packages are already present: {}".format(','.join(list(set(already_present))))
|
| |
+
|
| |
+ inner_pkg_filter = inner_pkg_filter + to_be_added
|
| |
+
|
| |
+ self._apply_filter_changes(tag, data, inner_pkg_filter, dry_run,
|
| |
+ parent_tag)
|
| |
+
|
| |
+ def cmd_normalize(self, tag, parent_tag=None, dry_run=False):
|
| |
+ data = self.tag_data(tag, parent_tag)
|
| |
+ pkg_filter = data.get('pkg_filter')
|
| |
+ if not pkg_filter:
|
| |
+ return
|
| |
+ #raise ValueError("Something is wrong with package filter. It is empty.")
|
| |
+
|
| |
+ inner_pkg_filter = parse_pkg_filter(pkg_filter)
|
| |
+
|
| |
+ normalized_filter = normalize_filter(inner_pkg_filter)
|
| |
+ if inner_pkg_filter == list(normalized_filter):
|
| |
+ return
|
| |
+ #raise RuntimeError("Nothing to do. Package filter is already normalized")
|
| |
+
|
| |
+ self._apply_filter_changes(tag, data, inner_pkg_filter, dry_run,
|
| |
+ parent_tag)
|
| |
+
|
| |
+ def cmd_exists(self, tag, package, parent_tag=None):
|
| |
+ data = self.tag_data(tag, parent_tag)
|
| |
+ pkg_filter = data.get('pkg_filter')
|
| |
+ if not pkg_filter:
|
| |
+ raise ValueError("Something is wrong with package filter. It is empty.")
|
| |
+
|
| |
+ inner_pkg_filter = parse_pkg_filter(pkg_filter)
|
| |
+ return package in inner_pkg_filter
|
| |
+
|
| |
+
|
| |
+ def parse_pkg_filter(data):
|
| |
+ if not (data.startswith('^(') and data.endswith(')$')):
|
| |
+ raise ValueError("Unsupported filter format")
|
| |
+ return data[2:-2].split('|')
|
| |
+
|
| |
+
|
| |
+ def construct_pkg_filter_regex(data):
|
| |
+ inner = '|'.join(data)
|
| |
+ return "^({})$".format(inner)
|
| |
+
|
| |
+
|
| |
+ def normalize_filter(inner_pkg_filter):
|
| |
+ return sorted(set(inner_pkg_filter), key=str.lower)
|
| |
+
|
| |
+
|
| |
+ def print_cli_command(tag, data, koji_profile=None, parent_tag=None):
|
| |
+ fmt = "koji {}edit-tag-inheritance --pkg-filter=\"{}\" {} {}"
|
| |
+ if koji_profile:
|
| |
+ profile_opt = "--profile=\"{}\" ".format(koji_profile)
|
| |
+ else:
|
| |
+ profile_opt = ""
|
| |
+ for parent in data:
|
| |
+ if parent_tag:
|
| |
+ if parent['name'] != parent_tag:
|
| |
+ continue
|
| |
+ print fmt.format(profile_opt, parent['pkg_filter'], tag, parent['name'])
|
| |
+
|
| |
+
|
| |
+ def print_action(dry_run):
|
| |
+ if dry_run:
|
| |
+ print "Would run:"
|
| |
+ else:
|
| |
+ print "Running:"
|
| |
This tools simplifies management of package filter for tags. It expects package filter in format ^(package1|package2)$. Commands:
- list lists packages one per line
- add adds a package to the filter
- remove removes a package from the filter
- exists tests existence of a package in the filter
- normalize sorts packages in filter alphabetically and makes sure they are unique. If somebody adds packages manually tool is able to parse that. But normalized package list helps people to read that out of the tool. Normalization is done on all operations that changes package filter.
Some commands accept parent tag - it is required only if tag has more than one parent.
All actions that modifies stuff accept --dry-run.
Finally I'm not sure where to write a documentation for this new command. Could you advise?