#170 Port fedrepo-req and fedrepo-req-branch to fedpkg
Merged 6 years ago by cqi. Opened 6 years ago by mprahl.
Unknown source port-fedrepo-req  into  master

@@ -33,13 +33,14 @@

      # global options

  

      local options="--help -v -q"

-     local options_value="--dist --release --user --path"

+     local options_value="--dist --release --user --path --user-config"

      local commands="build chain-build ci clean clog clone co commit compile \

      container-build diff gimmespec giturl help gitbuildhash import install lint \

      local mockbuild mock-config module-build module-build-cancel \

      module-build-local module-build-info module-build-watch module-overview \

-     new new-sources patch prep pull push retire scratch-build sources srpm \

-     switch-branch tag unused-patches update upload verify-files verrel"

+     new new-sources patch prep pull push retire request-branch request-repo \

+     scratch-build sources srpm switch-branch tag unused-patches update upload \

+     verify-files verrel"

  

      # parse main options and get command

  
@@ -189,6 +190,13 @@

          retire)

              after_more=true

              ;;

+         request-branch)

+             options="--no-git-branch --all-releases"

+             options_string="--sl"

+         request-repo)

+             options="--exception"

+             options_string="--description --monitor --upstreamurl --summary"

+             ;;

          scratch-build)

              options="--nowait --background"

              options_target="--target"

@@ -29,3 +29,13 @@

  oidc_client_id = mbs-authorizer

  oidc_client_secret = notsecret

  oidc_scopes = openid,https://id.fedoraproject.org/scope/groups,https://mbs.fedoraproject.org/oidc/submit-build

+ 

+ [fedpkg-stage.bugzilla]

+ # Use production Bugzilla for read-only data

+ url = https://bugzilla.redhat.com/

+ 

+ [fedpkg-stage.pagure]

+ url = https://stg.pagure.io/

+ 

+ [fedpkg-stage.pdc]

+ url = https://pdc.stg.fedoraproject.org/

@@ -34,3 +34,12 @@

  oidc_client_id = mbs-authorizer

  oidc_client_secret = notsecret

  oidc_scopes = openid,https://id.fedoraproject.org/scope/groups,https://mbs.fedoraproject.org/oidc/submit-build

+ 

+ [fedpkg.bugzilla]

+ url = https://bugzilla.redhat.com/

+ 

+ [fedpkg.pagure]

+ url = https://pagure.io/

+ 

+ [fedpkg.pdc]

+ url = https://pdc.fedoraproject.org/

@@ -302,6 +302,28 @@

      '(-h --help)'{-h,--help}'[show help message and exit]'

  }

  

+ (( $+functions[_fedpkg-request-branch] )) ||

+ _fedpkg-request-branch () {

+   _arguments -C \

+     '(-h --help)'{-h,--help}'[show help message and exit]' \

+     '(--sl)'{--sl}'[The service levels (SLs) tied to the branch]' \

+     '--no-git-branch[Do not create the branch in git but still create it in PDC]:optional' \

+     '--all-releases[Make a new branch request for every active Fedora release]:optional' \

+     ':branch'

+ }

+ 

+ (( $+functions[_fedpkg-request-repo] )) ||

+ _fedpkg-request-repo () {

+   _arguments -C \

+     '(-h --help)'{-h,--help}'[show help message and exit]' \

+     '(-d --description)'{-d,--description}'[The description in dist-git]' \

+     '(-m --monitor)'{-m,--monitor}'[The Koshei monitoring type for the repo]' \

+     '(-u --upstreamurl)'{-u,--upstreamurl}'[The upstream URL of the project]' \

+     '(-s --summary)'{-s,--summary}'[Override the package summary from the Bugzilla bug]' \

+     '--exception[The package is an exception to the regular package review process]:optional' \

+     ':bug'

+ }

+ 

  (( $+functions[_fedpkg-scratch-build] )) ||

  _fedpkg-scratch-build () {

    _arguments -C \
@@ -426,6 +448,8 @@

      prep:'local test rpmbuild prep'

      pull:'pull changes from remote repository and update working copy'

      push:'push changes to remote repository'

+     request-branch:'request a new dist-git branch'

+     request-repo:'request a new dist-git repository'

      scratch-build:'request scratch build'

      sources:'download source files'

      srpm:'create a source rpm'
@@ -462,6 +486,7 @@

    _arguments -C \

      '(- :)'{-h,--help}'[show help message and exit]' \

      '(-C --config)'{-C,--config}'[specify a config file to use]:config file:_files' \

+     '--user-config[specify a user config file to use]:user config file:_files' \

      '--dist[override the discovered distribution]:distribution' \

      '--release[override the discovered release]:release' \

      '--user[override the discovered user name]:user' \

file modified
+7
@@ -15,6 +15,7 @@

  import sys

  

  import fedpkg

+ import fedpkg.utils

  import pyrpkg

  import pyrpkg.utils

  
@@ -29,10 +30,15 @@

  

  

  def main():

+     default_user_config_path = os.path.join(

+         os.path.expanduser('~'), '.config', 'rpkg', '%s.conf' % cli_name)

      # Setup an argparser and parse the known commands to get the config file

      parser = argparse.ArgumentParser(add_help=False)

      parser.add_argument('-C', '--config', help='Specify a config file to use',

                          default='/etc/rpkg/%s.conf' % cli_name)

+     parser.add_argument(

+         '--user-config', help='Specify a user config file to use',

+         default=default_user_config_path)
cqi commented 6 years ago

--user--config does not appear in -h output, e.g. fedpkg -h.

cqi commented 6 years ago

s/--user--config/--user-config/

Fixed

  

      (args, other) = parser.parse_known_args()

  
@@ -45,6 +51,7 @@

      # Setup a configuration object and read config file data

      config = ConfigParser()

      config.read(args.config)

+     config.read(args.user_config)

  

      client = fedpkg.cli.fedpkgClient(config, name=cli_name)

      client.do_imports(site='fedpkg')

file added
+116
@@ -0,0 +1,116 @@

+ # -*- coding: utf-8 -*-

+ # cli.py - a cli client class module for fedpkg

+ #

+ # Copyright (C) 2017 Red Hat Inc.

+ # Author(s): Matt Prahl <mprahl@redhat.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.

+ 

+ # So that we import the bugzilla package and not fedpkg.bugzilla

+ from __future__ import absolute_import

+ from datetime import datetime

+ 

+ import bugzilla

+ from pyrpkg import rpkgError

+ 

+ 

+ class BugzillaClient(object):

+     """A Bugzilla helper class"""

+     api_url = None

+     _client = None

+ 

+     def __init__(self, url):

+         self.api_url = '{0}/xmlrpc.cgi'.format(url.rstrip('/'))

+ 

+     @property

+     def client(self):

+         """

+         Only connect to Bugzilla when the client property is first used. This

+         will make the unit tests less complicated and shorten the connections

+         to the Bugzilla server.

+         """

+         if not self._client:

+             # use_creds is only available in python-bugzilla 2.0+

+             try:

+                 self._client = bugzilla.Bugzilla(self.api_url, use_creds=False)

+             except TypeError:

+                 self._client = bugzilla.Bugzilla(self.api_url)

+ 

+         return self._client

+ 

+     def get_review_bug(self, bug_id, namespace, pkg):

+         """

+         Gets a Bugzilla bug representing a Fedora package review and does as

+         much validation as it can without authenticating to Bugzilla. This

+         function was inspired by:

+         https://github.com/fedora-infra/pkgdb2/blob/master/pkgdb2/api/extras.py

+         https://pagure.io/pkgdb-cli/blob/master/f/pkgdb2client/utils.py

+         :param bug_id: string or integer of the Bugzilla bug ID

+         :param namespace: string of the dist-git namespace

+         :param pkg: string of the package name

+         """

+         try:

+             bug = self.client.getbug(bug_id)

+         except Exception as error:

+             raise rpkgError(

+                 'The Bugzilla bug could not be verified. The following '

+                 'error was encountered: {0}'.format(str(error)))

+ 

+         # Do some basic validation on the bug

+         pagure_namespace_to_component = {

+             'rpms': 'Package Review',

+             'container': 'Container Review',

+             'modules': 'Module Review',

+             'test-modules': 'Module Review'

+         }

+         pagure_namespace_to_product = {

+             'rpms': ['Fedora', 'Fedora EPEL'],

+             'container': ['Fedora Container Images'],

+             'modules': ['Fedora Modules'],

+             'test-modules': ['Fedora']

+         }

+         bz_proper_component = pagure_namespace_to_component.get(namespace)

+         bz_proper_products = pagure_namespace_to_product.get(namespace)

+         if bz_proper_component is None or bug.component != bz_proper_component:

+             raise rpkgError('The Bugzilla bug provided is not the proper type')

+         elif bug.product not in bz_proper_products:

+             raise rpkgError('The Bugzilla bug provided is not for "{0}"'

+                             .format('" or "'.join(bz_proper_products)))

+         elif bug.assigned_to in ['', None, 'nobody@fedoraproject.org']:

+             raise rpkgError(

+                 'The Bugzilla bug provided is not assigned to anyone')

+         # Check if the review was approved

+         flag_set = False

+         for flag in bug.flags:

+             name, status = flag.get('name'), flag.get('status')

+             if name == 'fedora-review' and status == '+':

+                 flag_set = True

+                 update_dt = flag.get('modification_date')

+                 if update_dt:

+                     dt = datetime.strptime(

+                         update_dt.value, '%Y%m%dT%H:%M:%S')

+                     delta = datetime.utcnow().date() - dt.date()

+                     if delta.days > 60:

+                         raise rpkgError('The Bugzilla bug\'s review was '

+                                         'approved over 60 days ago')

+                 break

+         if not flag_set:

+             raise rpkgError('The Bugzilla bug is not approved yet')

+         # Check the format of the Bugzilla bug title

+         tmp_summary = bug.summary.partition(':')[2]

+         if not tmp_summary:

+             raise rpkgError(

+                 'Invalid title for this Bugzilla bug (no ":" present)')

+         if ' - ' not in tmp_summary:

+             raise rpkgError(

+                 'Invalid title for this Bugzilla bug (no "-" present)')

+         pkg_in_bug = tmp_summary.split(' - ', 1)[0].strip()

+         if pkg != pkg_in_bug:

+             error = ('The package in the Bugzilla bug "{0}" doesn\'t match '

+                      'the one provided "{1}"'.format(pkg_in_bug, pkg))

+             raise rpkgError(error)

+         return bug

file modified
+183
@@ -10,16 +10,22 @@

  # 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 hashlib

  import os

  import re

+ import json

  import six

  import textwrap

  

  from six.moves.configparser import NoSectionError

  from six.moves.configparser import NoOptionError

  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)

  

  

  class fedpkgClient(cliClient):
@@ -31,6 +37,10 @@

      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. ' \
@@ -41,6 +51,8 @@

  

          self.register_retire()

          self.register_update()

+         self.register_request_repo()

+         self.register_request_branch()

  

      # Target registry goes here

      def register_retire(self):
@@ -65,6 +77,55 @@

          )

          update_parser.set_defaults(command=self.update)

  

+     def register_request_repo(self):

+         help_msg = 'Request a new dist-git repository'

+         request_repo_parser = self.subparsers.add_parser(

+             'request-repo', help=help_msg, description=help_msg)

+         request_repo_parser.add_argument(

+             'bug', nargs='?', type=int,

+             help='Bugzilla bug ID of the package review request')

+         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 Koshei 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.set_defaults(command=self.request_repo)

+ 

+     def register_request_branch(self):

+         help_msg = 'Request a new dist-git branch'

+         request_branch_parser = self.subparsers.add_parser(

+             'request-branch', help=help_msg, description=help_msg)

+         request_branch_parser.add_argument(

+             'branch', nargs='?', help='The branch to request')

+         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(

+             '--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)

+ 

      # Target functions go here

      def retire(self):

          # Skip if package is already retired...
@@ -191,3 +252,125 @@

          # Clean up

          os.unlink('bodhi.template')

          os.unlink('clog')

+ 

+     def request_repo(self):

+         # bug is not a required parameter in the event the packager has an

+         # exception, in which case, they may use the --exception flag

+         if not self.args.bug and not self.args.exception:

+             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, self.cmd.module_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(self.cmd.module_name))

+ 

+         summary_from_bug = ''

+         if self.args.bug:

+             bz_url = self.config.get('{0}.bugzilla'.format(self.name), 'url')

+             bz_client = BugzillaClient(bz_url)

+             bug = bz_client.get_review_bug(

+                 self.args.bug, self.cmd.ns, self.cmd.module_name)

+             summary_from_bug = bug.summary.split(' - ', 1)[1].strip()

+ 

+         ticket_body = {

+             'action': 'new_repo',

+             'branch': 'master',

+             'bug_id': self.args.bug or '',

+             'description': self.args.description or '',

+             'exception': self.args.exception,

+             'monitor': self.args.monitor,

+             'namespace': self.cmd.ns,

+             'repo': self.cmd.module_name,

+             'summary': self.args.summary or summary_from_bug,

+             'upstreamurl': self.args.upstreamurl or ''

+         }

+ 

+         ticket_body = json.dumps(ticket_body, indent=True)

+         ticket_body = '```\n{0}\n```'.format(ticket_body)

+         ticket_title = 'New Repo for "{0}/{1}"'.format(

+             self.cmd.ns, self.cmd.module_name)

+ 

+         pagure_url = self.config.get('{0}.pagure'.format(self.name), 'url')

+         pagure_token = get_pagure_token(self.config, self.name)

+         print(new_pagure_issue(

+             pagure_url, pagure_token, ticket_title, ticket_body))

+ 

+     def request_branch(self):

+         service_levels = self.args.sl

+         branch = None

+ 

+         if self.args.all_releases:

+             if self.args.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 self.args.branch:

+             try:

+                 branch = self.cmd.repo.active_branch.name

+             except rpkgError:

+                 raise rpkgError('You must specify a branch if you are not in '

+                                 'a git repository')

+         else:

+             branch = self.args.branch

+ 

+         bodhi_url = self.config.get('{0}.bodhi'.format(self.name), 'url')

+         if branch:

+             if self.cmd.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 = get_release_branches(bodhi_url)

+             if branch in release_branches:

+                 if service_levels:

+                     raise rpkgError(

+                         'You can\'t provide SLs for release branches')

+             else:

+                 if re.match(r'^(f\d+|el\d+|epel\d+)$', branch):

+                     raise rpkgError('{0} is not a current release branch'

+                                     .format(branch))

+                 elif not service_levels:

+                     raise rpkgError(

+                         'You must provide SLs for non-release branches')

+ 

+         # If service levels were provided, verify them

+         if service_levels:

+             pdc_url = self.config.get('{0}.pdc'.format(self.name), 'url')

+             sl_dict = sl_list_to_dict(service_levels)

+             verify_sls(pdc_url, sl_dict)

+ 

+         pagure_url = self.config.get('{0}.pagure'.format(self.name), 'url')

+         pagure_token = get_pagure_token(self.config, self.name)

+         if self.args.all_releases:

+             release_branches = get_release_branches(bodhi_url)

+             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': self.cmd.ns,

+                 'repo': self.cmd.module_name,

+                 'create_git_branch': not self.args.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, self.cmd.ns, self.cmd.module_name)

+ 

+             print(new_pagure_issue(

+                 pagure_url, pagure_token, ticket_title, ticket_body))

file added
+187
@@ -0,0 +1,187 @@

+ # -*- coding: utf-8 -*-

+ # cli.py - a cli client class module for fedpkg

+ #

+ # Copyright (C) 2017 Red Hat Inc.

+ # Author(s): Matt Prahl <mprahl@redhat.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 re

+ import json

+ from datetime import datetime

+ 

+ from six.moves.urllib.parse import urlencode

+ from six.moves.configparser import NoSectionError, NoOptionError

+ import requests

+ from requests.exceptions import ConnectionError

+ from fedora.client.bodhi import Bodhi2Client

+ from pyrpkg import rpkgError

+ 

+ 

+ def get_sl_type(url, sl_name):

+     """

+     Gets the service level (SL) type from PDC

+     :param url: a string of the URL to PDC

+     :param sl_name: a string of the SL name

+     :return: a dictionary representing the SL type or None

+     """

+     api_url = '{0}/rest_api/v1/component-sla-types/'.format(url.rstrip('/'))

+     api_url_w_args = '{0}?{1}'.format(api_url, urlencode({'name': sl_name}))

+     try:

+         rv = requests.get(api_url_w_args, timeout=60)

+     except ConnectionError as error:

+         error_msg = ('The connection to PDC failed while trying to validate '

+                      'the passed in service level. The error was: {0}'

+                      .format(str(error)))

+         raise rpkgError(error_msg)

+ 

+     if not rv.ok:

+         base_error_msg = ('The following error occurred while validating the '

+                           'passed in service level in PDC: {0}')

+         raise rpkgError(base_error_msg.format(rv.text))

+ 

+     rv_json = rv.json()

+     if rv_json['count'] == 1:

+         return rv_json['results'][0]

+     else:

+         return None

+ 

+ 

+ def new_pagure_issue(url, token, title, body):

+     """

+     Posts a new Pagure issue

+     :param url: a string of the URL to Pagure

+     :param token: a string of the Pagure API token that has rights to create

+     a ticket

+     :param title: a string of the issue's title

+     :param body: a string pf the issue's body

+     :return: a string of the URL to the created issue in the UI

+     """

+     api_url = '{0}/api/0'.format(url.rstrip('/'))

+     new_issue_url = '{0}/releng/fedora-scm-requests/new_issue'.format(api_url)

+ 

+     headers = {

+         'Authorization': 'token {0}'.format(token),

+         'Accept': 'application/json',

+         'Content-Type': 'application/json'

+     }

+     payload = json.dumps({

+         'title': title,

+         'issue_content': body

+     })

+     try:

+         rv = requests.post(

+             new_issue_url, headers=headers, data=payload, timeout=60)

+     except ConnectionError as error:

+         error_msg = ('The connection to Pagure failed while trying to '

+                      'create a new issue. The error was: {0}'.format(

+                          str(error)))

+         raise rpkgError(error_msg)

+ 

+     base_error_msg = ('The following error occurred while creating a new '

+                       'issue in Pagure: {0}')

+     if not rv.ok:

+         # Lets see if the API returned an error message in JSON that we can

+         # show the user

+         try:

+             rv_error = rv.json().get('error')

+         except ValueError:

+             rv_error = rv.text

+         raise rpkgError(base_error_msg.format(rv_error))

+ 

+     return '{0}/releng/fedora-scm-requests/issue/{1}'.format(

+         url.rstrip('/'), rv.json()['issue']['id'])

+ 

+ 

+ def get_release_branches(bodhi_url):

+     """

+     Get the active Fedora release branches from Bodhi

+     :param bodhi_url: a string of the URL to Bodhi

+     :return: a set containing the active Fedora release branches

+     """

+     bodhi = Bodhi2Client(bodhi_url)

+     branches = set()

+     page = 1

+     while True:

+         rv = bodhi.send_request('releases', auth=False, params={'page': page})

+         for release in rv['releases']:

+             if release['state'] == 'current':

+                 branches.add(release['branch'])

+         if page < rv['pages']:

+             page += 1

+         else:

+             break

+ 

+     return branches

+ 

+ 

+ def sl_list_to_dict(sls):

+     """

+     Takes a list of SLs and returns them in a dictionary format. Any errors in

+     the SLs will be raised as an rpkgError.

+     :param sls: list of SLs in the format of sl_name:2017-12-25

+     :return: dictionary in the format of {'sl_name': '2017-12-25'}

+     """

+     sl_dict = {}

+     # Ensures the SL is in the format "security_fixes:2020-01-01"

+     sl_regex = re.compile(r'^(.+)(?:\:)(\d{4}-\d{2}-\d{2})$')

+     for sl in sls:

+         sl_match = re.match(sl_regex, sl)

+         if sl_match:

+             sl_name = sl_match.groups()[0]

+             sl_date = sl_match.groups()[1]

+             sl_dict[sl_name] = sl_date

+         else:

+             raise rpkgError(

+                 'The SL "{0}" is in an invalid format'.format(sl))

+ 

+     return sl_dict

+ 

+ 

+ def verify_sls(pdc_url, sl_dict):

+     """

+     Verifies that the service levels are properly formatted and exist in PDC

+     :param pdc_url: a string of the URL to PDC

+     :param sl_dict: a dictionary with the SLs of the request

+     :return: None or ValidationError

+     """

+     # Make sure the EOL date is in the format of 2020-12-01

+     eol_date_regex = re.compile(r'\d{4}-\d{2}-\d{2}')

+     for sl, eol in sl_dict.items():

+         if re.match(eol_date_regex, eol):

+             eol_date = datetime.strptime(eol, '%Y-%m-%d').date()

+             today = datetime.utcnow().date()

+             if eol_date < today:

+                 raise rpkgError(

+                     'The SL "{0}" is already expired'.format(eol))

+             elif eol_date.month not in [6, 12] or eol_date.day != 1:

+                 raise rpkgError(

+                     'The SL "{0}" must expire on June 1st or December 1st'

+                     .format(eol))

+         else:

+             raise rpkgError(

+                 'The EOL date "{0}" is in an invalid format'.format(eol))

+ 

+         sl_obj = get_sl_type(pdc_url, sl)

+         if not sl_obj:

+             raise rpkgError('The SL "{0}" is not in PDC'.format(sl))

+ 

+ 

+ def get_pagure_token(config, cli_name):

+     """

+     Gets the Pagure token configured in the user's configuration file

+     :param config: ConfigParser object

+     :param cli_name: string of the CLI's name (e.g. fedpkg)

+     :return: string of the Pagure token

+     """

+     conf_section = '{0}.pagure'.format(cli_name)

+     try:

+         return config.get(conf_section, 'token')

+     except (NoSectionError, NoOptionError):

+         raise rpkgError('The "token" value must be set under the "{0}" '

+                         'section in your "{1}" user configuration'

+                         .format(conf_section, cli_name))

file modified
+10 -1
@@ -12,4 +12,13 @@

  

  [fedpkg-stage.bodhi]

  url = https://bodhi.stg.example.com/

- staging = True 

\ No newline at end of file

+ staging = True

+ 

+ [fedpkg-stage.bugzilla]

+ url = https://bugzilla.stg.example.com

+ 

+ [fedpkg-stage.pagure]

+ url = https://pagure.stg.example.com/

+ 

+ [fedpkg-stage.pdc]

+ url = https://pdc.stg.example.com/

file modified
+10 -1
@@ -12,4 +12,13 @@

  

  [fedpkg.bodhi]

  url = https://bodhi.dummy.example.com/

- staging = False 

\ No newline at end of file

+ staging = False

+ 

+ [fedpkg.bugzilla]

+ url = https://bugzilla.example.com

+ 

+ [fedpkg.pagure]

+ url = https://pagure.example.com/

+ 

+ [fedpkg.pdc]

+ url = https://pdc.example.com/

@@ -0,0 +1,2 @@

+ [fedpkg-stage.pagure]

+ token = iamnotsecret

@@ -0,0 +1,2 @@

+ [fedpkg.pagure]

+ token = iamnotsecret

file modified
+775 -2
@@ -10,15 +10,22 @@

  # option) any later version.  See http://www.gnu.org/copyleft/gpl.html for

  # the full text of the license.

  

- import six

+ import sys

+ import json

+ from datetime import datetime, timedelta

+ from tempfile import mkdtemp

+ from os import rmdir

  

+ import six

  from six.moves.configparser import NoOptionError

  from six.moves.configparser import NoSectionError

+ from six.moves import StringIO

  

  from pyrpkg.errors import rpkgError

  from utils import CliTestCase

+ from fedpkg.bugzilla import BugzillaClient

  

- from mock import call, patch, mock_open, PropertyMock

+ from mock import call, patch, mock_open, PropertyMock, Mock

  

  

  class TestUpdate(CliTestCase):
@@ -181,3 +188,769 @@

                    '--user', 'someone', '--staging', self.mock_nvr.return_value],

                   shell=True)

          ])

+ 

+ 

+ @patch.object(BugzillaClient, 'client')

+ class TestRequestRepo(CliTestCase):

+     """Test the request-repo command"""

+ 

+     def setUp(self):

+         self.mock_bug = Mock()

+         self.mock_bug.creator = 'Tom Hanks'

+         self.mock_bug.component = 'Package Review'

+         self.mock_bug.product = 'Fedora'

+         self.mock_bug.assigned_to = 'Tom Brady'

+         self.mock_bug.setter = 'Tom Brady'

+         mod_date = Mock()

+         mod_date.value = datetime.utcnow().strftime('%Y%m%dT%H:%M:%S')

+         self.mock_bug.flags = [{

+             'status': '+',

+             'name': 'fedora-review',

+             'type_id': 65,

+             'is_active': 1,

+             'id': 1441813,

+             'setter': 'Tom Brady',

+             'modification_date': mod_date

+         }]

+         self.mock_bug.summary = ('Review Request: nethack - A rogue-like '

+                                  'single player dungeon exploration game')

+         super(TestRequestRepo, self).setUp()

+ 

+     def get_cli(self, cli_cmd, name='fedpkg-stage', cfg='fedpkg-stage.conf',

+                 user_cfg='fedpkg-user-stage.conf'):

+         with patch('sys.argv', new=cli_cmd):

+             return self.new_cli(name=name, cfg=cfg, user_cfg=user_cfg)

+ 

+     @patch('requests.post')

+     @patch('sys.stdout', new=StringIO())

+     def test_request_repo(self, mock_request_post, mock_bz):

+         """Tests a standard request-repo call"""

+         self.mock_bug.summary = ('Review Request: testpkg - a description')

+         mock_bz.getbug.return_value = self.mock_bug

+         mock_rv = Mock()

+         mock_rv.ok = True

+         mock_rv.json.return_value = {'issue': {'id': 2}}

+         mock_request_post.return_value = mock_rv

+ 

+         cli_cmd = ['fedpkg-stage', '--path', self.cloned_repo_path,

+                    'request-repo', '1441813']

+         cli = self.get_cli(cli_cmd)

+         cli.request_repo()

+ 

+         expected_issue_content = {

+             'action': 'new_repo',

+             'branch': 'master',

+             'bug_id': 1441813,

+             'description': '',

+             'exception': False,

+             'monitor': 'monitoring',

+             'namespace': 'rpms',

+             'repo': 'testpkg',

+             'summary': 'a description',

+             'upstreamurl': ''

+         }

+         # Get the data that was submitted to Pagure

+         post_data = mock_request_post.call_args_list[0][1]['data']

+         actual_issue_content = json.loads(json.loads(

+             post_data)['issue_content'].strip('```'))

+         self.assertEqual(expected_issue_content, actual_issue_content)

+         output = sys.stdout.getvalue().strip()

+         expected_output = ('https://pagure.stg.example.com/releng/'

+                            'fedora-scm-requests/issue/2')

+         self.assertEqual(output, expected_output)

+ 

+     @patch('requests.post')

+     @patch('sys.stdout', new=StringIO())

+     def test_request_repo_override(self, mock_request_post, mock_bz):

+         """Tests a request-repo call with an overridden repo name"""

+         mock_bz.getbug.return_value = self.mock_bug

+         mock_rv = Mock()

+         mock_rv.ok = True

+         mock_rv.json.return_value = {'issue': {'id': 2}}

+         mock_request_post.return_value = mock_rv

+ 

+         cli_cmd = ['fedpkg-stage', '--path', self.cloned_repo_path,

+                    '--module-name', 'nethack', 'request-repo', '1441813']

+         cli = self.get_cli(cli_cmd)

+         cli.request_repo()

+ 

+         expected_issue_content = {

+             'action': 'new_repo',

+             'branch': 'master',

+             'bug_id': 1441813,

+             'description': '',

+             'exception': False,

+             'monitor': 'monitoring',

+             'namespace': 'rpms',

+             'repo': 'nethack',

+             'summary': ('A rogue-like single player dungeon exploration '

+                         'game'),

+             'upstreamurl': ''

+         }

+         # Get the data that was submitted to Pagure

+         post_data = mock_request_post.call_args_list[0][1]['data']

+         actual_issue_content = json.loads(json.loads(

+             post_data)['issue_content'].strip('```'))

+         self.assertEqual(expected_issue_content, actual_issue_content)

+         output = sys.stdout.getvalue().strip()

+         expected_output = ('https://pagure.stg.example.com/releng/'

+                            'fedora-scm-requests/issue/2')

+         self.assertEqual(output, expected_output)

+ 

+     @patch('requests.post')

+     @patch('sys.stdout', new=StringIO())

+     def test_request_repo_module(self, mock_request_post, mock_bz):

+         """Tests a request-repo call for a new module"""

+         self.mock_bug.product = 'Fedora Modules'

+         self.mock_bug.component = 'Module Review'

+         mock_bz.getbug.return_value = self.mock_bug

+         mock_rv = Mock()

+         mock_rv.ok = True

+         mock_rv.json.return_value = {'issue': {'id': 2}}

+         mock_request_post.return_value = mock_rv

+ 

+         cli_cmd = ['fedpkg-stage', '--path', self.cloned_repo_path,

+                    '--module-name', 'modules/nethack', 'request-repo',

+                    '1441813']

+         cli = self.get_cli(cli_cmd)

+         cli.request_repo()

+ 

+         expected_issue_content = {

+             'action': 'new_repo',

+             'branch': 'master',

+             'bug_id': 1441813,

+             'description': '',

+             'exception': False,

+             'monitor': 'monitoring',

+             'namespace': 'modules',

+             'repo': 'nethack',

+             'summary': ('A rogue-like single player dungeon exploration '

+                         'game'),

+             'upstreamurl': ''

+         }

+         # Get the data that was submitted to Pagure

+         post_data = mock_request_post.call_args_list[0][1]['data']

+         actual_issue_content = json.loads(json.loads(

+             post_data)['issue_content'].strip('```'))

+         self.assertEqual(expected_issue_content, actual_issue_content)

+         output = sys.stdout.getvalue().strip()

+         expected_output = ('https://pagure.stg.example.com/releng/'

+                            'fedora-scm-requests/issue/2')

+         self.assertEqual(output, expected_output)

+ 

+     @patch('requests.post')

+     @patch('sys.stdout', new=StringIO())

+     def test_request_repo_container(self, mock_request_post, mock_bz):

+         """Tests a request-repo call for a new container"""

+         self.mock_bug.product = 'Fedora Container Images'

+         self.mock_bug.component = 'Container Review'

+         mock_bz.getbug.return_value = self.mock_bug

+         mock_rv = Mock()

+         mock_rv.ok = True

+         mock_rv.json.return_value = {'issue': {'id': 2}}

+         mock_request_post.return_value = mock_rv

+ 

+         cli_cmd = ['fedpkg-stage', '--path', self.cloned_repo_path,

+                    '--module-name', 'container/nethack', 'request-repo',

+                    '1441813']

+         cli = self.get_cli(cli_cmd)

+         cli.request_repo()

+ 

+         expected_issue_content = {

+             'action': 'new_repo',

+             'branch': 'master',

+             'bug_id': 1441813,

+             'description': '',

+             'exception': False,

+             'monitor': 'monitoring',

+             'namespace': 'container',

+             'repo': 'nethack',

+             'summary': ('A rogue-like single player dungeon exploration '

+                         'game'),

+             'upstreamurl': ''

+         }

+         # Get the data that was submitted to Pagure

+         post_data = mock_request_post.call_args_list[0][1]['data']

+         actual_issue_content = json.loads(json.loads(

+             post_data)['issue_content'].strip('```'))

+         self.assertEqual(expected_issue_content, actual_issue_content)

+         output = sys.stdout.getvalue().strip()

+         expected_output = ('https://pagure.stg.example.com/releng/'

+                            'fedora-scm-requests/issue/2')

+         self.assertEqual(output, expected_output)

+ 

+     @patch('requests.post')

+     @patch('sys.stdout', new=StringIO())

+     def test_request_repo_with_optional_details(

+             self, mock_request_post, mock_bz):

+         """Tests a request-repo call with the optional details"""

+         mock_bz.getbug.return_value = self.mock_bug

+         mock_rv = Mock()

+         mock_rv.ok = True

+         mock_rv.json.return_value = {'issue': {'id': 2}}

+         mock_request_post.return_value = mock_rv

+ 

+         cli_cmd = ['fedpkg-stage', '--path', self.cloned_repo_path,

+                    '--module-name', 'nethack', 'request-repo', '1441813', '-d',

+                    'a description', '-s', 'a summary', '-m', 'no-monitoring',

+                    '-u', 'http://test.local']

+         cli = self.get_cli(cli_cmd)

+         cli.request_repo()

+ 

+         expected_issue_content = {

+             'action': 'new_repo',

+             'branch': 'master',

+             'bug_id': 1441813,

+             'description': 'a description',

+             'exception': False,

+             'monitor': 'no-monitoring',

+             'namespace': 'rpms',

+             'repo': 'nethack',

+             'summary': 'a summary',

+             'upstreamurl': 'http://test.local'

+         }

+         # Get the data that was submitted to Pagure

+         post_data = mock_request_post.call_args_list[0][1]['data']

+         actual_issue_content = json.loads(json.loads(

+             post_data)['issue_content'].strip('```'))

+         self.assertEqual(expected_issue_content, actual_issue_content)

+         output = sys.stdout.getvalue().strip()

+         expected_output = ('https://pagure.stg.example.com/releng/'

+                            'fedora-scm-requests/issue/2')

+         self.assertEqual(output, expected_output)

+ 

+     @patch('requests.post')

+     @patch('sys.stdout', new=StringIO())

+     def test_request_repo_exception(self, mock_request_post, mock_bz):

+         """Tests a request-repo call with the exception flag"""

+         mock_rv = Mock()

+         mock_rv.ok = True

+         mock_rv.json.return_value = {'issue': {'id': 2}}

+         mock_request_post.return_value = mock_rv

+ 

+         cli_cmd = ['fedpkg-stage', '--path', self.cloned_repo_path,

+                    '--module-name', 'nethack', 'request-repo', '--exception']

+         cli = self.get_cli(cli_cmd)

+         cli.request_repo()

+ 

+         expected_issue_content = {

+             'action': 'new_repo',

+             'branch': 'master',

+             'bug_id': '',

+             'description': '',

+             'exception': True,

+             'monitor': 'monitoring',

+             'namespace': 'rpms',

+             'repo': 'nethack',

+             'summary': '',

+             'upstreamurl': ''

+         }

+         # Get the data that was submitted to Pagure

+         post_data = mock_request_post.call_args_list[0][1]['data']

+         actual_issue_content = json.loads(json.loads(

+             post_data)['issue_content'].strip('```'))

+         self.assertEqual(expected_issue_content, actual_issue_content)

+         output = sys.stdout.getvalue().strip()

+         expected_output = ('https://pagure.stg.example.com/releng/'

+                            'fedora-scm-requests/issue/2')

+         self.assertEqual(output, expected_output)

+         # Since it is an exception, Bugzilla will not have been queried

+         mock_bz.getbug.assert_not_called()

+ 

+     def test_request_repo_wrong_package(self, mock_bz):

+         """Tests request-repo errors when the package is wrong"""

+         mock_bz.getbug.return_value = self.mock_bug

+         cli_cmd = ['fedpkg-stage', '--path', self.cloned_repo_path,

+                    '--module-name', 'not-nethack', 'request-repo', '1441813']

+         cli = self.get_cli(cli_cmd)

+         expected_error = ('The package in the Bugzilla bug "nethack" doesn\'t '

+                           'match the one provided "not-nethack"')

+         try:

+             cli.request_repo()

+             assert False, 'rpkgError not raised'

+         except rpkgError as error:

+             self.assertEqual(str(error), expected_error)

+ 

+     def test_request_repo_wrong_bug_product(self, mock_bz):

+         """Tests request-repo errors when the bug product is not Fedora"""

+         self.mock_bug.product = 'Red Hat'

+         mock_bz.getbug.return_value = self.mock_bug

+         cli_cmd = ['fedpkg-stage', '--path', self.cloned_repo_path,

+                    '--module-name', 'nethack', 'request-repo', '1441813']

+         cli = self.get_cli(cli_cmd)

+         expected_error = \

+             'The Bugzilla bug provided is not for "Fedora" or "Fedora EPEL"'

+         try:

+             cli.request_repo()

+             assert False, 'rpkgError not raised'

+         except rpkgError as error:

+             self.assertEqual(str(error), expected_error)

+ 

+     def test_request_repo_invalid_summary(self, mock_bz):

+         """Tests request-repo errors when the bug summary has no colon"""

+         self.mock_bug.summary = 'I am so wrong'

+         mock_bz.getbug.return_value = self.mock_bug

+         cli_cmd = ['fedpkg-stage', '--path', self.cloned_repo_path,

+                    '--module-name', 'nethack', 'request-repo', '1441813']

+         cli = self.get_cli(cli_cmd)

+         expected_error = \

+             'Invalid title for this Bugzilla bug (no ":" present)'

+         try:

+             cli.request_repo()

+             assert False, 'rpkgError not raised'

+         except rpkgError as error:

+             self.assertEqual(str(error), expected_error)

+ 

+     def test_request_repo_invalid_summary_two(self, mock_bz):

+         """Tests request-repo errors when the bug summary has no dash"""

+         self.mock_bug.summary = 'So:Wrong'

+         mock_bz.getbug.return_value = self.mock_bug

+         cli_cmd = ['fedpkg-stage', '--path', self.cloned_repo_path,

+                    '--module-name', 'nethack', 'request-repo', '1441813']

+         cli = self.get_cli(cli_cmd)

+         expected_error = \

+             'Invalid title for this Bugzilla bug (no "-" present)'

+         try:

+             cli.request_repo()

+             assert False, 'rpkgError not raised'

+         except rpkgError as error:

+             self.assertEqual(str(error), expected_error)

+ 

+     def test_request_repo_wrong_summary(self, mock_bz):

+         """Tests request-repo errors when the bug summary is wrong"""

+         self.mock_bug.summary = ('Review Request: fedpkg - lorum ipsum')

+         mock_bz.getbug.return_value = self.mock_bug

+         cli_cmd = ['fedpkg-stage', '--path', self.cloned_repo_path,

+                    '--module-name', 'nethack', 'request-repo', '1441813']

+         cli = self.get_cli(cli_cmd)

+         expected_error = ('The package in the Bugzilla bug "fedpkg" doesn\'t '

+                           'match the one provided "nethack"')

+         try:

+             cli.request_repo()

+             assert False, 'rpkgError not raised'

+         except rpkgError as error:

+             self.assertEqual(str(error), expected_error)

+ 

+     def test_request_repo_expired_bug(self, mock_bz):

+         """Tests request-repo errors when the bug was approved over 60 days ago

+         """

+         self.mock_bug.flags[0]['modification_date'].value = \

+             (datetime.utcnow() - timedelta(days=75)).strftime(

+                 '%Y%m%dT%H:%M:%S')

+         mock_bz.getbug.return_value = self.mock_bug

+         cli_cmd = ['fedpkg-stage', '--path', self.cloned_repo_path,

+                    '--module-name', 'nethack', 'request-repo', '1441813']

+         cli = self.get_cli(cli_cmd)

+         expected_error = \

+             'The Bugzilla bug\'s review was approved over 60 days ago'

+         try:

+             cli.request_repo()

+             assert False, 'rpkgError not raised'

+         except rpkgError as error:

+             self.assertEqual(str(error), expected_error)

+ 

+     def test_request_repo_bug_not_approved(self, mock_bz):

+         """Tests request-repo errors when the bug is not approved"""

+         self.mock_bug.flags[0]['name'] = 'something else'

+         mock_bz.getbug.return_value = self.mock_bug

+         cli_cmd = ['fedpkg-stage', '--path', self.cloned_repo_path,

+                    '--module-name', 'nethack', 'request-repo', '1441813']

+         cli = self.get_cli(cli_cmd)

+         expected_error = 'The Bugzilla bug is not approved yet'

+         try:

+             cli.request_repo()

+             assert False, 'rpkgError not raised'

+         except rpkgError as error:

+             self.assertEqual(str(error), expected_error)

+ 

+     def test_request_repo_bug_not_assigned(self, mock_bz):

+         """Tests request-repo errors when the bug is not assigned"""

+         self.mock_bug.assigned_to = None

+         mock_bz.getbug.return_value = self.mock_bug

+         cli_cmd = ['fedpkg-stage', '--path', self.cloned_repo_path,

+                    '--module-name', 'nethack', 'request-repo', '1441813']

+         cli = self.get_cli(cli_cmd)

+         expected_error = 'The Bugzilla bug provided is not assigned to anyone'

+         try:

+             cli.request_repo()

+             assert False, 'rpkgError not raised'

+         except rpkgError as error:

+             self.assertEqual(str(error), expected_error)

+ 

+     def test_request_repo_invalid_name(self, mock_bz):

+         """Tests request-repo errors when the repo name is invalid"""

+         self.mock_bug.product = 'Red Hat'

+         mock_bz.getbug.return_value = self.mock_bug

+         cli_cmd = ['fedpkg-stage', '--path', self.cloned_repo_path,

+                    '--module-name', '$nethack', 'request-repo', '1441813']

+         cli = self.get_cli(cli_cmd)

+         expected_error = (

+             'The repository name "$nethack" 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.')

+         try:

+             cli.request_repo()

+             assert False, 'rpkgError not raised'

+         except rpkgError as error:

+             self.assertEqual(str(error), expected_error)

+ 

+     @patch('requests.post')

+     def test_request_repo_pagure_error(self, mock_request_post, mock_bz):

+         """Tests a standard request-repo call when the Pagure API call fails"""

+         mock_bz.getbug.return_value = self.mock_bug

+         cli_cmd = ['fedpkg-stage', '--path', self.cloned_repo_path,

+                    '--module-name', 'nethack', 'request-repo', '1441813']

+         cli = self.get_cli(cli_cmd)

+         mock_rv = Mock()

+         mock_rv.ok = False

+         mock_rv.json.return_value = {'error': 'some error'}

+         mock_request_post.return_value = mock_rv

+         try:

+             cli.request_repo()

+             assert False, 'rpkgError not raised'

+         except rpkgError as error:

+             expected_error = ('The following error occurred while creating a '

+                               'new issue in Pagure: some error')

+             self.assertEqual(str(error), expected_error)

+ 

+     def test_request_repo_no_bug(self, mock_bz):

+         """Tests request-repo errors when no bug or exception is provided"""

+         self.mock_bug.product = 'Red Hat'

+         mock_bz.getbug.return_value = self.mock_bug

+         cli_cmd = ['fedpkg-stage', '--path', self.cloned_repo_path,

+                    '--module-name', 'nethack', 'request-repo']

+         cli = self.get_cli(cli_cmd)

+         expected_error = \

+             'A Bugzilla bug is required on new repository requests'

+         try:

+             cli.request_repo()

+             assert False, 'rpkgError not raised'

+         except rpkgError as error:

+             self.assertEqual(str(error), expected_error)

+ 

+ 

+ class TestRequestBranch(CliTestCase):

+     """Test the request-branch command"""

+ 

+     def setUp(self):

+         super(TestRequestBranch, self).setUp()

+ 

+     def tearDown(self):

+         super(TestRequestBranch, self).tearDown()

+ 

+     def get_cli(self, cli_cmd, name='fedpkg-stage', cfg='fedpkg-stage.conf',

+                 user_cfg='fedpkg-user-stage.conf'):

+         with patch('sys.argv', new=cli_cmd):

+             return self.new_cli(name=name, cfg=cfg, user_cfg=user_cfg)

+ 

+     @patch('requests.post')

+     @patch('fedpkg.cli.get_release_branches')

+     @patch('sys.stdout', new=StringIO())

+     def test_request_branch(self, mock_grb, mock_request_post):

+         """Tests request-branch"""

+         mock_grb.return_value = set(['el6', 'epel7', 'f25', 'f26', 'f27'])

+         mock_rv = Mock()

+         mock_rv.ok = True

+         mock_rv.json.return_value = {'issue': {'id': 2}}

+         mock_request_post.return_value = mock_rv

+         # Checkout the f27 branch

+         self.run_cmd(['git', 'checkout', 'f27'], cwd=self.cloned_repo_path)

+ 

+         cli_cmd = ['fedpkg-stage', '--path', self.cloned_repo_path,

+                    'request-branch']

+         cli = self.get_cli(cli_cmd)

+         cli.request_branch()

+ 

+         expected_issue_content = {

+             'action': 'new_branch',

+             'repo': 'testpkg',

+             'namespace': 'rpms',

+             'branch': 'f27',

+             'create_git_branch': True

+         }

+         # Get the data that was submitted to Pagure

+         post_data = mock_request_post.call_args_list[0][1]['data']

+         actual_issue_content = json.loads(json.loads(

+             post_data)['issue_content'].strip('```'))

+         self.assertEqual(expected_issue_content, actual_issue_content)

+         output = sys.stdout.getvalue().strip()

+         expected_output = ('https://pagure.stg.example.com/releng/'

+                            'fedora-scm-requests/issue/2')

+         self.assertEqual(output, expected_output)

+ 

+     @patch('requests.post')

+     @patch('fedpkg.cli.get_release_branches')

+     @patch('sys.stdout', new=StringIO())

+     def test_request_branch_override(self, mock_grb, mock_request_post):

+         """Tests request-branch with an overriden package and branch name"""

+         mock_grb.return_value = set(['el6', 'epel7', 'f25', 'f26', 'f27'])

+         mock_rv = Mock()

+         mock_rv.ok = True

+         mock_rv.json.return_value = {'issue': {'id': 2}}

+         mock_request_post.return_value = mock_rv

+ 

+         cli_cmd = ['fedpkg-stage', '--path', self.cloned_repo_path,

+                    '--module-name', 'nethack', 'request-branch', 'f27']

+         cli = self.get_cli(cli_cmd)

+         cli.request_branch()

+ 

+         expected_issue_content = {

+             'action': 'new_branch',

+             'repo': 'nethack',

+             'namespace': 'rpms',

+             'branch': 'f27',

+             'create_git_branch': True

+         }

+         # Get the data that was submitted to Pagure

+         post_data = mock_request_post.call_args_list[0][1]['data']

+         actual_issue_content = json.loads(json.loads(

+             post_data)['issue_content'].strip('```'))

+         self.assertEqual(expected_issue_content, actual_issue_content)

+         output = sys.stdout.getvalue().strip()

+         expected_output = ('https://pagure.stg.example.com/releng/'

+                            'fedora-scm-requests/issue/2')

+         self.assertEqual(output, expected_output)

+ 

+     @patch('requests.post')

+     @patch('fedpkg.cli.get_release_branches')

+     @patch('sys.stdout', new=StringIO())

+     def test_request_branch_module(self, mock_grb, mock_request_post):

+         """Tests request-branch for a new module branch"""

+         mock_grb.return_value = set(['el6', 'epel7', 'f25', 'f26', 'f27'])

+         mock_rv = Mock()

+         mock_rv.ok = True

+         mock_rv.json.return_value = {'issue': {'id': 2}}

+         mock_request_post.return_value = mock_rv

+ 

+         cli_cmd = ['fedpkg-stage', '--path', self.cloned_repo_path,

+                    '--module-name', 'modules/nethack', 'request-branch', 'f27']

+         cli = self.get_cli(cli_cmd)

+         cli.request_branch()

+ 

+         expected_issue_content = {

+             'action': 'new_branch',

+             'repo': 'nethack',

+             'namespace': 'modules',

+             'branch': 'f27',

+             'create_git_branch': True

+         }

+         # Get the data that was submitted to Pagure

+         post_data = mock_request_post.call_args_list[0][1]['data']

+         actual_issue_content = json.loads(json.loads(

+             post_data)['issue_content'].strip('```'))

+         self.assertEqual(expected_issue_content, actual_issue_content)

+         output = sys.stdout.getvalue().strip()

+         expected_output = ('https://pagure.stg.example.com/releng/'

+                            'fedora-scm-requests/issue/2')

+         self.assertEqual(output, expected_output)

+ 

+     @patch('requests.post')

+     @patch('fedpkg.cli.get_release_branches')

+     @patch('sys.stdout', new=StringIO())

+     def test_request_branch_container(self, mock_grb, mock_request_post):

+         """Tests request-branch for a new container branch"""

+         mock_grb.return_value = set(['el6', 'epel7', 'f25', 'f26', 'f27'])

+         mock_rv = Mock()

+         mock_rv.ok = True

+         mock_rv.json.return_value = {'issue': {'id': 2}}

+         mock_request_post.return_value = mock_rv

+ 

+         cli_cmd = ['fedpkg-stage', '--path', self.cloned_repo_path,

+                    '--module-name', 'container/nethack', 'request-branch',

+                    'f27']

+         cli = self.get_cli(cli_cmd)

+         cli.request_branch()

+ 

+         expected_issue_content = {

+             'action': 'new_branch',

+             'repo': 'nethack',

+             'namespace': 'container',

+             'branch': 'f27',

+             'create_git_branch': True

+         }

+         # Get the data that was submitted to Pagure

+         post_data = mock_request_post.call_args_list[0][1]['data']

+         actual_issue_content = json.loads(json.loads(

+             post_data)['issue_content'].strip('```'))

+         self.assertEqual(expected_issue_content, actual_issue_content)

+         output = sys.stdout.getvalue().strip()

+         expected_output = ('https://pagure.stg.example.com/releng/'

+                            'fedora-scm-requests/issue/2')

+         self.assertEqual(output, expected_output)

+ 

+     @patch('requests.post')

+     @patch('fedpkg.cli.get_release_branches')

+     @patch('fedpkg.cli.verify_sls')

+     @patch('sys.stdout', new=StringIO())

+     def test_request_branch_sls(self, mock_verify_sls, mock_grb,

+                                 mock_request_post):

+         """Tests request-branch with service levels"""

+         mock_grb.return_value = set(['el6', 'epel7', 'f25', 'f26', 'f27'])

+         mock_rv_post = Mock()

+         mock_rv_post.ok = True

+         mock_rv_post.json.return_value = {'issue': {'id': 2}}

+         mock_request_post.return_value = mock_rv_post

+ 

+         cli_cmd = ['fedpkg-stage', '--path', self.cloned_repo_path,

+                    '--module-name', 'nethack', 'request-branch', '9', '--sl',

+                    'security_fixes:2030-12-01', 'bug_fixes:2030-12-01']

+         cli = self.get_cli(cli_cmd)

+         cli.request_branch()

+ 

+         expected_issue_content = {

+             'action': 'new_branch',

+             'repo': 'nethack',

+             'namespace': 'rpms',

+             'branch': '9',

+             'create_git_branch': True,

+             'sls': {

+                 'security_fixes': '2030-12-01',

+                 'bug_fixes': '2030-12-01'

+             }

+         }

+         # Get the data that was submitted to Pagure

+         post_data = mock_request_post.call_args_list[0][1]['data']

+         actual_issue_content = json.loads(json.loads(

+             post_data)['issue_content'].strip('```'))

+         self.assertEqual(expected_issue_content, actual_issue_content)

+         output = sys.stdout.getvalue().strip()

+         expected_output = ('https://pagure.stg.example.com/releng/'

+                            'fedora-scm-requests/issue/2')

+         self.assertEqual(output, expected_output)

+ 

+     @patch('requests.post')

+     @patch('fedpkg.cli.get_release_branches')

+     @patch('sys.stdout', new=StringIO())

+     def test_request_branch_all_releases(self, mock_grb, mock_request_post):

+         """Tests request-branch with the '--all-releases' option """

+         mock_grb.return_value = set(['el6', 'epel7', 'f25', 'f26', 'f27'])

+         post_side_effect = []

+         for i in range(1, 4):

+             mock_rv = Mock()

+             mock_rv.ok = True

+             mock_rv.json.return_value = {'issue': {'id': i}}

+             post_side_effect.append(mock_rv)

+         mock_request_post.side_effect = post_side_effect

+ 

+         cli_cmd = ['fedpkg-stage', '--path', self.cloned_repo_path,

+                    '--module-name', 'nethack', 'request-branch',

+                    '--all-releases']

+         cli = self.get_cli(cli_cmd)

+         cli.request_branch()

+ 

+         for i in range(3):

+             expected_issue_content = {

+                 'action': 'new_branch',

+                 'repo': 'nethack',

+                 'namespace': 'rpms',

+                 'branch': 'f' + str(27 - i),

+                 'create_git_branch': True

+             }

+             post_data = mock_request_post.call_args_list[i][1]['data']

+             actual_issue_content = json.loads(json.loads(

+                 post_data)['issue_content'].strip('```'))

+             self.assertEqual(expected_issue_content, actual_issue_content)

+ 

+         output = sys.stdout.getvalue().strip()

+         expected_output = """\

+ https://pagure.stg.example.com/releng/fedora-scm-requests/issue/1

+ https://pagure.stg.example.com/releng/fedora-scm-requests/issue/2

+ https://pagure.stg.example.com/releng/fedora-scm-requests/issue/3"""

+         self.assertEqual(output, expected_output)

+ 

+     def test_request_branch_invalid_use_of_all_releases(self):

+         """Tests request-branch with a branch and the '--all-releases' option

+         """

+         cli_cmd = ['fedpkg-stage', '--path', self.cloned_repo_path,

+                    '--module-name', 'nethack', 'request-branch', 'f27',

+                    '--all-releases']

+         cli = self.get_cli(cli_cmd)

+         expected_error = \

+             'You cannot specify a branch with the "--all-releases" option'

+         try:

+             cli.request_branch()

+             assert False, 'rpkgError not raised'

+         except rpkgError as error:

+             self.assertEqual(str(error), expected_error)

+ 

+     def test_request_branch_invalid_use_of_all_releases_sl(self):

+         """Tests request-branch with an SL and the '--all-releases' option

+         """

+         cli_cmd = ['fedpkg-stage', '--path', self.cloned_repo_path,

+                    '--module-name', 'nethack', 'request-branch',

+                    '--all-releases', '--sl', 'security_fixes:2020-01-01']

+         cli = self.get_cli(cli_cmd)

+         expected_error = ('You cannot specify service levels with the '

+                           '"--all-releases" option')

+         try:

+             cli.request_branch()

+             assert False, 'rpkgError not raised'

+         except rpkgError as error:

+             self.assertEqual(str(error), expected_error)

+ 

+     @patch('fedpkg.cli.get_release_branches')

+     def test_request_branch_invalid_sls(self, mock_grb):

+         """Tests request-branch with invalid service levels"""

+         mock_grb.return_value = set(['el6', 'epel7', 'f25', 'f26', 'f27'])

+ 

+         cli_cmd = ['fedpkg-stage', '--path', self.cloned_repo_path,

+                    '--module-name', 'nethack', 'request-branch', '9', '--sl',

+                    'security_fixes-2030-12-01', 'bug_fixes:2030-12-01']

+         cli = self.get_cli(cli_cmd)

+         expected_error = \

+             'The SL "security_fixes-2030-12-01" is in an invalid format'

+         try:

+             cli.request_branch()

+             assert False, 'rpkgError not raised'

+         except rpkgError as error:

+             self.assertEqual(str(error), expected_error)

+ 

+     @patch('fedpkg.cli.get_release_branches')

+     def test_request_branch_sls_on_release_branch_error(self, mock_grb):

+         """Tests request-branch with a release branch and service levels"""

+         mock_grb.return_value = set(['el6', 'epel7', 'f25', 'f26', 'f27'])

+ 

+         cli_cmd = ['fedpkg-stage', '--path', self.cloned_repo_path,

+                    '--module-name', 'nethack', 'request-branch', 'f27', '--sl',

+                    'security_fixes-2030-12-01', 'bug_fixes:2030-12-01']

+         cli = self.get_cli(cli_cmd)

+         expected_error = 'You can\'t provide SLs for release branches'

+         try:

+             cli.request_branch()

+             assert False, 'rpkgError not raised'

+         except rpkgError as error:

+             self.assertEqual(str(error), expected_error)

+ 

+     def test_request_branch_invalid_module_branch_name(self):

+         """Test request-branch raises an exception when a invalid module branch

+         name is supplied"""

+         cli_cmd = ['fedpkg-stage', '--path', self.cloned_repo_path,

+                    '--module-name', 'modules/nethack', 'request-branch',

+                    'some:branch']

+         cli = self.get_cli(cli_cmd)

+         expected_error = (

+             'Only characters, numbers, periods, dashes, underscores, and '

+             'pluses are allowed in module branch names')

+         try:

+             cli.request_branch()

+             assert False, 'rpkgError not raised'

+         except rpkgError as error:

+             self.assertEqual(str(error), expected_error)

+ 

+     def test_request_branch_no_branch(self):

+         """Test request-branch raises an exception when a branch isn't supplied

+         """

+         tempdir = mkdtemp()

+         cli_cmd = ['fedpkg-stage', '--path', tempdir,

+                    '--module-name', 'nethack', 'request-branch']

+         cli = self.get_cli(cli_cmd)

+         expected_error = (

+             'You must specify a branch if you are not in a git repository')

+         try:

+             cli.request_branch()

+             assert False, 'rpkgError not raised'

+         except rpkgError as error:

+             self.assertEqual(str(error), expected_error)

+         finally:

+             rmdir(tempdir)

file added
+143
@@ -0,0 +1,143 @@

+ # -*- coding: utf-8 -*-

+ # fedpkg - a Python library for RPM Packagers

+ #

+ # Copyright (C) 2017 Red Hat Inc.

+ # Author(s): Matt Prahl <mprahl@redhat.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.

+ 

+ from mock import patch, Mock

+ from pyrpkg.errors import rpkgError

+ 

+ from utils import CliTestCase

+ from fedpkg import utils

+ 

+ 

+ class TestUtils(CliTestCase):

+     """Test functions in fedpkg.utils"""

+ 

+     @patch('requests.get')

+     def test_get_sl_type(self, mock_get):

+         """Test get_sl_type"""

+         sl_type = {

+             'id': 1,

+             'name': 'security_fixes',

+             'description': 'security_fixes',

+         }

+         mock_rv = Mock()

+         mock_rv.ok = True

+         mock_rv.json.return_value = {

+             'count': 1,

+             'results': [sl_type]

+         }

+         mock_get.return_value = mock_rv

+         rv = utils.get_sl_type('http://pdc.local/', 'securty_fixes')

+         self.assertEqual(rv, sl_type)

+ 

+     @patch('requests.get')

+     def test_get_sl_type_pdc_error(self, mock_request_get):

+         """Test get_sl_type when PDC errors"""

+         mock_rv = Mock()

+         mock_rv.ok = False

+         mock_rv.text = 'Some error'

+         mock_request_get.return_value = mock_rv

+         try:

+             utils.get_sl_type('http://pdc.local/', 'securty_fixes')

+             assert False, 'rpkgError not raised'

+         except rpkgError as error:

+             expected_error = ('The following error occurred while validating '

+                               'the passed in service level in PDC: Some error')

+             self.assertEqual(str(error), expected_error)

+ 

+     @patch('fedpkg.utils.get_sl_type')

+     def test_verify_sls(self, mock_get_sl_type):

+         """Test verify_sls"""

+         mock_get_sl_type.return_value = {

+             'id': 1,

+             'name': 'security_fixes',

+             'description': 'security_fixes',

+         }

+         sls = {'security_fixes': '2222-12-01'}

+         # If it's invalid, an rpkgError will be raised

+         try:

+             utils.verify_sls('http://pdc.local/', sls)

+         except rpkgError:

+             assert False, 'An rpkgError exception was raised but not expected'

+ 

+     @patch('fedpkg.utils.get_sl_type')

+     def test_verify_sls_eol_expired(self, mock_get_sl_type):

+         """Test verify_sls raises an exception when an EOL is expired"""

+         mock_get_sl_type.return_value = {

+             'id': 1,

+             'name': 'security_fixes',

+             'description': 'security_fixes',

+         }

+         sls = {'security_fixes': '2001-12-01'}

+ 

+         try:

+             utils.verify_sls('http://pdc.local/', sls)

+             assert False, 'An rpkgError exception was not raised'

+         except rpkgError as e:

+             self.assertEqual(str(e), 'The SL "2001-12-01" is already expired')

+ 

+     def test_sl_list_dict(self):

+         """Test sl_list_to_dict"""

+         sls = ['security_fixes:2030-01-01', 'bug_fixes:2029-01-01']

+         sls_dict = {'security_fixes': '2030-01-01', 'bug_fixes': '2029-01-01'}

+         self.assertEqual(utils.sl_list_to_dict(sls), sls_dict)

+ 

+     def test_sl_list_to_dict_invalid_format(self):

+         """Tests sl_list_to_dict with an invalid SL format. An error is

+         expected.

+         """

+         try:

+             sls = ['security_fixes:2030-12-01', 'bug_fixes/2030-12-01']

+             utils.sl_list_to_dict(sls)

+             assert False, 'An rpkgError exception was not raised'

+         except rpkgError as e:

+             assert str(e) == \

+                 'The SL "bug_fixes/2030-12-01" is in an invalid format'

+ 

+     def test_verify_sls_invalid_date(self):

+         """Test verify_sls with an SL that is not June 1st or December 1st. An

+         error is expected.

+         """

+         for eol in ['2030-01-01', '2030-12-25']:

+             try:

+                 sls = {'security_fixes': eol, 'bug_fixes': '2030-12-01'}

+                 utils.verify_sls('abc', sls)

+                 assert False, 'An rpkgError exception was not raised'

+             except rpkgError as e:

+                 assert str(e) == ('The SL "{0}" must expire on June 1st or '

+                                   'December 1st'.format(eol))

+ 

+     @patch('fedpkg.utils.Bodhi2Client')

+     def test_get_release_branches(self, mock_bodhi):

+         """Test that get_release_branches returns all the active Fedora release

+         branches.

+         """

+         mock_bodhi_client = Mock()

+         mock_bodhi_client.send_request.return_value = {

+             u'page': 1,

+             u'pages': 1,

+             u'releases': [

+                 {'state': 'current', 'branch': 'el6'},

+                 {'state': 'archived', 'branch': 'f24'},

+                 {'state': 'current', 'branch': 'epel7'},

+                 {'state': 'current', 'branch': 'f25'},

+                 {'state': 'archived', 'branch': 'el5'},

+                 {'state': 'archived', 'branch': 'f23'},

+                 {'state': 'current', 'branch': 'f26'},

+                 {'state': 'pending', 'branch': 'f27m'},

+                 {'state': 'current', 'branch': 'f27'}

+             ],

+             u'rows_per_page': 20,

+             u'total': 11}

+         mock_bodhi.return_value = mock_bodhi_client

+         expected = set(['el6', 'epel7', 'f25', 'f26', 'f27'])

+         actual = utils.get_release_branches('http://bodhi.local')

+         self.assertEqual(expected, actual)

file modified
+11 -3
@@ -136,7 +136,10 @@

  

      def setUp(self):

          # create a base repo

-         self.repo_path = tempfile.mkdtemp(prefix='fedpkg-commands-tests-')

+         self.repo_base = tempfile.mkdtemp(prefix='fedpkg-commands-tests-')

+         # Have a namespace of rpms and a repo name of testpkg

+         self.repo_path = os.path.join(self.repo_base, 'rpms', 'testpkg')

+         os.makedirs(self.repo_path)

  

          self.spec_filename = 'docpkg.spec'

  
@@ -175,7 +178,7 @@

              self.run_cmd(cmd, cwd=self.cloned_repo_path)

  

      def tearDown(self):

-         shutil.rmtree(self.repo_path)

+         shutil.rmtree(self.repo_base)

          shutil.rmtree(self.cloned_repo_path)

  

      def make_commands(self, path=None, user=None, dist=None, target=None,
@@ -232,7 +235,7 @@

      default_config_file = os.path.join(os.path.dirname(__file__),

                                         'fedpkg-test.conf')

  

-     def new_cli(self, name='fedpkg', cfg=None):

+     def new_cli(self, name='fedpkg', cfg=None, user_cfg=None):

          config = configparser.SafeConfigParser()

          if cfg:

              config_file = os.path.join(os.path.dirname(__file__), cfg)
@@ -240,6 +243,11 @@

              config_file = self.default_config_file

          config.read(config_file)

  

+         if user_cfg:

+             user_config_file = os.path.join(

+                 os.path.dirname(__file__), user_cfg)

+             config.read(user_config_file)

+ 

          client = fedpkg.cli.fedpkgClient(config, name=name)

          client.setupLogging(pyrpkg.log)

          pyrpkg.log.setLevel(logging.CRITICAL)

Addresses #137

To test this out, you'll need to get a Pagure API token to be able to create new tickets on the "releng/fedora-scm-requests" repo in stage and add it to ~/.config/rpkg/fedpkg-stage.conf. To do this:

  • Go to your Pagure account's API tokens page in stage
  • Select "Create a new ticket against this project"
  • Click on "Create"
  • Scroll down to the "API Keys" section and copy the API key.
  • Create your personal config file at ~/.config/rpkg/fedpkg-stage.conf
  • Paste the API key so that it looks like this:
[fedpkg-stage.pagure]
token = <api_key_here>

Then to view the help of the commands, you can use:
fedpkg-stage -C conf/etc/rpkg/fedpkg-stage.conf request-branch --help
fedpkg-stage -C conf/etc/rpkg/fedpkg-stage.conf request-repo --help

These three urls could move to three separated dedicated section fedpkg.bugzilla, fedpkg.pagure, fedpkg.pdc. I think this separation gives a clear view of the core options and other services' options that are used to work with.

User configuration file is really useful. I like it. Instead of handling a user configuration file separately, why not let fedpkg read it along with system-wide config file /etc/rpkg/fedpkg.conf together? configparser can handle these files properly.

Based on above comment, in /etc/rpkg/fedpkg.conf, there is

[fedpkg.pagure]
url = 'https://pagure.io/

in ~/.config/rpkg/fedpkg.conf, there is

[fedpkg.pagure]
token = <api_key_here>

after reading, we can access both of these pagure options.

(to be continued) we don't need to pass user_config explicitly as user configuration is already included in config.

And also don't need to check existence of user configuration for these two commands here. Just call get_pagure_token inside each command, raising error if someone forgets to create his own config file with token.

This is what I'm thinking that we can reuse existing fedpkg code without handling user configuration and options check for request_repo, request_branch separately. What do you think?

pretty please pagure-ci rebuild

Is it worth to move this to /etc/rpkg/fedpkg.conf fedpkg section?

Another user case would be to validate user input namespace. For example, rhpkg is able to clone in form rhpkg clone namespace/repo-name, so the namespace part could be validated and report error earlier rather than relying on git to report a clone error. This is what I can imagine now. Any other can benefit from this?

/cc lsedlar

This is just an idea and no need to do in this PR.

What do you think guys? :)

It can't hurt. I'll move it to the config file.

rebased onto 1629083480e543cc8be4cd2f3bd40bfdaa1a121a

6 years ago

@cqi thank you for the review. I've addressed your comments in the latest rebase.

Looks good to me. Thanks.

@mprahl is it ok to try these two commands with fedpkg-stage?

Looks good to me. Thanks.
@mprahl is it ok to try these two commands with fedpkg-stage?

Should be totally fine to test it out. By the way, I'll be away starting December 14th and coming back January 2nd. Is it possible to get this merged before then?

--user--config does not appear in -h output, e.g. fedpkg -h.

s/--user--config/--user-config/

Generally the patch looks good to me. The tests are very nice. I'm still not completely convinced the package name should always be required for requesting a new branch, but I guess it's not a big deal.

I think @lsedlar is correct. I didn't notice this. Firstly, --module-name can be reused to specify package name, that option is actually used for the purpose of overriding the behavior of getting package name from git push URL. So, following forms could work

fedpkg --module-name new-package request-repo
fedpkg --module-name new-package request-branch ...

Secondly, for requesting a branch, there would be two scenario. One is to request branches without waiting for the repo to be approved and created. Another one is someone could request new branches inside a clone repository. In the latter case which I mentioned in #137 , package name, the repo argument, is not necessary actually. Just use Commands.module_name. An exmaple is

fedpkg request-branch branch

@mprahl Could you reconsider this?

@cqi I prefer keeping it the way it is, but since you both prefer it, I'll incorporate your recommendation.

rebased onto 945673dbcf5d0a3b04fe2b58ef1fe94527c409a5

6 years ago

Okay, I addressed the comments. Please review again.

rebased onto 251a930523072f227fb5c01e1f0e9e9b5597c593

6 years ago

rebased onto f7d9b56

6 years ago

@mprahl Thank you very much. Looks good. :thumbsup:

I also find some issues

  • --all-releases does not create request for EPEL branches el6 and epel7

  • --all-releases creates duplicate request if that was created already, steps to reproduce

fedpkg-stage --module-name esteidcerts request-branch f27
fedpkg-stage --module-name esteidcerts request-branch --all-releases

then there is duplicate request for branch f27

  • request-branch creates duplicate request as well, steps to reproduce
fedpkg-stage --module-name esteidcerts request-branch f27
run again

there are two requests for branch f27

I think these issues do not impact the use of this first version. Let's fix them later.

@mprahl Thank you very much. Looks good. 👍
I also find some issues

--all-releases does not create request for EPEL branches el6 and epel7

--all-releases creates duplicate request if that was created already, steps to reproduce

fedpkg-stage --module-name esteidcerts request-branch f27
fedpkg-stage --module-name esteidcerts request-branch --all-releases

then there is duplicate request for branch f27

request-branch creates duplicate request as well, steps to reproduce

fedpkg-stage --module-name esteidcerts request-branch f27
run again

there are two requests for branch f27
I think these issues do not impact the use of this first version. Let's fix them later.

@cqi, it was by design that --all-releases doesn't include EPEL because most packages don't go to EPEL, and the ones that do, don't necessarily need both EPEL6 and EPEL7.

As to your other comment, I'm okay with it not being idempotent. To make it idempotent, we'd either keep track of what the user has requested, or check all open requests by the user in the Pagure ticket queue. The former would be error prone since the user may want to resubmit that same request if the first request was denied by an admin by mistake, and the latter adds additional API calls to Pagure, which would slow the down the performance of the command even more. We could look into implementing the latter option, but I'd like to hold off.

With that said, thank you very much for the review. May you please merge it when you feel comfortable?

Code wise this looks good to me.

Pull-Request has been merged by cqi

6 years ago

Thank you all :tada: