Created
August 24, 2018 09:18
-
-
Save ferdnyc/0d4e99231d07cd7f10af4b1acac40318 to your computer and use it in GitHub Desktop.
Comparison (via unified diff) of the fedpkg and rfpkg __init__.py and cli.py source
--- fedpkg/fedpkg/cli.py 2018-08-23 12:21:22.627622258 -0400 | |
+++ rfpkg/src/rfpkg/cli.py 2018-08-23 12:21:38.273925248 -0400 | |
@@ -1,1136 +1,210 @@ | |
-# -*- coding: utf-8 -*- | |
-# cli.py - a cli client class module for fedpkg | |
+# cli.py - a cli client class module for rfpkg | |
# | |
# Copyright (C) 2011 Red Hat Inc. | |
# Author(s): Jesse Keating <jkeating@redhat.com> | |
+# Nicolas Chauvet <kwizart@gmail.com> - 2015 | |
# | |
# This program is free software; you can redistribute it and/or modify it | |
# under the terms of the GNU General Public License as published by the | |
# Free Software Foundation; either version 2 of the License, or (at your | |
# option) any later version. See http://www.gnu.org/copyleft/gpl.html for | |
# the full text of the license. | |
-from __future__ import print_function | |
from pyrpkg.cli import cliClient | |
-import argparse | |
-import io | |
+import sys | |
import os | |
+import logging | |
import re | |
-import json | |
-import pkg_resources | |
-import six | |
-import shutil | |
+import subprocess | |
import textwrap | |
-import itertools | |
+import hashlib | |
-from datetime import datetime | |
+import pkgdb2client | |
-from six.moves import configparser | |
-from six.moves.configparser import NoSectionError | |
-from six.moves.configparser import NoOptionError | |
-from six.moves.urllib_parse import urlparse | |
-from pyrpkg import rpkgError | |
-from fedpkg.bugzilla import BugzillaClient | |
-from fedpkg.utils import ( | |
- get_release_branches, sl_list_to_dict, verify_sls, new_pagure_issue, | |
- get_pagure_token, is_epel, assert_valid_epel_package, | |
- assert_new_tests_repo, get_dist_git_url, get_stream_branches, | |
- expand_release) | |
-RELEASE_BRANCH_REGEX = r'^(f\d+|el\d+|epel\d+)$' | |
-LOCAL_PACKAGE_CONFIG = 'package.cfg' | |
- | |
-BODHI_TEMPLATE = """\ | |
-[ %(nvr)s ] | |
- | |
-# bugfix, security, enhancement, newpackage (required) | |
-type=%(type_)s | |
- | |
-# testing, stable | |
-request=%(request)s | |
- | |
-# Bug numbers: 1234,9876 | |
-bugs=%(bugs)s | |
- | |
-%(changelog)s | |
-# Here is where you give an explanation of your update. | |
-# Content can span multiple lines, as long as they are indented deeper than | |
-# the first line. For example, | |
-# notes=first line | |
-# second line | |
-# and so on | |
-notes=%(descr)s | |
- | |
-# Enable request automation based on the stable/unstable karma thresholds | |
-autokarma=%(autokarma)s | |
-stable_karma=%(stable_karma)s | |
-unstable_karma=%(unstable_karma)s | |
- | |
-# Automatically close bugs when this marked as stable | |
-close_bugs=%(close_bugs)s | |
- | |
-# Suggest that users restart after update | |
-suggest_reboot=%(suggest_reboot)s | |
-""" | |
- | |
- | |
-def check_bodhi_version(): | |
- try: | |
- dist = pkg_resources.get_distribution('bodhi_client') | |
- except pkg_resources.DistributionNotFound: | |
- raise rpkgError('bodhi-client < 2.0 is not supported.') | |
- major = int(dist.version.split('.', 1)[0]) | |
- if major >= 4: | |
- raise rpkgError( | |
- 'This system has bodhi v{0}, which is unsupported.'.format(major)) | |
- | |
- | |
-class fedpkgClient(cliClient): | |
+class rfpkgClient(cliClient): | |
def __init__(self, config, name=None): | |
- self.DEFAULT_CLI_NAME = 'fedpkg' | |
- super(fedpkgClient, self).__init__(config, name) | |
+ self.DEFAULT_CLI_NAME = 'rfpkg' | |
+ super(rfpkgClient, self).__init__(config, name) | |
self.setup_fed_subparsers() | |
- def setup_argparser(self): | |
- super(fedpkgClient, self).setup_argparser() | |
- | |
- # This line is added here so that it shows up with the "--help" option, | |
- # but it isn't used for anything else | |
- self.parser.add_argument( | |
- '--user-config', help='Specify a user config file to use') | |
- opt_release = self.parser._option_string_actions['--release'] | |
- opt_release.help = 'Override the discovered release, e.g. f25, which has to match ' \ | |
- 'the remote branch name created in package repository. ' \ | |
- 'Particularly, use master to build RPMs for rawhide.' | |
- | |
def setup_fed_subparsers(self): | |
"""Register the fedora specific targets""" | |
- self.register_releases_info() | |
self.register_retire() | |
self.register_update() | |
- self.register_request_repo() | |
- self.register_request_tests_repo() | |
- self.register_request_branch() | |
- self.register_override() | |
# Target registry goes here | |
def register_retire(self): | |
"""Register the retire target""" | |
retire_parser = self.subparsers.add_parser( | |
'retire', | |
help='Retire a package', | |
description='This command will remove all files from the repo, ' | |
- 'leave a dead.package file, and push the changes.' | |
+ 'leave a dead.package file, push the changes and ' | |
+ 'retire the package in pkgdb.' | |
) | |
retire_parser.add_argument('reason', | |
help='Reason for retiring the package') | |
retire_parser.set_defaults(command=self.retire) | |
def register_update(self): | |
- description = ''' | |
-This will create a bodhi update request for the current package n-v-r. | |
- | |
-There are two ways to specify update details. Without any argument from command | |
-line, either update type or notes is omitted, a template editor will be shown | |
-and let you edit the detail information interactively. | |
- | |
-Alternatively, you could specify argument from command line to create an update | |
-directly, for example: | |
- | |
- {0} update --type bugfix --notes 'Rebuilt' --bugs 1000 1002 | |
- | |
-When all lines in template editor are commented out or deleted, the creation | |
-process is aborted. If the template keeps unchanged, {0} continues on creating | |
-update. That gives user a chance to confirm the auto-generated notes from | |
-change log if option --notes is omitted. | |
-'''.format(self.name) | |
- | |
update_parser = self.subparsers.add_parser( | |
'update', | |
- formatter_class=argparse.RawDescriptionHelpFormatter, | |
help='Submit last build as update', | |
- description=description, | |
+ description='This will create a bodhi update request for the ' | |
+ 'current package n-v-r.' | |
) | |
- | |
- def validate_stable_karma(value): | |
- error = argparse.ArgumentTypeError( | |
- 'Stable karma must be an integer which is greater than zero.') | |
- try: | |
- karma = int(value) | |
- except ValueError: | |
- raise error | |
- if karma <= 0: | |
- raise error | |
- | |
- def validate_unstable_karma(value): | |
- error = argparse.ArgumentTypeError( | |
- 'Unstable karma must be an integer which is less than zero.') | |
- try: | |
- karma = int(value) | |
- except ValueError: | |
- raise error | |
- if karma >= 0: | |
- raise error | |
- | |
- def validate_bugs(value): | |
- if not value.isdigit(): | |
- raise argparse.ArgumentTypeError( | |
- 'Invalid bug {0}. It should be an integer.'.format(value)) | |
- | |
- update_parser.add_argument( | |
- '--type', | |
- choices=['bugfix', 'security', 'enhancement', 'newpackage'], | |
- dest='update_type', | |
- help='Update type. Template editor will be shown if type is ' | |
- 'omitted.') | |
- update_parser.add_argument( | |
- '--request', | |
- choices=['testing', 'stable'], | |
- default='testing', | |
- help='Requested repository.') | |
- update_parser.add_argument( | |
- '--bugs', | |
- nargs='+', | |
- type=validate_bugs, | |
- help='Bug numbers. If omitted, bug numbers will be extracted from' | |
- ' change logs.') | |
- update_parser.add_argument( | |
- '--notes', | |
- help='Update description. Multiple lines of notes could be ' | |
- 'specified. If omitted, template editor will be shown.') | |
- update_parser.add_argument( | |
- '--disable-autokarma', | |
- action='store_false', | |
- default=True, | |
- dest='autokarma', | |
- help='Karma automatism is enabled by default. Use this option to ' | |
- 'disable that.') | |
- update_parser.add_argument( | |
- '--stable-karma', | |
- type=validate_stable_karma, | |
- metavar='KARMA', | |
- default=3, | |
- help='Stable karma. Default is 3.') | |
- update_parser.add_argument( | |
- '--unstable-karma', | |
- type=validate_unstable_karma, | |
- metavar='KARMA', | |
- default=-3, | |
- help='Unstable karma. Default is -3.') | |
- update_parser.add_argument( | |
- '--not-close-bugs', | |
- action='store_false', | |
- default=True, | |
- dest='close_bugs', | |
- help='By default, update will be created by enabling to close bugs' | |
- ' automatically. If this is what you do not want, use this ' | |
- 'option to disable the default behavior.') | |
- update_parser.add_argument( | |
- '--suggest-reboot', | |
- action='store_true', | |
- default=False, | |
- dest='suggest_reboot', | |
- help='Suggest user to reboot after update. Default is False.') | |
update_parser.set_defaults(command=self.update) | |
- def get_distgit_namespaces(self): | |
- dg_namespaced = self._get_bool_opt('distgit_namespaced') | |
- if dg_namespaced and self.config.has_option( | |
- self.name, 'distgit_namespaces'): | |
- return self.config.get(self.name, 'distgit_namespaces').split() | |
- else: | |
- return None | |
- | |
- def register_request_repo(self): | |
- help_msg = 'Request a new dist-git repository' | |
- description = '''Request a new dist-git repository | |
- | |
-Before requesting a new dist-git repository for a new package, you need to | |
-generate a pagure.io API token at https://{1}/settings/token/new, select the | |
-"Create a new ticket" ACL and save it in your local user configuration located | |
-at ~/.config/rpkg/{0}.conf. For example: | |
- | |
- [{0}.pagure] | |
- token = <api_key_here> | |
- | |
-Below is a basic example of the command to request a dist-git repository for | |
-the package foo: | |
- | |
- fedpkg request-repo foo 1234 | |
- | |
-Another example to request a module foo: | |
- | |
- fedpkg request-repo --namespace modules foo | |
-'''.format(self.name, urlparse(self.config.get( | |
- '{0}.pagure'.format(self.name), 'url')).netloc) | |
- | |
- request_repo_parser = self.subparsers.add_parser( | |
- 'request-repo', | |
- formatter_class=argparse.RawDescriptionHelpFormatter, | |
- help=help_msg, | |
- description=description) | |
- request_repo_parser.add_argument( | |
- 'name', | |
- help='Repository name to request.') | |
- request_repo_parser.add_argument( | |
- 'bug', nargs='?', type=int, | |
- help='Bugzilla bug ID of the package review request. ' | |
- 'Not required for requesting a module repository') | |
- request_repo_parser.add_argument( | |
- '--namespace', | |
- required=False, | |
- default='rpms', | |
- choices=self.get_distgit_namespaces(), | |
- dest='new_repo_namespace', | |
- help='Namespace of repository. If omitted, default to rpms.') | |
- request_repo_parser.add_argument( | |
- '--description', '-d', help='The repo\'s description in dist-git') | |
- monitoring_choices = [ | |
- 'no-monitoring', 'monitoring', 'monitoring-with-scratch'] | |
- request_repo_parser.add_argument( | |
- '--monitor', '-m', help='The Anitya monitoring type for the repo', | |
- choices=monitoring_choices, default=monitoring_choices[1]) | |
- request_repo_parser.add_argument( | |
- '--upstreamurl', '-u', | |
- help='The upstream URL of the project') | |
- request_repo_parser.add_argument( | |
- '--summary', '-s', | |
- help='Override the package\'s summary from the Bugzilla bug') | |
- request_repo_parser.add_argument( | |
- '--exception', action='store_true', | |
- help='The package is an exception to the regular package review ' | |
- 'process (specifically, it does not require a Bugzilla bug)') | |
- request_repo_parser.add_argument( | |
- '--no-initial-commit', | |
- action='store_true', | |
- help='Do not include an initial commit in the repository.') | |
- request_repo_parser.set_defaults(command=self.request_repo) | |
- | |
- def register_request_tests_repo(self): | |
- help_msg = 'Request a new tests dist-git repository' | |
- pagure_url = urlparse(self.config.get( | |
- '{0}.pagure'.format(self.name), 'url')).netloc | |
- anongiturl = self.config.get( | |
- self.name, 'anongiturl', vars={'repo': 'any', 'module': 'any'} | |
- ) | |
- description = '''Request a new dist-git repository in tests shared namespace | |
- | |
- {2}/projects/tests/* | |
- | |
-For more information about tests shared namespace see | |
- | |
- https://fedoraproject.org/wiki/CI/Share_Test_Code | |
- | |
-Please refer to the request-repo command to see what has to be done before | |
-requesting a repository in the tests namespace. | |
- | |
-Below is a basic example of the command to request a dist-git repository for | |
-the space tests/foo: | |
- | |
- fedpkg request-tests-repo foo "Description of the repository" | |
- | |
-Note that the space name needs to reflect the intent of the tests and will | |
-undergo a manual review. | |
- | |
-'''.format(self.name, pagure_url, get_dist_git_url(anongiturl)) | |
- | |
- request_tests_repo_parser = self.subparsers.add_parser( | |
- 'request-tests-repo', | |
- formatter_class=argparse.RawDescriptionHelpFormatter, | |
- help=help_msg, | |
- description=description) | |
- request_tests_repo_parser.add_argument( | |
- 'name', | |
- help='Repository name to request.') | |
- request_tests_repo_parser.add_argument( | |
- 'description', | |
- help='Description of the tests repository') | |
- request_tests_repo_parser.set_defaults(command=self.request_tests_repo) | |
- | |
- def register_request_branch(self): | |
- help_msg = 'Request a new dist-git branch' | |
- description = '''Request a new dist-git branch | |
- | |
-Please refer to the request-repo command to see what has to be done before | |
-requesting a dist-git branch. | |
- | |
-Branch name could be one of current active Fedora and EPEL releases. Use | |
-command ``{0} releases-info`` to get release names that can be used to request | |
-a branch. | |
- | |
-Below are various examples of requesting a dist-git branch. | |
- | |
-Request a branch inside a cloned package repository: | |
- | |
- {0} request-branch f27 | |
- | |
-Request a branch outside package repository, which could apply to cases of | |
-requested repository has not been approved and created, or just not change | |
-directory to package repository: | |
- | |
- {0} request-branch --repo foo f27 | |
-'''.format(self.name) | |
- | |
- request_branch_parser = self.subparsers.add_parser( | |
- 'request-branch', | |
- formatter_class=argparse.RawDescriptionHelpFormatter, | |
- help=help_msg, | |
- description=description) | |
- request_branch_parser.add_argument( | |
- 'branch', nargs='?', help='The branch to request.') | |
- request_branch_parser.add_argument( | |
- '--repo', | |
- required=False, | |
- dest='repo_name_for_branch', | |
- metavar='NAME', | |
- help='Repository name the new branch is requested for.' | |
- ) | |
- request_branch_parser.add_argument( | |
- '--namespace', | |
- required=False, | |
- dest='repo_ns_for_branch', | |
- choices=self.get_distgit_namespaces(), | |
- help='Repository name the new branch is requested for.' | |
- ) | |
- request_branch_parser.add_argument( | |
- '--sl', nargs='*', | |
- help=('The service levels (SLs) tied to the branch. This must be ' | |
- 'in the format of "sl_name:2020-12-01". This is only for ' | |
- 'non-release branches. You may provide more than one by ' | |
- 'separating each SL with a space.') | |
- ) | |
- request_branch_parser.add_argument( | |
- '--no-git-branch', default=False, action='store_true', | |
- help='Don\'t create the branch in git but still create it in PDC' | |
- ) | |
- request_branch_parser.add_argument( | |
- '--no-auto-module', default=False, action='store_true', | |
- help='If requesting an rpm arbitrary branch, do not ' | |
- 'also request a new matching module. See ' | |
- 'https://pagure.io/fedrepo_req/issue/129' | |
- ) | |
- request_branch_parser.add_argument( | |
- '--all-releases', default=False, action='store_true', | |
- help='Make a new branch request for every active Fedora release' | |
- ) | |
- request_branch_parser.set_defaults(command=self.request_branch) | |
- | |
- def register_releases_info(self): | |
- help_msg = 'Print Fedora or EPEL current active releases' | |
- parser = self.subparsers.add_parser( | |
- 'releases-info', | |
- help=help_msg, | |
- description=help_msg) | |
- | |
- group = parser.add_mutually_exclusive_group() | |
- group.add_argument( | |
- '-e', '--epel', | |
- action='store_true', | |
- dest='show_epel_only', | |
- help='Only show EPEL releases.') | |
- group.add_argument( | |
- '-f', '--fedora', | |
- action='store_true', | |
- dest='show_fedora_only', | |
- help='Only show Fedora active releases.') | |
- group.add_argument( | |
- '-j', '--join', | |
- action='store_true', | |
- help='Show all releases in one line separated by a space.') | |
- | |
- parser.set_defaults(command=self.show_releases_info) | |
- | |
- def register_override(self): | |
- """Register command line parser for subcommand override | |
- | |
- .. versionadded:: 1.34 | |
- """ | |
- | |
- def validate_duration(value): | |
- try: | |
- duration = int(value) | |
- except ValueError: | |
- raise argparse.ArgumentTypeError('duration must be an integer.') | |
- if duration > 0: | |
- return duration | |
- raise argparse.ArgumentTypeError( | |
- 'override should have 1 day to exist at least.') | |
- | |
- def validate_extend_duration(value): | |
- if value.isdigit(): | |
- return validate_duration(value) | |
- match = re.match(r'(\d{4})-(\d{1,2})-(\d{1,2})', value) | |
- if not match: | |
- raise argparse.ArgumentTypeError( | |
- 'Invalid expiration date. Valid format: yyyy-mm-dd.') | |
- y, m, d = match.groups() | |
- return datetime(year=int(y), month=int(m), day=int(d)) | |
- | |
- override_parser = self.subparsers.add_parser( | |
- 'override', | |
- help='Manage buildroot overrides') | |
- override_subparser = override_parser.add_subparsers( | |
- description='Commands on override') | |
- | |
- create_parser = override_subparser.add_parser( | |
- 'create', | |
- help='Create buildroot override from build', | |
- formatter_class=argparse.RawDescriptionHelpFormatter, | |
- description='''\ | |
-Create a buildroot override from build guessed from current release branch or | |
-specified explicitly. | |
- | |
-Examples: | |
- | |
-Create a buildroot override from build guessed from release branch. Note that, | |
-command must run inside a package repository. | |
- | |
- {0} switch-branch f28 | |
- {0} override create --duration 5 | |
- | |
-Create for a specified build: | |
- | |
- {0} override create --duration 5 package-1.0-1.fc28 | |
-'''.format(self.name)) | |
- create_parser.add_argument( | |
- '--duration', | |
- type=validate_duration, | |
- default=7, | |
- help='Number of days the override should exist. If omitted, ' | |
- 'default to 7 days.') | |
- create_parser.add_argument( | |
- '--notes', | |
- default='No explanation given...', | |
- help='Optional notes on why this override is in place.') | |
- create_parser.add_argument( | |
- 'NVR', | |
- nargs='?', | |
- help='Create override from this build. If omitted, build will be' | |
- ' guessed from current release branch.') | |
- create_parser.set_defaults(command=self.create_buildroot_override) | |
- | |
- extend_parser = override_subparser.add_parser( | |
- 'extend', | |
- help='Extend buildroot override expiration', | |
- formatter_class=argparse.RawDescriptionHelpFormatter, | |
- description='''\ | |
-Extend buildroot override expiration. | |
- | |
-An override expiration date could be extended by number of days or a specific | |
-date. If override is expired, expiration date will be extended from the date | |
-of today, otherwise from the expiration date. | |
- | |
-Command extend accepts an optional build NVR to find out its override in | |
-advance. If there is no such an override created previously, please use | |
-`override create` to create one. If build NVR is omitted, command extend must | |
-run inside a package repository and build will be guessed from current release | |
-branch. | |
- | |
-Examples: | |
- | |
-1. To give 2 days to override for build somepkg-0.2-1.fc28 | |
- | |
- {0} override extend 2 somepkg-0.2-1.fc28 | |
- | |
-2. To extend expiration date to 2018-7-1 | |
- | |
- cd /path/to/somepkg | |
- {0} switch-branch f28 | |
- {0} override extend 2018-7-1 | |
-'''.format(self.name)) | |
- extend_parser.add_argument( | |
- 'duration', | |
- type=validate_extend_duration, | |
- help='Number of days to extend the expiration date, or set the ' | |
- 'expiration date directly. Valid date format: yyyy-mm-dd.') | |
- extend_parser.add_argument( | |
- 'NVR', | |
- nargs='?', | |
- help='Buildroot override expiration for this build will be ' | |
- 'extended. If omitted, build will be guessed from current ' | |
- 'release branch.') | |
- extend_parser.set_defaults(command=self.extend_buildroot_override) | |
- | |
- def register_build(self): | |
- super(fedpkgClient, self).register_build() | |
- | |
- build_parser = self.subparsers.choices['build'] | |
- build_parser.formatter_class = argparse.RawDescriptionHelpFormatter | |
- build_parser.description = '''{0} | |
- | |
-fedpkg is also able to submit multiple builds to Koji at once from stream | |
-branch based on a local config, which is inside the repository. The config file | |
-is named package.cfg in INI format. For example, | |
- | |
- [koji] | |
- targets = master fedora epel7 | |
- | |
-You only need to put Fedora releases and EPEL in option targets and fedpkg will | |
-convert it to proper Koji build target for submitting builds. Beside regular | |
-release names, option targets accepts two shortcut names as well, fedora and | |
-epel, as you can see in the above example. Name fedora stands for current | |
-active Fedora releases, and epel stands for the active EPEL releases, which are | |
-el6 and epel7 currently. | |
- | |
-Note that the config file is a branch specific file. That means you could | |
-create package.cfg for each stream branch separately to indicate on which | |
-targets to build the package for a particular stream. | |
-'''.format('\n'.join(textwrap.wrap(build_parser.description))) | |
- | |
# Target functions go here | |
def retire(self): | |
- # Skip if package is already retired... | |
- if os.path.isfile(os.path.join(self.cmd.path, 'dead.package')): | |
- self.log.warn('dead.package found, package probably already ' | |
- 'retired - will not remove files from git or ' | |
- 'overwrite existing dead.package file') | |
- else: | |
- self.cmd.retire(self.args.reason) | |
- self.push() | |
+ try: | |
+ module_name = self.cmd.module_name | |
+ ns_module_name = self.cmd.ns_module_name | |
+ namespace = ns_module_name.split(module_name)[0].rstrip('/') | |
+ # Skip if package is already retired to allow to retire only in | |
+ # pkgdb | |
+ if os.path.isfile(os.path.join(self.cmd.path, 'dead.package')): | |
+ self.log.warn('dead.package found, package probably already ' | |
+ 'retired - will not remove files from git or ' | |
+ 'overwrite existing dead.package file') | |
+ else: | |
+ self.cmd.retire(self.args.reason) | |
+ self.push() | |
+ | |
+ branch = self.cmd.branch_merge | |
+ pkgdb = pkgdb2client.PkgDB( | |
+ login_callback=pkgdb2client.ask_password, url="https://admin.rpmfusion.org/pkgdb") | |
+ pkgdb.retire_packages(module_name, branch, namespace=namespace) | |
+ except Exception as e: | |
+ self.log.error('Could not retire package: %s' % e) | |
+ sys.exit(1) | |
def _format_update_clog(self, clog): | |
''' Format clog for the update template. ''' | |
lines = [l for l in clog.split('\n') if l] | |
if len(lines) == 0: | |
return "- Rebuilt.", "" | |
elif len(lines) == 1: | |
return lines[0], "" | |
log = ["# Changelog:"] | |
log.append('# - ' + lines[0]) | |
for l in lines[1:]: | |
log.append('# ' + l) | |
log.append('#') | |
return lines[0], "\n".join(log) | |
- def _get_bodhi_config(self): | |
- try: | |
- section = '%s.bodhi' % self.name | |
- return { | |
- 'staging': self.config.getboolean(section, 'staging'), | |
- } | |
- except (ValueError, NoOptionError, NoSectionError) as e: | |
- self.log.error(str(e)) | |
- raise rpkgError('Could not get bodhi options. It seems configuration is changed. ' | |
- 'Please try to reinstall %s or consult developers to see what ' | |
- 'is wrong with it.' % self.name) | |
- | |
- @staticmethod | |
- def is_update_aborted(template_file): | |
- """Check if the update is aborted | |
- | |
- As long as the template file cannot be loaded by configparse, abort | |
- immediately. | |
- | |
- From user's perspective, it is similar with aborting commit when option | |
- -m is omitted. If all lines are commented out, abort. | |
- """ | |
- config = configparser.ConfigParser() | |
- try: | |
- loaded_files = config.read(template_file) | |
- except configparser.MissingSectionHeaderError: | |
- return True | |
- # Something wrong with the template, which causes it cannot be loaded. | |
- if not loaded_files: | |
- return True | |
- # template can be loaded even if it's empty. | |
- if not config.sections(): | |
- return True | |
- return False | |
- | |
- def _prepare_bodhi_template(self, template_file): | |
- bodhi_args = { | |
- 'nvr': self.cmd.nvr, | |
- 'bugs': six.u(''), | |
- 'descr': six.u( | |
- 'Here is where you give an explanation of your update.'), | |
- 'request': self.args.request, | |
- 'autokarma': str(self.args.autokarma), | |
- 'stable_karma': self.args.stable_karma, | |
- 'unstable_karma': self.args.unstable_karma, | |
- 'close_bugs': str(self.args.close_bugs), | |
- 'suggest_reboot': str(self.args.suggest_reboot), | |
- } | |
- | |
- if self.args.update_type: | |
- bodhi_args['type_'] = self.args.update_type | |
- else: | |
- bodhi_args['type_'] = '' | |
- | |
- self.cmd.clog() | |
- clog_file = os.path.join(self.cmd.path, 'clog') | |
- with io.open(clog_file, encoding='utf-8') as f: | |
- clog = f.read() | |
- | |
- if self.args.bugs: | |
- bodhi_args['bugs'] = self.args.bugs | |
- else: | |
- # Extract bug numbers from the latest changelog entry | |
- bugs = re.findall(r'#([0-9]*)', clog) | |
- if bugs: | |
- bodhi_args['bugs'] = ','.join(bugs) | |
- | |
- if self.args.notes: | |
- bodhi_args['descr'] = self.args.notes.replace('\n', '\n ') | |
- bodhi_args['changelog'] = '' | |
- else: | |
- # Use clog as default message | |
- bodhi_args['descr'], bodhi_args['changelog'] = \ | |
- self._format_update_clog(clog) | |
+ def update(self): | |
+ template = """\ | |
+[ %(nvr)s ] | |
- template = textwrap.dedent(BODHI_TEMPLATE) % bodhi_args | |
+# bugfix, security, enhancement, newpackage (required) | |
+type= | |
- with io.open(template_file, 'w', encoding='utf-8') as f: | |
- f.write(template) | |
+# testing, stable | |
+request=testing | |
- if not self.args.update_type or not self.args.notes: | |
- # Open the template in a text editor | |
- editor = os.getenv('EDITOR', 'vi') | |
- self.cmd._run_command([editor, template_file], shell=True) | |
+# Bug numbers: 1234,9876 | |
+bugs=%(bugs)s | |
- # Check to see if we got a template written out. Bail otherwise | |
- if not os.path.isfile(template_file): | |
- raise rpkgError('No bodhi update details saved!') | |
+%(changelog)s | |
+# Here is where you give an explanation of your update. | |
+notes=%(descr)s | |
- return not self.is_update_aborted(template_file) | |
+# Enable request automation based on the stable/unstable karma thresholds | |
+autokarma=True | |
+stable_karma=3 | |
+unstable_karma=-3 | |
- return True | |
+# Automatically close bugs when this marked as stable | |
+close_bugs=True | |
- def update(self): | |
- check_bodhi_version() | |
- bodhi_config = self._get_bodhi_config() | |
+# Suggest that users restart after update | |
+suggest_reboot=False | |
+""" | |
- bodhi_template_file = 'bodhi.template' | |
- ready = self._prepare_bodhi_template(bodhi_template_file) | |
+ bodhi_args = {'nvr': self.cmd.nvr, | |
+ 'bugs': '', | |
+ 'descr': 'Here is where you give an explanation' | |
+ ' of your update.'} | |
- if ready: | |
+ # Extract bug numbers from the latest changelog entry | |
+ self.cmd.clog() | |
+ clog = file('clog').read() | |
+ bugs = re.findall(r'#([0-9]*)', clog) | |
+ if bugs: | |
+ bodhi_args['bugs'] = ','.join(bugs) | |
+ | |
+ # Use clog as default message | |
+ bodhi_args['descr'], bodhi_args['changelog'] = \ | |
+ self._format_update_clog(clog) | |
+ | |
+ template = textwrap.dedent(template) % bodhi_args | |
+ | |
+ # Calculate the hash of the unaltered template | |
+ orig_hash = hashlib.new('sha1') | |
+ orig_hash.update(template) | |
+ orig_hash = orig_hash.hexdigest() | |
+ | |
+ # Write out the template | |
+ out = file('bodhi.template', 'w') | |
+ out.write(template) | |
+ out.close() | |
+ | |
+ # Open the template in a text editor | |
+ editor = os.getenv('EDITOR', 'vi') | |
+ self.cmd._run_command([editor, 'bodhi.template'], shell=True) | |
+ | |
+ # Check to see if we got a template written out. Bail otherwise | |
+ if not os.path.isfile('bodhi.template'): | |
+ self.log.error('No bodhi update details saved!') | |
+ sys.exit(1) | |
+ # If the template was changed, submit it to bodhi | |
+ hash = self.cmd.lookasidecache.hash_file('bodhi.template', 'sha1') | |
+ if hash != orig_hash: | |
try: | |
- self.cmd.update(bodhi_config, template=bodhi_template_file) | |
+ self.cmd.update('bodhi.template') | |
except Exception as e: | |
- # Reserve original edited bodhi template so that packager could | |
- # have a chance to recover content on error for next try. | |
- shutil.copyfile(bodhi_template_file, | |
- '{0}.last'.format(bodhi_template_file)) | |
- raise rpkgError('Could not generate update request: %s\n' | |
- 'A copy of the filled in template is saved ' | |
- 'as bodhi.template.last' % e) | |
- finally: | |
- os.unlink(bodhi_template_file) | |
- os.unlink('clog') | |
+ self.log.error('Could not generate update request: %s' % e) | |
+ sys.exit(1) | |
else: | |
self.log.info('Bodhi update aborted!') | |
- def request_repo(self): | |
- self._request_repo( | |
- repo_name=self.args.name, | |
- ns=self.args.new_repo_namespace, | |
- branch='master', | |
- summary=self.args.summary, | |
- description=self.args.description, | |
- upstreamurl=self.args.upstreamurl, | |
- monitor=self.args.monitor, | |
- bug=self.args.bug, | |
- exception=self.args.exception, | |
- name=self.name, | |
- config=self.config, | |
- initial_commit=not self.args.no_initial_commit, | |
- ) | |
- | |
- def request_tests_repo(self): | |
- self._request_repo( | |
- repo_name=self.args.name, | |
- ns='tests', | |
- description=self.args.description, | |
- name=self.name, | |
- config=self.config, | |
- anongiturl=self.cmd.anongiturl | |
- ) | |
- | |
- @staticmethod | |
- def _request_repo(repo_name, ns, description, name, config, branch=None, | |
- summary=None, upstreamurl=None, monitor=None, bug=None, | |
- exception=None, anongiturl=None, initial_commit=True): | |
- """ Implementation of `request_repo`. | |
- | |
- Submits a request for a new dist-git repo. | |
- | |
- :param repo_name: The repository name string. Typically the | |
- value of `self.cmd.repo_name`. | |
- :param ns: The repository namespace string, i.e. 'rpms' or 'modules'. | |
- Typically takes the value of `self.cmd.ns`. | |
- :param description: A string, the description of the new repo. | |
- Typically takes the value of `self.args.description`. | |
- :param name: A string representing which section of the config should be | |
- used. Typically the value of `self.name`. | |
- :param config: A dict containing the configuration, loaded from file. | |
- Typically the value of `self.config`. | |
- :param branch: The git branch string when requesting a repo. | |
- Typically 'master'. | |
- :param summary: A string, the summary of the new repo. Typically | |
- takes the value of `self.args.summary`. | |
- :param upstreamurl: A string, the upstreamurl of the new repo. | |
- Typically takes the value of `self.args.upstreamurl`. | |
- :param monitor: A string, the monitoring flag of the new repo, i.e. | |
- `'no-monitoring'`, `'monitoring'`, or `'monitoring-with-scratch'`. | |
- Typically takes the value of `self.args.monitor`. | |
- :param bug: An integer representing the bugzilla ID of a "package | |
- review" associated with this new repo. Typically takes the | |
- value of `self.args.bug`. | |
- :param exception: An boolean specifying whether or not this request is | |
- an exception to the packaging policy. Exceptional requests may be | |
- granted the right to waive their package review at the discretion of | |
- Release Engineering. Typically takes the value of | |
- `self.args.exception`. | |
- :param anongiturl: A string with the name of the anonymous git url. | |
- Typically the value of `self.cmd.anongiturl`. | |
- :return: None | |
- """ | |
- | |
- # bug is not a required parameter in the event the packager has an | |
- # exception, in which case, they may use the --exception flag | |
- # neither in case of modules, which don't require a formal review | |
- if not bug and not exception and ns not in ['tests', 'modules']: | |
- raise rpkgError( | |
- 'A Bugzilla bug is required on new repository requests') | |
- repo_regex = r'^[a-zA-Z0-9_][a-zA-Z0-9-_.+]*$' | |
- if not bool(re.match(repo_regex, repo_name)): | |
- raise rpkgError( | |
- 'The repository name "{0}" is invalid. It must be at least ' | |
- 'two characters long with only letters, numbers, hyphens, ' | |
- 'underscores, plus signs, and/or periods. Please note that ' | |
- 'the project cannot start with a period or a plus sign.' | |
- .format(repo_name)) | |
- | |
- summary_from_bug = '' | |
- if bug and ns not in ['tests', 'modules']: | |
- bz_url = config.get('{0}.bugzilla'.format(name), 'url') | |
- bz_client = BugzillaClient(bz_url) | |
- bug_obj = bz_client.get_review_bug(bug, ns, repo_name) | |
- summary_from_bug = bug_obj.summary.split(' - ', 1)[1].strip() | |
- | |
- if ns == 'tests': | |
- # check if tests repository does not exist already | |
- assert_new_tests_repo(repo_name, get_dist_git_url(anongiturl)) | |
- | |
- ticket_body = { | |
- 'action': 'new_repo', | |
- 'namespace': 'tests', | |
- 'repo': repo_name, | |
- 'description': description, | |
- } | |
- else: | |
- ticket_body = { | |
- 'action': 'new_repo', | |
- 'branch': branch, | |
- 'bug_id': bug or '', | |
- 'description': description or '', | |
- 'exception': exception, | |
- 'monitor': monitor, | |
- 'namespace': ns, | |
- 'repo': repo_name, | |
- 'summary': summary or summary_from_bug, | |
- 'upstreamurl': upstreamurl or '' | |
- } | |
- if not initial_commit: | |
- ticket_body['initial_commit'] = False | |
- | |
- ticket_body = json.dumps(ticket_body, indent=True) | |
- ticket_body = '```\n{0}\n```'.format(ticket_body) | |
- ticket_title = 'New Repo for "{0}/{1}"'.format(ns, repo_name) | |
- | |
- pagure_url = config.get('{0}.pagure'.format(name), 'url') | |
- pagure_token = get_pagure_token(config, name) | |
- print(new_pagure_issue( | |
- pagure_url, pagure_token, ticket_title, ticket_body)) | |
- | |
- def request_branch(self): | |
- if self.args.repo_name_for_branch: | |
- self.cmd.repo_name = self.args.repo_name_for_branch | |
- self.cmd.ns = self.args.repo_ns_for_branch or 'rpms' | |
+ # Clean up | |
+ os.unlink('bodhi.template') | |
+ os.unlink('clog') | |
+ | |
+if __name__ == '__main__': | |
+ client = cliClient() | |
+ client._do_imports() | |
+ client.parse_cmdline() | |
+ if not client.args.path: | |
try: | |
- active_branch = self.cmd.repo.active_branch.name | |
- except rpkgError: | |
- active_branch = None | |
- self._request_branch( | |
- service_levels=self.args.sl, | |
- all_releases=self.args.all_releases, | |
- branch=self.args.branch, | |
- active_branch=active_branch, | |
- repo_name=self.cmd.repo_name, | |
- ns=self.cmd.ns, | |
- no_git_branch=self.args.no_git_branch, | |
- no_auto_module=self.args.no_auto_module, | |
- name=self.name, | |
- config=self.config, | |
- ) | |
+ client.args.path = os.getcwd() | |
+ except: | |
+ print('Could not get current path, have you deleted it?') | |
+ sys.exit(1) | |
+ | |
+ # setup the logger -- This logger will take things of INFO or DEBUG and | |
+ # log it to stdout. Anything above that (WARN, ERROR, CRITICAL) will go | |
+ # to stderr. Normal operation will show anything INFO and above. | |
+ # Quiet hides INFO, while Verbose exposes DEBUG. In all cases WARN or | |
+ # higher are exposed (via stderr). | |
+ log = client.site.log | |
+ client.setupLogging(log) | |
+ | |
+ if client.args.v: | |
+ log.setLevel(logging.DEBUG) | |
+ elif client.args.q: | |
+ log.setLevel(logging.WARNING) | |
+ else: | |
+ log.setLevel(logging.INFO) | |
- @staticmethod | |
- def _request_branch(service_levels, all_releases, branch, active_branch, | |
- repo_name, ns, no_git_branch, no_auto_module, | |
- name, config): | |
- """ Implementation of `request_branch`. | |
- | |
- Submits a request for a new branch of a given dist-git repo. | |
- | |
- :param service_levels: A list of service level strings. Typically the | |
- value of `self.args.service_levels`. | |
- :param all_releases: A boolean indicating if this request should be made | |
- for all active Fedora branches. | |
- :param branch: A string specifying the specific branch to be requested. | |
- :param active_branch: A string (or None) specifying the active branch in | |
- the current git repo (the branch that is currently checked out). | |
- :param repo_name: The repository name string. Typically the | |
- value of `self.cmd.repo_name`. | |
- :param ns: The repository namespace string, i.e. 'rpms' or 'modules'. | |
- Typically takes the value of `self.cmd.ns`. | |
- :param no_git_branch: A boolean flag. If True, the SCM admins should | |
- create the git branch in PDC, but not in pagure.io. | |
- :param no_auto_module: A boolean flag. If True, requests for | |
- non-standard branches should not automatically result in additional | |
- requests for matching modules. | |
- :param name: A string representing which section of the config should be | |
- used. Typically the value of `self.name`. | |
- :param config: A dict containing the configuration, loaded from file. | |
- Typically the value of `self.config`. | |
- :return: None | |
- """ | |
- | |
- if all_releases: | |
- if branch: | |
- raise rpkgError('You cannot specify a branch with the ' | |
- '"--all-releases" option') | |
- elif service_levels: | |
- raise rpkgError('You cannot specify service levels with the ' | |
- '"--all-releases" option') | |
- elif not branch: | |
- if active_branch: | |
- branch = active_branch | |
- else: | |
- raise rpkgError('You must specify a branch if you are not in ' | |
- 'a git repository') | |
- | |
- pdc_url = config.get('{0}.pdc'.format(name), 'url') | |
- if branch: | |
- if is_epel(branch): | |
- assert_valid_epel_package(repo_name, branch) | |
- | |
- if ns in ['modules', 'test-modules']: | |
- branch_valid = bool(re.match(r'^[a-zA-Z0-9.\-_+]+$', branch)) | |
- if not branch_valid: | |
- raise rpkgError( | |
- 'Only characters, numbers, periods, dashes, ' | |
- 'underscores, and pluses are allowed in module branch ' | |
- 'names') | |
- release_branches = list(itertools.chain( | |
- *list(get_release_branches(pdc_url).values()))) | |
- if branch in release_branches: | |
- if service_levels: | |
- raise rpkgError( | |
- 'You can\'t provide SLs for release branches') | |
- else: | |
- if re.match(RELEASE_BRANCH_REGEX, branch): | |
- raise rpkgError('{0} is a current release branch' | |
- .format(branch)) | |
- elif not service_levels: | |
- raise rpkgError( | |
- 'You must provide SLs for non-release branches (%s)' % branch) | |
- | |
- # If service levels were provided, verify them | |
- if service_levels: | |
- sl_dict = sl_list_to_dict(service_levels) | |
- verify_sls(pdc_url, sl_dict) | |
- | |
- pagure_url = config.get('{0}.pagure'.format(name), 'url') | |
- pagure_token = get_pagure_token(config, name) | |
- if all_releases: | |
- release_branches = list(itertools.chain( | |
- *list(get_release_branches(pdc_url).values()))) | |
- branches = [b for b in release_branches | |
- if re.match(r'^(f\d+)$', b)] | |
- else: | |
- branches = [branch] | |
- | |
- for b in sorted(list(branches), reverse=True): | |
- ticket_body = { | |
- 'action': 'new_branch', | |
- 'branch': b, | |
- 'namespace': ns, | |
- 'repo': repo_name, | |
- 'create_git_branch': not no_git_branch | |
- } | |
- if service_levels: | |
- ticket_body['sls'] = sl_dict | |
- | |
- ticket_body = json.dumps(ticket_body, indent=True) | |
- ticket_body = '```\n{0}\n```'.format(ticket_body) | |
- ticket_title = 'New Branch "{0}" for "{1}/{2}"'.format( | |
- b, ns, repo_name) | |
- | |
- print(new_pagure_issue( | |
- pagure_url, pagure_token, ticket_title, ticket_body)) | |
- | |
- # For non-standard rpm branch requests, also request a matching new | |
- # module repo with a matching branch. | |
- auto_module = ( | |
- ns == 'rpms' | |
- and not re.match(RELEASE_BRANCH_REGEX, b) | |
- and not no_auto_module | |
- ) | |
- if auto_module: | |
- summary = ('Automatically requested module for ' | |
- 'rpms/%s:%s.' % (repo_name, b)) | |
- fedpkgClient._request_repo( | |
- repo_name=repo_name, | |
- ns='modules', | |
- branch='master', | |
- summary=summary, | |
- description=summary, | |
- upstreamurl=None, | |
- monitor='no-monitoring', | |
- bug=None, | |
- exception=True, | |
- name=name, | |
- config=config, | |
- ) | |
- fedpkgClient._request_branch( | |
- service_levels=service_levels, | |
- all_releases=all_releases, | |
- branch=b, | |
- active_branch=active_branch, | |
- repo_name=repo_name, | |
- ns='modules', | |
- no_git_branch=no_git_branch, | |
- no_auto_module=True, # Avoid infinite recursion. | |
- name=name, | |
- config=config, | |
- ) | |
- | |
- def create_buildroot_override(self): | |
- """Create a buildroot override in Bodhi""" | |
- check_bodhi_version() | |
- if self.args.NVR: | |
- if not self.cmd.anon_kojisession.getBuild(self.args.NVR): | |
- raise rpkgError( | |
- 'Build {0} does not exist.'.format(self.args.NVR)) | |
- bodhi_config = self._get_bodhi_config() | |
- self.cmd.create_buildroot_override( | |
- bodhi_config, | |
- build=self.args.NVR or self.cmd.nvr, | |
- duration=self.args.duration, | |
- notes=self.args.notes) | |
- | |
- def extend_buildroot_override(self): | |
- check_bodhi_version() | |
- if self.args.NVR: | |
- if not self.cmd.anon_kojisession.getBuild(self.args.NVR): | |
- raise rpkgError( | |
- 'Build {0} does not exist.'.format(self.args.NVR)) | |
- bodhi_config = self._get_bodhi_config() | |
- self.cmd.extend_buildroot_override( | |
- bodhi_config, | |
- build=self.args.NVR or self.cmd.nvr, | |
- duration=self.args.duration) | |
- | |
- def read_releases_from_local_config(self, active_releases): | |
- """Read configured releases from build config from repo""" | |
- config_file = os.path.join(self.cmd.path, LOCAL_PACKAGE_CONFIG) | |
- if not os.path.exists(config_file): | |
- self.log.warning('No local config file exists.') | |
- self.log.warning( | |
- 'Create %s to specify build targets to build.', | |
- LOCAL_PACKAGE_CONFIG) | |
- return None | |
- config = configparser.ConfigParser() | |
- if not config.read([config_file]): | |
- raise rpkgError('Package config {0} is not accessible.'.format( | |
- LOCAL_PACKAGE_CONFIG)) | |
- if not config.has_option('koji', 'targets'): | |
- self.log.warning( | |
- 'Build target is not configured. Continue to build as normal.') | |
- return None | |
- target_releases = config.get('koji', 'targets', raw=True).split() | |
- expanded_releases = [] | |
- for rel in target_releases: | |
- expanded = expand_release(rel, active_releases) | |
- if expanded: | |
- expanded_releases += expanded | |
- else: | |
- self.log.error('Target %s is unknown. Skip.', rel) | |
- return sorted(set(expanded_releases)) | |
- | |
- @staticmethod | |
- def is_stream_branch(stream_branches, name): | |
- """Determine if a branch is stream branch | |
- | |
- :param stream_branches: list of stream branches of a package. Each of | |
- them is a mapping containing name and active status, which are | |
- minimum set of properties to be included. For example, ``[{'name': | |
- '8', 'active': true}, {'name': '10', 'active': true}]``. | |
- :type stream_branches: list[dict] | |
- :param str name: branch name to check if it is a stream branch. | |
- :return: True if branch is a stream branch, False otherwise. | |
- :raises rpkgError: if branch is a stream branch but it is inactive. | |
- """ | |
- for branch_info in stream_branches: | |
- if branch_info['name'] != name: | |
- continue | |
- if branch_info['active']: | |
- return True | |
- else: | |
- raise rpkgError('Cannot build from stream branch {0} as it is ' | |
- 'inactive.'.format(name)) | |
- return False | |
- | |
- def _build(self, sets=None): | |
- if hasattr(self.args, 'chain') or self.args.scratch: | |
- return super(fedpkgClient, self)._build(sets) | |
- | |
- server_url = self.config.get('{0}.pdc'.format(self.name), 'url') | |
- | |
- stream_branches = get_stream_branches(server_url, self.cmd.repo_name) | |
- self.log.debug( | |
- 'Package %s has stream branches: %r', | |
- self.cmd.repo_name, [item['name'] for item in stream_branches]) | |
- | |
- if not self.is_stream_branch(stream_branches, self.cmd.branch_merge): | |
- return super(fedpkgClient, self)._build(sets) | |
- | |
- self.log.debug('Current branch %s is a stream branch.', | |
- self.cmd.branch_merge) | |
- | |
- releases = self.read_releases_from_local_config( | |
- get_release_branches(server_url)) | |
- | |
- if not releases: | |
- # If local config file is not created yet, or no build targets | |
- # are not configured, let's build as normal. | |
- return super(fedpkgClient, self)._build(sets) | |
- | |
- self.log.debug('Build on release targets: %r', releases) | |
- task_ids = [] | |
- for release in releases: | |
- self.cmd.branch_merge = release | |
- self.cmd.target = self.cmd.build_target(release) | |
- task_id = super(fedpkgClient, self)._build(sets) | |
- task_ids.append(task_id) | |
- return task_ids | |
- | |
- def show_releases_info(self): | |
- server_url = self.config.get('{0}.pdc'.format(self.name), 'url') | |
- releases = get_release_branches(server_url) | |
- | |
- def _join(l): | |
- return ' '.join(l) | |
- | |
- if self.args.show_epel_only: | |
- print(_join(releases['epel'])) | |
- elif self.args.show_fedora_only: | |
- print(_join(releases['fedora'])) | |
- elif self.args.join: | |
- print(' '.join(itertools.chain(releases['fedora'], | |
- releases['epel']))) | |
- else: | |
- print('Fedora: {0}'.format(_join(releases['fedora']))) | |
- print('EPEL: {0}'.format(_join(releases['epel']))) | |
+ # Run the necessary command | |
+ try: | |
+ client.args.command() | |
+ except KeyboardInterrupt: | |
+ pass |
--- fedpkg/fedpkg/__init__.py 2018-08-23 12:21:22.626622238 -0400 | |
+++ rfpkg/src/rfpkg/__init__.py 2018-08-23 12:21:38.273925248 -0400 | |
@@ -1,396 +1,394 @@ | |
-# fedpkg - a Python library for RPM Packagers | |
+# rfpkg - a Python library for RPM Packagers | |
# | |
# Copyright (C) 2011 Red Hat Inc. | |
# Author(s): Jesse Keating <jkeating@redhat.com> | |
+# Copyright (C) 2016 - Nicolas Chauvet <kwizart@gmail.com> | |
# | |
# This program is free software; you can redistribute it and/or modify it | |
# under the terms of the GNU General Public License as published by the | |
# Free Software Foundation; either version 2 of the License, or (at your | |
# option) any later version. See http://www.gnu.org/copyleft/gpl.html for | |
# the full text of the license. | |
import pyrpkg | |
import os | |
+import cli | |
import git | |
import re | |
+import rpmfusion_cert | |
import platform | |
+import subprocess | |
+import urlparse | |
+import koji | |
-from datetime import datetime, timedelta | |
-from . import cli # noqa | |
-from .lookaside import FedoraLookasideCache | |
+from .lookaside import RPMFusionLookasideCache | |
from pyrpkg.utils import cached_property | |
-try: | |
- from bodhi.client.bindings import BodhiClient as _BodhiClient | |
-except ImportError: | |
- _BodhiClient = None | |
- | |
- | |
-if _BodhiClient is not None: | |
- from fedora.client import AuthError | |
- | |
- def clear_csrf_and_retry(func): | |
- """Clear csrf token and retry | |
- | |
- fedpkg uses Bodhi Python binding API list_overrides first before other | |
- save and extend APIs. That causes a readonly csrf token is received, | |
- which will be got again when next time to construct request data to | |
- modify updates. That is not expected and AuthError will be raised. | |
- | |
- So, the solution is to capture the AuthError error, clear the token and | |
- try to modify update again by requesting another token with user's | |
- credential. | |
- """ | |
- def _decorator(self, *args, **kwargs): | |
- try: | |
- return func(self, *args, **kwargs) | |
- except AuthError: | |
- self._session.cookies.clear() | |
- self.csrf_token = None | |
- return func(self, *args, **kwargs) | |
- return _decorator | |
- | |
- class BodhiClient(_BodhiClient): | |
- """Customized BodhiClient for fedpkg""" | |
- | |
- UPDATE_TYPES = ['bugfix', 'security', 'enhancement', 'newpackage'] | |
- REQUEST_TYPES = ['testing', 'stable'] | |
- | |
- @clear_csrf_and_retry | |
- def save(self, *args, **kwargs): | |
- return super(BodhiClient, self).save(*args, **kwargs) | |
- | |
- @clear_csrf_and_retry | |
- def save_override(self, *args, **kwargs): | |
- return super(BodhiClient, self).save_override(*args, **kwargs) | |
- | |
- @clear_csrf_and_retry | |
- def extend_override(self, override, expiration_date): | |
- data = dict( | |
- nvr=override['nvr'], | |
- notes=override['notes'], | |
- expiration_date=expiration_date, | |
- edited=override['nvr'], | |
- csrf_token=self.csrf(), | |
- ) | |
- return self.send_request( | |
- 'overrides/', verb='POST', auth=True, data=data) | |
- | |
class Commands(pyrpkg.Commands): | |
- def __init__(self, *args, **kwargs): | |
+ def __init__(self, path, lookaside, lookasidehash, lookaside_cgi, | |
+ gitbaseurl, anongiturl, branchre, kojiprofile, | |
+ build_client, **kwargs): | |
"""Init the object and some configuration details.""" | |
- super(Commands, self).__init__(*args, **kwargs) | |
+ super(Commands, self).__init__(path, lookaside, lookasidehash, | |
+ lookaside_cgi, gitbaseurl, anongiturl, | |
+ branchre, kojiprofile, build_client, | |
+ **kwargs) | |
+ | |
+ # New data | |
+ self.secondary_arch = { | |
+ 'ppc': [ | |
+ 'apmud', | |
+ 'libbsr', | |
+ 'librtas', | |
+ 'libservicelog', | |
+ 'libvpd', | |
+ 'lsvpd', | |
+ 'powerpc-utils', | |
+ 'powerpc-utils-papr', | |
+ 'powerpc-utils-python', | |
+ 'ppc64-diag', | |
+ 'ppc64-utils', | |
+ 'servicelog', | |
+ 'yaboot', | |
+ ], 'arm': ['xorg-x11-drv-omapfb'], | |
+ 's390': ['s390utils', 'openssl-ibmca', 'libica', 'libzfcphbaapi'] | |
+ } | |
+ | |
+ # New properties | |
+ self._kojiprofile = None | |
+ self._cert_file = None | |
+ self._ca_cert = None | |
+ # Store this for later | |
+ self._orig_kojiprofile = kojiprofile | |
+ | |
+ # RPM Fusion default namespace | |
+ self.default_namespace = 'free' | |
+ self.namespace = None | |
+ self.hashtype = 'md5' | |
+ | |
+ # Add new properties | |
+ @property | |
+ def kojiprofile(self): | |
+ """This property ensures the kojiprofile attribute""" | |
+ | |
+ if not self._kojiprofile: | |
+ self.load_kojiprofile() | |
+ return self._kojiprofile | |
+ | |
+ @kojiprofile.setter | |
+ def kojiprofile(self, value): | |
+ self._kojiprofile = value | |
- self.source_entry_type = 'bsd' | |
+ def load_kojiprofile(self): | |
+ """This loads the kojiprofile attribute | |
- def load_user(self): | |
- """This sets the user attribute, based on the Fedora SSL cert.""" | |
- fedora_upn = os.path.expanduser('~/.fedora.upn') | |
- if os.path.exists(fedora_upn): | |
- with open(fedora_upn, 'r') as f: | |
- self._user = f.read().strip() | |
- else: | |
- self.log.debug('Could not get user from .fedora.upn, falling back' | |
- ' to default method') | |
- super(Commands, self).load_user() | |
+ This will either use the one passed in via arguments or a | |
+ secondary arch config depending on the package | |
+ """ | |
+ | |
+ # We have to allow this to work, even if we don't have a package | |
+ # we're working on, for things like gitbuildhash. | |
+ try: | |
+ null = self.module_name | |
+ except: | |
+ self._kojiprofile = self._orig_kojiprofile | |
+ return | |
+ for arch in self.secondary_arch.keys(): | |
+ if self.module_name in self.secondary_arch[arch]: | |
+ self._kojiprofile = arch | |
+ return | |
+ self._kojiprofile = self._orig_kojiprofile | |
+ | |
+ @cached_property | |
+ def cert_file(self): | |
+ """A client-side certificate for SSL authentication | |
+ | |
+ We override this from pyrpkg because we actually need a client-side | |
+ certificate. | |
+ """ | |
+ return os.path.expanduser('~/.rpmfusion.cert') | |
+ | |
+ @cached_property | |
+ def ca_cert(self): | |
+ """A CA certificate to authenticate the server in SSL connections | |
+ | |
+ We override this from pyrpkg because we actually need a custom | |
+ CA certificate. | |
+ """ | |
+ return os.path.expanduser('~/.rpmfusion-server-ca.cert') | |
+ | |
+ def load_ns_module_name(self): | |
+ """Loads a RPM Fusion package module.""" | |
+ | |
+ if self.push_url: | |
+ parts = urlparse.urlparse(self.push_url) | |
+ | |
+ if self.distgit_namespaced: | |
+ path_parts = [p for p in parts.path.split("/") if p] | |
+ ns_module_name = "/".join(path_parts[-2:]) | |
+ _ns = path_parts[-2] | |
+ | |
+ if ns_module_name.endswith('.git'): | |
+ ns_module_name = ns_module_name[:-len('.git')] | |
+ self._ns_module_name = ns_module_name | |
+ self.namespace = _ns | |
+ return | |
@cached_property | |
def lookasidecache(self): | |
"""A helper to interact with the lookaside cache | |
We override this because we need a different download path. | |
""" | |
- return FedoraLookasideCache( | |
- self.lookasidehash, self.lookaside, self.lookaside_cgi) | |
+ self.load_ns_module_name() | |
+ self._cert_file = os.path.expanduser('~/.rpmfusion.cert') | |
+ | |
+ return RPMFusionLookasideCache( | |
+ self.lookasidehash, self.lookaside, self.lookaside_cgi, | |
+ client_cert=self._cert_file, ca_cert=self._ca_cert, namespace=self.namespace) | |
# Overloaded property loaders | |
def load_rpmdefines(self): | |
"""Populate rpmdefines based on branch data""" | |
# Determine runtime environment | |
self._runtime_disttag = self._determine_runtime_env() | |
+ self.load_ns_module_name() | |
# We only match the top level branch name exactly. | |
- # Anything else is too dangerous and --dist should be used | |
+ # Anything else is too dangerous and --release should be used | |
# This regex works until after Fedora 99. | |
if re.match(r'f\d\d$', self.branch_merge): | |
self._distval = self.branch_merge.split('f')[1] | |
self._distvar = 'fedora' | |
- self._disttag = 'fc%s' % self._distval | |
- self.mockconfig = 'fedora-%s-%s' % (self._distval, self.localarch) | |
- self.override = 'f%s-override' % self._distval | |
+ self.dist = 'fc%s' % self._distval | |
+ self.mockconfig = 'fedora-%s-%s-rpmfusion_%s' % (self._distval, self.localarch, self.namespace) | |
+ self.override = 'f%s-%s-override' % (self._distval, self.namespace) | |
self._distunset = 'rhel' | |
# Works until RHEL 10 | |
- elif re.match(r'el\d$', self.branch_merge) or \ | |
- re.match(r'epel\d$', self.branch_merge): | |
+ elif re.match(r'el\d$', self.branch_merge) or re.match(r'epel\d$', self.branch_merge): | |
self._distval = self.branch_merge.split('el')[1] | |
self._distvar = 'rhel' | |
- self._disttag = 'el%s' % self._distval | |
- self.mockconfig = 'epel-%s-%s' % (self._distval, self.localarch) | |
- self.override = 'epel%s-override' % self._distval | |
+ self.dist = 'el%s' % self._distval | |
+ self.mockconfig = 'epel-%s-%s-rpmfusion_%s' % (self._distval, self.localarch, self.namespace) | |
+ self.override = 'epel%s-%s-override' % (self._distval, self.namespace) | |
self._distunset = 'fedora' | |
- elif re.match(r'olpc\d$', self.branch_merge): | |
- self._distval = self.branch_merge.split('olpc')[1] | |
- self._distvar = 'olpc' | |
- self._disttag = 'olpc%s' % self._distval | |
- self.override = 'dist-olpc%s-override' % self._distval | |
- self._distunset = 'rhel' | |
# master | |
elif re.match(r'master$', self.branch_merge): | |
self._distval = self._findmasterbranch() | |
self._distvar = 'fedora' | |
- self._disttag = 'fc%s' % self._distval | |
- self.mockconfig = 'fedora-rawhide-%s' % self.localarch | |
+ self.dist = 'fc%s' % self._distval | |
+ self.mockconfig = 'fedora-rawhide-%s-rpmfusion_%s' % (self.localarch, self.namespace) | |
self.override = None | |
self._distunset = 'rhel' | |
# If we don't match one of the above, punt | |
else: | |
raise pyrpkg.rpkgError('Could not find the release/dist from branch name ' | |
'%s\nPlease specify with --release' % | |
self.branch_merge) | |
self._rpmdefines = ["--define '_sourcedir %s'" % self.path, | |
"--define '_specdir %s'" % self.path, | |
"--define '_builddir %s'" % self.path, | |
"--define '_srcrpmdir %s'" % self.path, | |
"--define '_rpmdir %s'" % self.path, | |
- "--define 'dist .%s'" % self._disttag, | |
+ "--define 'dist .%s'" % self.dist, | |
"--define '%s %s'" % (self._distvar, | |
self._distval), | |
"--eval '%%undefine %s'" % self._distunset, | |
- "--define '%s 1'" % self._disttag] | |
+ "--define '%s 1'" % self.dist] | |
if self._runtime_disttag: | |
- if self._disttag != self._runtime_disttag: | |
+ if self.dist != self._runtime_disttag: | |
# This means that the runtime is known, and is different from | |
# the target, so we need to unset the _runtime_disttag | |
self._rpmdefines.append("--eval '%%undefine %s'" % | |
self._runtime_disttag) | |
- def build_target(self, release): | |
- if release == 'master': | |
- return 'rawhide' | |
- else: | |
- return '%s-candidate' % release | |
+ def load_target(self): | |
+ """This creates the target attribute based on branch merge""" | |
- def load_container_build_target(self): | |
+ self.load_ns_module_name() | |
if self.branch_merge == 'master': | |
- self._container_build_target = 'rawhide-%s-candidate' % self.ns | |
+ self._target = 'rawhide-%s' % self.namespace | |
else: | |
- super(Commands, self).load_container_build_target() | |
+ self._target = '%s-%s' % ( self.branch_merge , self.namespace) | |
- def _tag2version(self, dest_tag): | |
- """ get the '26' part of 'f26-foo' string """ | |
- return dest_tag.split('-')[0].replace('f', '') | |
+ def load_user(self): | |
+ """This sets the user attribute, based on the RPM Fusion SSL cert.""" | |
+ try: | |
+ self._user = rpmfusion_cert.read_user_cert() | |
+ except Exception as e: | |
+ self.log.debug('Could not read RPM Fusion cert, falling back to ' | |
+ 'default method: %s' % e) | |
+ super(Commands, self).load_user() | |
# New functionality | |
def _findmasterbranch(self): | |
- """Find the right "fedora" for master""" | |
+ """Find the right "rpmfusion" for master""" | |
# If we already have a koji session, just get data from the source | |
if self._kojisession: | |
- rawhidetarget = self.kojisession.getBuildTarget('rawhide') | |
- return self._tag2version(rawhidetarget['dest_tag_name']) | |
- | |
- # Create a list of "fedoras" | |
- fedoras = [] | |
- | |
- # Create a regex to find branches that exactly match f##. Should not | |
- # catch branches such as f14-foobar | |
- branchre = r'f\d\d$' | |
- | |
- # Find the repo refs | |
- for ref in self.repo.refs: | |
- # Only find the remote refs | |
- if type(ref) == git.RemoteReference: | |
- # Search for branch name by splitting off the remote | |
- # part of the ref name and returning the rest. This may | |
- # fail if somebody names a remote with / in the name... | |
- if re.match(branchre, ref.name.split('/', 1)[1]): | |
- # Add just the simple f## part to the list | |
- fedoras.append(ref.name.split('/')[1]) | |
- if fedoras: | |
- # Sort the list | |
- fedoras.sort() | |
- # Start with the last item, strip the f, add 1, return it. | |
- return(int(fedoras[-1].strip('f')) + 1) | |
+ rawhidetarget = self.kojisession.getBuildTarget('rawhide-free') | |
+ desttag = rawhidetarget['dest_tag_name'] | |
+ desttag=desttag.split('-') | |
+ desttag.remove('free') | |
+ desttag=''.join(desttag) | |
+ return desttag.replace('f', '') | |
else: | |
# We may not have Fedoras. Find out what rawhide target does. | |
try: | |
rawhidetarget = self.anon_kojisession.getBuildTarget( | |
- 'rawhide') | |
- except Exception: | |
+ 'rawhide-free') | |
+ except: | |
# We couldn't hit koji, bail. | |
- raise pyrpkg.rpkgError( | |
- 'Unable to query koji to find rawhide target') | |
- return self._tag2version(rawhidetarget['dest_tag_name']) | |
+ raise pyrpkg.rpkgError('Unable to query koji to find rawhide \ | |
+ target') | |
+ desttag = rawhidetarget['dest_tag_name'] | |
+ desttag=desttag.split('-') | |
+ desttag.remove('free') | |
+ desttag=''.join(desttag) | |
+ return desttag.replace('f', '') | |
def _determine_runtime_env(self): | |
"""Need to know what the runtime env is, so we can unset anything | |
conflicting | |
""" | |
+ | |
try: | |
- runtime_os, runtime_version, _ = platform.linux_distribution() | |
- except Exception: | |
- return None | |
+ mydist = platform.linux_distribution() | |
+ except: | |
+ # This is marked as eventually being deprecated. | |
+ try: | |
+ mydist = platform.dist() | |
+ except: | |
+ runtime_os = 'unknown' | |
+ runtime_version = '0' | |
+ | |
+ if mydist: | |
+ runtime_os = mydist[0] | |
+ runtime_version = mydist[1] | |
+ else: | |
+ runtime_os = 'unknown' | |
+ runtime_version = '0' | |
if runtime_os in ['redhat', 'centos']: | |
return 'el%s' % runtime_version | |
if runtime_os == 'Fedora': | |
return 'fc%s' % runtime_version | |
- if (runtime_os == 'Red Hat Enterprise Linux Server' or | |
- runtime_os.startswith('CentOS')): | |
- return 'el{0}'.format(runtime_version.split('.')[0]) | |
- | |
- def check_inheritance(self, build_target, dest_tag): | |
- """Disable check inheritance | |
- Tag inheritance check is not required in Fedora when make chain build | |
- in Koji. | |
- """ | |
+ # fall through, return None | |
+ return None | |
- def construct_build_url(self, *args, **kwargs): | |
- """Override build URL for Fedora Koji build | |
+ def construct_build_url(self): | |
+ """Override build URL for RPM Fusion Koji build | |
- In Fedora Koji, anonymous URL should have prefix "git+https://" | |
+ In RPM Fusion Koji, anonymous URL should have prefix "git+https://" | |
""" | |
- url = super(Commands, self).construct_build_url(*args, **kwargs) | |
- return 'git+{0}'.format(url) | |
+ url = super(Commands, self).construct_build_url() | |
+ if not url.startswith('git'): | |
+ url = 'git+{0}'.format(url) | |
+ return url | |
def retire(self, message): | |
"""Delete all tracked files and commit a new dead.package file | |
Use optional message in commit. | |
Runs the commands and returns nothing | |
""" | |
cmd = ['git'] | |
if self.quiet: | |
cmd.append('--quiet') | |
cmd.extend(['rm', '-rf', '.']) | |
self._run_command(cmd, cwd=self.path) | |
fd = open(os.path.join(self.path, 'dead.package'), 'w') | |
fd.write(message + '\n') | |
fd.close() | |
cmd = ['git', 'add', os.path.join(self.path, 'dead.package')] | |
self._run_command(cmd, cwd=self.path) | |
self.commit(message=message) | |
- def update(self, bodhi_config, template='bodhi.template', bugs=[]): | |
+ def update(self, template='bodhi.template', bugs=[]): | |
"""Submit an update to bodhi using the provided template.""" | |
- bodhi = BodhiClient(username=self.user, | |
- staging=bodhi_config['staging']) | |
- | |
- update_details = bodhi.parse_file(template) | |
- for detail in update_details: | |
- if not detail['type']: | |
- raise ValueError( | |
- 'Missing update type, which is required to create update.') | |
- if detail['type'] not in BodhiClient.UPDATE_TYPES: | |
- raise ValueError( | |
- 'Incorrect update type {0}'.format(detail['type'])) | |
- if detail['request'] not in BodhiClient.REQUEST_TYPES: | |
- raise ValueError( | |
- 'Incorrect request type {0}'.format(detail['request'])) | |
- | |
- bodhi.save(**detail) | |
- | |
- def create_buildroot_override(self, bodhi_config, build, duration, | |
- notes=''): | |
- bodhi = BodhiClient(username=self.user, | |
- staging=bodhi_config['staging']) | |
- result = bodhi.list_overrides(builds=build) | |
- if result['total'] == 0: | |
- try: | |
- self.log.debug( | |
- 'Create override in %s: nvr=%s, duration=%s, notes="%s"', | |
- 'staging Bodhi' if bodhi_config['staging'] else 'Bodhi', | |
- build, duration, notes) | |
- override = bodhi.save_override( | |
- nvr=build, duration=duration, notes=notes) | |
- except Exception as e: | |
- self.log.error(str(e)) | |
- raise pyrpkg.rpkgError('Cannot create override.') | |
- else: | |
- self.log.info(bodhi.override_str(override, minimal=False)) | |
+ # build up the bodhi arguments, based on which version of bodhi is | |
+ # installed | |
+ bodhi_major_version = _get_bodhi_version()[0] | |
+ if bodhi_major_version < 2: | |
+ cmd = ['bodhi', '--new', '--release', self.branch_merge, | |
+ '--file', 'bodhi.template', self.nvr, '--username', | |
+ self.user] | |
+ elif bodhi_major_version == 2: | |
+ cmd = ['bodhi', 'updates', 'new', '--file', 'bodhi.template', | |
+ '--user', self.user, self.nvr] | |
else: | |
- override = result['overrides'][0] | |
- expiration_date = datetime.strptime(override['expiration_date'], | |
- '%Y-%m-%d %H:%M:%S') | |
- if expiration_date < datetime.utcnow(): | |
- self.log.info( | |
- 'Buildroot override for %s exists and is expired. Consider' | |
- ' using command `override extend` to extend duration.', | |
- build) | |
- else: | |
- self.log.info('Buildroot override for %s already exists and ' | |
- 'not expired.', build) | |
+ msg = 'This system has bodhi v{0}, which is unsupported.' | |
+ msg = msg.format(bodhi_major_version) | |
+ raise Exception(msg) | |
+ self._run_command(cmd, shell=True) | |
- def extend_buildroot_override(self, bodhi_config, build, duration): | |
- bodhi = BodhiClient(username=self.user, | |
- staging=bodhi_config['staging']) | |
- result = bodhi.list_overrides(builds=build) | |
+ def load_kojisession(self, anon=False): | |
+ """Initiate a koji session. | |
- if result['total'] == 0: | |
- self.log.info('No buildroot override for build %s', build) | |
- return | |
+ The koji session can be logged in or anonymous | |
+ """ | |
+ koji_config = self.read_koji_config() | |
- override = result['overrides'][0] | |
- expiration_date = datetime.strptime(override['expiration_date'], | |
- '%Y-%m-%d %H:%M:%S') | |
- utcnow = datetime.utcnow() | |
- | |
- # bodhi-client binding API save_override calculates expiration | |
- # date by adding duration to datetime.utcnow | |
- # This comparison should use utcnow as well. | |
- if expiration_date < utcnow: | |
- self.log.debug('Buildroot override is expired on %s', | |
- override['expiration_date']) | |
- self.log.debug('Extend expiration date from today in UTC.') | |
- base_date = utcnow | |
- else: | |
- self.log.debug( | |
- 'Extend expiration date from future expiration date.') | |
- base_date = expiration_date | |
- | |
- if isinstance(duration, datetime): | |
- if duration < utcnow: | |
- raise pyrpkg.rpkgError( | |
- 'At least, specified expiration date {0} should be ' | |
- 'future date.'.format(duration.strftime('%Y-%m-%d'))) | |
- if duration < base_date: | |
- self.log.warning( | |
- 'Expiration date %s to be set is before override current' | |
- ' expiration date %s', | |
- duration, base_date) | |
- # Keep time unchanged | |
- new_expiration_date = datetime( | |
- year=duration.year, | |
- month=duration.month, | |
- day=duration.day, | |
- hour=base_date.hour, | |
- minute=base_date.minute, | |
- second=base_date.second) | |
- else: | |
- new_expiration_date = base_date + timedelta(days=duration) | |
+ # Expand out the directory options | |
+ for name in ('cert', 'ca', 'serverca'): | |
+ path = koji_config[name] | |
+ if path: | |
+ koji_config[name] = os.path.expanduser(path) | |
+ | |
+ # save the weburl and topurl for later use as well | |
+ self._kojiweburl = koji_config['weburl'] | |
+ self._topurl = koji_config['topurl'] | |
+ | |
+ self.log.debug('Initiating a %s session to %s', | |
+ os.path.basename(self.build_client), koji_config['server']) | |
+ | |
+ # Build session options used to create instance of ClientSession | |
+ session_opts = koji.grab_session_options(koji_config) | |
try: | |
- self.log.debug('Extend override expiration date to %s', | |
- new_expiration_date) | |
- override = bodhi.extend_override(override, new_expiration_date) | |
- except Exception as e: | |
- self.log.error('Cannot extend override expiration.') | |
- raise pyrpkg.rpkgError(str(e)) | |
+ session = koji.ClientSession(koji_config['server'], session_opts) | |
+ except Exception: | |
+ raise rpkgError('Could not initiate %s session' % os.path.basename(self.build_client)) | |
else: | |
- self.log.info(bodhi.override_str(override, minimal=False)) | |
+ if anon: | |
+ self._anon_kojisession = session | |
+ else: | |
+ self._kojisession = session | |
+ | |
+ if not anon: | |
+ try: | |
+ self.login_koji_session(koji_config, self._kojisession) | |
+ except pyrpkg.rpkgAuthError: | |
+ self.log.info("You might want to run rpmfusion-packager-setup to " | |
+ "regenerate SSL certificate. For more info see " | |
+ "https://fedoraproject.org/wiki/Using_the_Koji_build" | |
+ "_system#Fedora_Account_System_.28FAS2.29_Setup") | |
+ raise | |
+ | |
+ | |
+def _get_bodhi_version(): | |
+ """ | |
+ Use bodhi --version to determine the version of the Bodhi CLI that's | |
+ installed on the system, then return a list of the version components. | |
+ For example, if bodhi --version returns "2.1.9", this function will return | |
+ [2, 1, 9]. | |
+ """ | |
+ bodhi = subprocess.Popen(['bodhi', '--version'], stdout=subprocess.PIPE) | |
+ version = bodhi.communicate()[0].strip() | |
+ return [int(component) for component in version.split('.')] | |
if __name__ == "__main__": | |
- from fedpkg.__main__ import main | |
+ from rfpkg.__main__ import main | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment