Blame koji-tags/library/koji_tag_inheritance.py

6e61fb
#!/usr/bin/python
6e61fb
from ansible.module_utils.basic import AnsibleModule
6e61fb
from ansible.module_utils import common_koji
6e61fb
6e61fb
6e61fb
ANSIBLE_METADATA = {
6e61fb
    'metadata_version': '1.0',
6e61fb
    'status': ['preview'],
6e61fb
    'supported_by': 'community'
6e61fb
}
6e61fb
6e61fb
6e61fb
DOCUMENTATION = '''
6e61fb
---
6e61fb
module: koji_tag_inheritance
6e61fb
6e61fb
short_description: Manage a Koji tag inheritance relationship
6e61fb
description:
6e61fb
   - Fine-grained management for tag inheritance relationships.
6e61fb
   - The `koji_tag` module is all-or-nothing when it comes to managing tag
6e61fb
     inheritance. When you set inheritance with `koji_tag`, the module will
6e61fb
     delete any inheritance relationships that are not defined there.
6e61fb
   - In some cases you may want to declare *some* inheritance relationships
6e61fb
     within Ansible without clobbering other existing inheritance
6e61fb
     relationships. For example, `MBS
6e61fb
     <https://fedoraproject.org/wiki/Changes/ModuleBuildService>`_ will
6e61fb
     dynamically manage some inheritance relationships of tags.
6e61fb
options:
6e61fb
   child_tag:
6e61fb
     description:
6e61fb
       - The name of the Koji tag that will be the child.
6e61fb
     required: true
6e61fb
   parent_tag:
6e61fb
     description:
6e61fb
       - The name of the Koji tag that will be the parent of the child.
6e61fb
     required: true
6e61fb
   priority:
6e61fb
     description:
6e61fb
       - The priority of this parent for this child. Parents with smaller
6e61fb
         numbers will override parents with bigger numbers.
6e61fb
       - 'When defining an inheritance relationship with "state: present", you
6e61fb
         must specify a priority. When deleting an inheritance relationship
6e61fb
         with "state: absent", you should not specify a priority. Ansible will
6e61fb
         simply remove the parent_tag link, regardless of its priority.'
6e61fb
     required: true
6e61fb
   maxdepth:
6e61fb
     description:
6e61fb
       - By default, a tag's inheritance chain is unlimited. This means that
6e61fb
         Koji will look back through an unlimited chain of parent and
6e61fb
         grandparent tags to determine the contents of the tag.
6e61fb
       - You may use this maxdepth parameter to limit the maximum depth of the
6e61fb
         inheritance. For example "0" means that only the parent tag itself
6e61fb
         will be available in the inheritance - parent tags of the parent tag
6e61fb
         won't be available.
6e61fb
       - 'To restore the default umlimited depth behavior on a tag, you can set
6e61fb
         ``maxdepth: null`` or ``maxdepth: `` (empty value).'
6e61fb
       - If you do not set any ``maxdepth`` parameter at all, koji-ansible
6e61fb
         will overwrite an existing tag's current maxdepth setting to "null"
6e61fb
         (in other words, unlimited depth). This was the historical behavior
6e61fb
         of the module and the easiest way to implement this in the code.
6e61fb
         Arguably this behavior is unexpected, because Ansible should only do
6e61fb
         what you tell it to do. We might change this in the future so that
6e61fb
         Ansible only modifies ``maxdepth`` *if* you explicitly configure it.
6e61fb
         Please open GitHub issues to discuss your use-case.
6e61fb
     required: false
6e61fb
     default: null (unlimited depth)
6e61fb
   pkg_filter:
6e61fb
     description:
6e61fb
       - Regular expression selecting the packages for which builds can be
6e61fb
         inherited through this inheritance link.
6e61fb
       - Don't forget to use ``^`` and ``$`` when limiting to exact package
6e61fb
         names; they are not implicit.
6e61fb
       - The default empty string allows all packages to be inherited through
6e61fb
         this link.
6e61fb
     required: false
6e61fb
     default: ''
6e61fb
   intransitive:
6e61fb
     description:
6e61fb
       - Prevents inheritance link from being used by the child tag's children.
6e61fb
         In other words, the link is only used to determine parent tags for the
6e61fb
         child tag directly, but not to determine "grandparent" tags for the
6e61fb
         child tag's children.
6e61fb
     required: false
6e61fb
     default: false
6e61fb
   noconfig:
6e61fb
     description:
6e61fb
       - Prevents tag options ("extra") from being inherited.
6e61fb
     required: false
6e61fb
     default: false
6e61fb
   state:
6e61fb
     description:
6e61fb
       - Whether to add or remove this inheritance link.
6e61fb
     choices: [present, absent]
6e61fb
     default: present
6e61fb
requirements:
6e61fb
  - "python >= 2.7"
6e61fb
  - "koji"
6e61fb
'''
6e61fb
6e61fb
EXAMPLES = '''
6e61fb
- name: Use devtoolset to build for Ceph Nautilus
6e61fb
  hosts: localhost
6e61fb
  tasks:
6e61fb
    - name: set devtoolset-7 as a parent of ceph nautilus
6e61fb
      koji_tag_inheritance:
6e61fb
        koji: kojidev
6e61fb
        parent_tag: sclo7-devtoolset-7-rh-release
6e61fb
        child_tag: storage7-ceph-nautilus-el7-build
6e61fb
        priority: 25
6e61fb
6e61fb
    - name: remove devtoolset-7 as a parent of my other build tag
6e61fb
      koji_tag_inheritance:
6e61fb
        parent_tag: sclo7-devtoolset-7-rh-release
6e61fb
        child_tag: other-storage-el7-build
6e61fb
        state: absent
6e61fb
'''
6e61fb
6e61fb
RETURN = ''' # '''
6e61fb
6e61fb
6e61fb
def get_ids_and_inheritance(session, child_tag, parent_tag):
6e61fb
    """
6e61fb
    Query Koji for the current state of these tags and inheritance.
6e61fb
6e61fb
    :param session: Koji client session
6e61fb
    :param str child_tag: Koji tag name
6e61fb
    :param str parent_tag: Koji tag name
6e61fb
    :return: 3-element tuple of child_id (int), parent_id (int),
6e61fb
             and current_inheritance (list)
6e61fb
    """
6e61fb
    child_taginfo = session.getTag(child_tag)
6e61fb
    parent_taginfo = session.getTag(parent_tag)
6e61fb
    child_id = child_taginfo['id'] if child_taginfo else None
6e61fb
    parent_id = parent_taginfo['id'] if parent_taginfo else None
6e61fb
    if child_id:
6e61fb
        current_inheritance = session.getInheritanceData(child_id)
6e61fb
    else:
6e61fb
        current_inheritance = []
6e61fb
    # TODO use multicall to get all of this at once:
6e61fb
    # (Need to update the test suite fakes to handle multicalls)
6e61fb
    # session.multicall = True
6e61fb
    # session.getTag(child_tag, strict=True)
6e61fb
    # session.getTag(parent_tag, strict=True)
6e61fb
    # session.getInheritanceData(child_tag)
6e61fb
    # multicall_results = session.multiCall(strict=True)
6e61fb
    # # flatten multicall results:
6e61fb
    # multicall_results = [result[0] for result in multicall_results]
6e61fb
    # child_id = multicall_results[0]['id']
6e61fb
    # parent_id = multicall_results[1]['id']
6e61fb
    # current_inheritance = multicall_results[2]
6e61fb
    return (child_id, parent_id, current_inheritance)
6e61fb
6e61fb
6e61fb
def generate_new_rule(child_id, parent_tag, parent_id, priority, maxdepth,
6e61fb
                      pkg_filter, intransitive, noconfig):
6e61fb
    """
6e61fb
    Return a full inheritance rule to add for this child tag.
6e61fb
6e61fb
    :param int child_id: Koji tag id
6e61fb
    :param str parent_tag: Koji tag name
6e61fb
    :param int parent_id: Koji tag id
6e61fb
    :param int priority: Priority of this parent for this child
6e61fb
    :param int maxdepth: Max depth of the inheritance
6e61fb
    :param str pkg_filter: Regular expression string of package names to include
6e61fb
    :param bool intransitive: Don't allow this inheritance link to be inherited
6e61fb
    :param bool noconfig: Prevent tag options ("extra") from being inherited
6e61fb
    """
6e61fb
    return {
6e61fb
        'child_id': child_id,
6e61fb
        'intransitive': intransitive,
6e61fb
        'maxdepth': maxdepth,
6e61fb
        'name': parent_tag,
6e61fb
        'noconfig': noconfig,
6e61fb
        'parent_id': parent_id,
6e61fb
        'pkg_filter': pkg_filter,
6e61fb
        'priority': priority}
6e61fb
6e61fb
6e61fb
def add_tag_inheritance(session, child_tag, parent_tag, priority, maxdepth,
6e61fb
                        pkg_filter, intransitive, noconfig, check_mode):
6e61fb
    """
6e61fb
    Ensure that a tag inheritance rule exists.
6e61fb
6e61fb
    :param session: Koji client session
6e61fb
    :param str child_tag: Koji tag name
6e61fb
    :param str parent_tag: Koji tag name
6e61fb
    :param int priority: Priority of this parent for this child
6e61fb
    :param int maxdepth: Max depth of the inheritance
6e61fb
    :param str pkg_filter: Regular expression string of package names to include
6e61fb
    :param bool intransitive: Don't allow this inheritance link to be inherited
6e61fb
    :param bool noconfig: Prevent tag options ("extra") from being inherited
6e61fb
    :param bool check_mode: don't make any changes
6e61fb
    :return: result (dict)
6e61fb
    """
6e61fb
    result = {'changed': False, 'stdout_lines': []}
6e61fb
    data = get_ids_and_inheritance(session, child_tag, parent_tag)
6e61fb
    child_id, parent_id, current_inheritance = data
6e61fb
    if not child_id:
6e61fb
        msg = 'child tag %s not found' % child_tag
6e61fb
        if check_mode:
6e61fb
            result['stdout_lines'].append(msg)
6e61fb
        else:
6e61fb
            raise ValueError(msg)
6e61fb
    if not parent_id:
6e61fb
        msg = 'parent tag %s not found' % parent_tag
6e61fb
        if check_mode:
6e61fb
            result['stdout_lines'].append(msg)
6e61fb
        else:
6e61fb
            raise ValueError(msg)
6e61fb
6e61fb
    new_rule = generate_new_rule(child_id, parent_tag, parent_id, priority,
6e61fb
                                 maxdepth, pkg_filter, intransitive, noconfig)
6e61fb
    new_rules = [new_rule]
6e61fb
    for rule in current_inheritance:
6e61fb
        if rule == new_rule:
6e61fb
            return result
6e61fb
        if rule['priority'] == priority:
6e61fb
            # prefix taginfo-style inheritance strings with diff-like +/-
6e61fb
            result['stdout_lines'].append('dissimilar rules:')
6e61fb
            result['stdout_lines'].extend(
6e61fb
                    map(lambda r: ' -' + r,
6e61fb
                        common_koji.describe_inheritance_rule(rule)))
6e61fb
            result['stdout_lines'].extend(
6e61fb
                    map(lambda r: ' +' + r,
6e61fb
                        common_koji.describe_inheritance_rule(new_rule)))
6e61fb
            delete_rule = rule.copy()
6e61fb
            # Mark this rule for deletion
6e61fb
            delete_rule['delete link'] = True
6e61fb
            new_rules.insert(0, delete_rule)
6e61fb
6e61fb
    if len(new_rules) > 1:
6e61fb
        result['stdout_lines'].append('remove inheritance link:')
6e61fb
        result['stdout_lines'].extend(
6e61fb
                common_koji.describe_inheritance(new_rules[:-1]))
6e61fb
    result['stdout_lines'].append('add inheritance link:')
6e61fb
    result['stdout_lines'].extend(
6e61fb
            common_koji.describe_inheritance_rule(new_rule))
6e61fb
    result['changed'] = True
6e61fb
    if not check_mode:
6e61fb
        common_koji.ensure_logged_in(session)
6e61fb
        session.setInheritanceData(child_tag, new_rules)
6e61fb
    return result
6e61fb
6e61fb
6e61fb
def remove_tag_inheritance(session, child_tag, parent_tag, check_mode):
6e61fb
    """
6e61fb
    Ensure that a tag inheritance rule does not exist.
6e61fb
6e61fb
    :param session: Koji client session
6e61fb
    :param str child_tag: Koji tag name
6e61fb
    :param str parent_tag: Koji tag name
6e61fb
    :param bool check_mode: don't make any changes
6e61fb
    :return: result (dict)
6e61fb
    """
6e61fb
    result = {'changed': False, 'stdout_lines': []}
6e61fb
    current_inheritance = session.getInheritanceData(child_tag)
6e61fb
    found_rule = {}
6e61fb
    for rule in current_inheritance:
6e61fb
        if rule['name'] == parent_tag:
6e61fb
            found_rule = rule.copy()
6e61fb
            # Mark this rule for deletion
6e61fb
            found_rule['delete link'] = True
6e61fb
    if not found_rule:
6e61fb
        return result
6e61fb
    result['stdout_lines'].append('remove inheritance link:')
6e61fb
    result['stdout_lines'].extend(
6e61fb
            common_koji.describe_inheritance_rule(found_rule))
6e61fb
    result['changed'] = True
6e61fb
    if not check_mode:
6e61fb
        common_koji.ensure_logged_in(session)
6e61fb
        session.setInheritanceData(child_tag, [found_rule])
6e61fb
    return result
6e61fb
6e61fb
6e61fb
def run_module():
6e61fb
    module_args = dict(
6e61fb
        koji=dict(type='str', required=False),
6e61fb
        child_tag=dict(type='str', required=True),
6e61fb
        parent_tag=dict(type='str', required=True),
6e61fb
        priority=dict(type='int', required=False),
6e61fb
        maxdepth=dict(type='int', required=False, default=None),
6e61fb
        pkg_filter=dict(type='str', required=False, default=''),
6e61fb
        intransitive=dict(type='bool', required=False, default=False),
6e61fb
        noconfig=dict(type='bool', required=False, default=False),
6e61fb
        state=dict(type='str', choices=[
6e61fb
                   'present', 'absent'], required=False, default='present'),
6e61fb
    )
6e61fb
    module = AnsibleModule(
6e61fb
        argument_spec=module_args,
6e61fb
        supports_check_mode=True
6e61fb
    )
6e61fb
6e61fb
    if not common_koji.HAS_KOJI:
6e61fb
        module.fail_json(msg='koji is required for this module')
6e61fb
6e61fb
    check_mode = module.check_mode
6e61fb
    params = module.params
6e61fb
    state = params['state']
6e61fb
    profile = params['koji']
6e61fb
6e61fb
    session = common_koji.get_session(profile)
6e61fb
6e61fb
    if state == 'present':
6e61fb
        if 'priority' not in params:
6e61fb
            module.fail_json(msg='specify a "priority" integer')
6e61fb
        result = add_tag_inheritance(session,
6e61fb
                                     child_tag=params['child_tag'],
6e61fb
                                     parent_tag=params['parent_tag'],
6e61fb
                                     priority=params['priority'],
6e61fb
                                     maxdepth=params['maxdepth'],
6e61fb
                                     pkg_filter=params['pkg_filter'],
6e61fb
                                     intransitive=params['intransitive'],
6e61fb
                                     noconfig=params['noconfig'],
6e61fb
                                     check_mode=check_mode)
6e61fb
    elif state == 'absent':
6e61fb
        result = remove_tag_inheritance(session,
6e61fb
                                        child_tag=params['child_tag'],
6e61fb
                                        parent_tag=params['parent_tag'],
6e61fb
                                        check_mode=check_mode)
6e61fb
6e61fb
    module.exit_json(**result)
6e61fb
6e61fb
6e61fb
def main():
6e61fb
    run_module()
6e61fb
6e61fb
6e61fb
if __name__ == '__main__':
6e61fb
    main()