diff --git a/lib/ansible/modules/extras/packaging/os/zypper_repository.py b/lib/ansible/modules/extras/packaging/os/zypper_repository.py index 446723ef04..0e4e805856 100644 --- a/lib/ansible/modules/extras/packaging/os/zypper_repository.py +++ b/lib/ansible/modules/extras/packaging/os/zypper_repository.py @@ -58,16 +58,28 @@ options: required: false default: "no" choices: [ "yes", "no" ] - aliases: [] refresh: description: - Enable autorefresh of the repository. required: false default: "yes" choices: [ "yes", "no" ] - aliases: [] -notes: [] -requirements: [ zypper ] + priority: + description: + - Set priority of repository. Packages will always be installed + from the repository with the smallest priority number. + required: false + version_added: "2.1" + overwrite_multiple: + description: + - Overwrite multiple repository entries, if repositories with both name and + URL already exist. + required: false + default: "no" + choices: [ "yes", "no" ] + version_added: "2.1" +requirements: + - "zypper >= 1.0 # included in openSuSE >= 11.1 or SuSE Linux Enterprise Server/Desktop >= 11.0" ''' EXAMPLES = ''' @@ -83,18 +95,10 @@ EXAMPLES = ''' REPO_OPTS = ['alias', 'name', 'priority', 'enabled', 'autorefresh', 'gpgcheck'] -def zypper_version(module): - """Return (rc, message) tuple""" - cmd = ['/usr/bin/zypper', '-V'] - rc, stdout, stderr = module.run_command(cmd, check_rc=False) - if rc == 0: - return rc, stdout - else: - return rc, stderr - def _parse_repos(module): - """parses the output of zypper -x lr and returns a parse repo dictionary""" + """parses the output of zypper -x lr and return a parse repo dictionary""" cmd = ['/usr/bin/zypper', '-x', 'lr'] + from xml.dom.minidom import parseString as parseXML rc, stdout, stderr = module.run_command(cmd, check_rc=False) if rc == 0: @@ -120,81 +124,81 @@ def _parse_repos(module): d['stdout'] = stdout module.fail_json(msg='Failed to execute "%s"' % " ".join(cmd), **d) -def _parse_repos_old(module): - """parses the output of zypper sl and returns a parse repo dictionary""" - cmd = ['/usr/bin/zypper', 'sl'] - repos = [] - rc, stdout, stderr = module.run_command(cmd, check_rc=True) - for line in stdout.split('\n'): - matched = re.search(r'\d+\s+\|\s+(?P\w+)\s+\|\s+(?P\w+)\s+\|\s+(?P\w+)\s+\|\s+(?P\w+)\s+\|\s+(?P.*)', line) - if matched == None: - continue - - m = matched.groupdict() - m['alias']= m['name'] - m['priority'] = 100 - m['gpgcheck'] = 1 - repos.append(m) - - return repos - -def repo_exists(module, old_zypper, **kwargs): - - def repo_subset(realrepo, repocmp): - for k in repocmp: - if k not in realrepo: - return False - - for k, v in realrepo.items(): - if k in repocmp: - if v.rstrip("/") != repocmp[k].rstrip("/"): - return False - return True - - if old_zypper: - repos = _parse_repos_old(module) - else: - repos = _parse_repos(module) - - for repo in repos: - if repo_subset(repo, kwargs): +def _repo_changes(realrepo, repocmp): + for k in repocmp: + if repocmp[k] and k not in realrepo: return True + + for k, v in realrepo.items(): + if k in repocmp and repocmp[k]: + valold = str(repocmp[k] or "") + valnew = v or "" + if k == "url": + valold, valnew = valold.rstrip("/"), valnew.rstrip("/") + if valold != valnew: + return True return False +def repo_exists(module, repodata, overwrite_multiple): + existing_repos = _parse_repos(module) -def add_repo(module, repo, alias, description, disable_gpg_check, old_zypper, refresh): - if old_zypper: - cmd = ['/usr/bin/zypper', 'sa'] + # look for repos that have matching alias or url to the one searched + repos = [] + for kw in ['alias', 'url']: + name = repodata[kw] + for oldr in existing_repos: + if repodata[kw] == oldr[kw] and oldr not in repos: + repos.append(oldr) + + if len(repos) == 0: + # Repo does not exist yet + return (False, False, None) + elif len(repos) == 1: + # Found an existing repo, look for changes + has_changes = _repo_changes(repos[0], repodata) + return (True, has_changes, repos) + elif len(repos) == 2 and overwrite_multiple: + # Found two repos and want to overwrite_multiple + return (True, True, repos) else: - cmd = ['/usr/bin/zypper', 'ar', '--check'] + # either more than 2 repos (shouldn't happen) + # or overwrite_multiple is not active + module.fail_json(msg='More than one repo matched "%s": "%s"' % (name, repos)) - if repo.startswith("file:/") and old_zypper: - cmd.extend(['-t', 'Plaindir']) +def modify_repo(module, repodata, old_repos): + repo = repodata['url'] + cmd = ['/usr/bin/zypper', 'ar', '--check'] + if repodata['name']: + cmd.extend(['--name', repodata['name']]) + + if repodata['priority']: + cmd.extend(['--priority', str(repodata['priority'])]) + + if repodata['enabled'] == '0': + cmd.append('--disable') + + if repodata['gpgcheck'] == '1': + cmd.append('--gpgcheck') else: - cmd.extend(['-t', 'plaindir']) - - if description: - cmd.extend(['--name', description]) - - if disable_gpg_check and not old_zypper: cmd.append('--no-gpgcheck') - if refresh: + if repodata['autorefresh'] == '1': cmd.append('--refresh') cmd.append(repo) if not repo.endswith('.repo'): - cmd.append(alias) + cmd.append(repodata['alias']) + + if old_repos is not None: + for oldrepo in old_repos: + remove_repo(module, oldrepo['url']) rc, stdout, stderr = module.run_command(cmd, check_rc=False) changed = rc == 0 if rc == 0: changed = True - elif 'already exists. Please use another alias' in stderr: - changed = False else: - #module.fail_json(msg=stderr if stderr else stdout) if stderr: module.fail_json(msg=stderr) else: @@ -203,16 +207,8 @@ def add_repo(module, repo, alias, description, disable_gpg_check, old_zypper, re return changed -def remove_repo(module, repo, alias, old_zypper): - - if old_zypper: - cmd = ['/usr/bin/zypper', 'sd'] - else: - cmd = ['/usr/bin/zypper', 'rr'] - if alias: - cmd.append(alias) - else: - cmd.append(repo) +def remove_repo(module, repo): + cmd = ['/usr/bin/zypper', 'rr', repo] rc, stdout, stderr = module.run_command(cmd, check_rc=True) changed = rc == 0 @@ -237,59 +233,68 @@ def main(): description=dict(required=False), disable_gpg_check = dict(required=False, default='no', type='bool'), refresh = dict(required=False, default='yes', type='bool'), + priority = dict(required=False, type='int'), + enabled = dict(required=False, default='yes', type='bool'), + overwrite_multiple = dict(required=False, default='no', type='bool'), ), supports_check_mode=False, ) repo = module.params['repo'] + alias = module.params['name'] state = module.params['state'] - name = module.params['name'] - description = module.params['description'] - disable_gpg_check = module.params['disable_gpg_check'] - refresh = module.params['refresh'] + overwrite_multiple = module.params['overwrite_multiple'] + + repodata = { + 'url': repo, + 'alias': alias, + 'name': module.params['description'], + 'priority': module.params['priority'], + } + # rewrite bools in the language that zypper lr -x provides for easier comparison + if module.params['enabled']: + repodata['enabled'] = '1' + else: + repodata['enabled'] = '0' + if module.params['disable_gpg_check']: + repodata['gpgcheck'] = '0' + else: + repodata['gpgcheck'] = '1' + if module.params['refresh']: + repodata['autorefresh'] = '1' + else: + repodata['autorefresh'] = '0' def exit_unchanged(): - module.exit_json(changed=False, repo=repo, state=state, name=name) - - rc, out = zypper_version(module) - match = re.match(r'zypper\s+(\d+)\.(\d+)\.(\d+)', out) - if not match or int(match.group(1)) > 0: - old_zypper = False - else: - old_zypper = True + module.exit_json(changed=False, repodata=repodata, state=state) # Check run-time module parameters if state == 'present' and not repo: module.fail_json(msg='Module option state=present requires repo') - if state == 'absent' and not repo and not name: + if state == 'absent' and not repo and not alias: module.fail_json(msg='Alias or repo parameter required when state=absent') if repo and repo.endswith('.repo'): - if name: - module.fail_json(msg='Incompatible option: \'name\'. Do not use name when adding repo files') + if alias: + module.fail_json(msg='Incompatible option: \'name\'. Do not use name when adding .repo files') else: - if not name and state == "present": - module.fail_json(msg='Name required when adding non-repo files:') + if not alias and state == "present": + module.fail_json(msg='Name required when adding non-repo files.') - if repo and repo.endswith('.repo'): - exists = repo_exists(module, old_zypper, url=repo, alias=name) - elif repo: - exists = repo_exists(module, old_zypper, url=repo) - else: - exists = repo_exists(module, old_zypper, alias=name) + exists, mod, old_repos = repo_exists(module, repodata, overwrite_multiple) if state == 'present': - if exists: + if exists and not mod: exit_unchanged() - - changed = add_repo(module, repo, name, description, disable_gpg_check, old_zypper, refresh) + changed = modify_repo(module, repodata, old_repos) elif state == 'absent': if not exists: exit_unchanged() + if not repo: + repo=alias + changed = remove_repo(module, repo) - changed = remove_repo(module, repo, name, old_zypper) - - module.exit_json(changed=changed, repo=repo, state=state) + module.exit_json(changed=changed, repodata=repodata, state=state) # import module snippets from ansible.module_utils.basic import *