lrossett / centos / centpkg

Forked from centos/centpkg 2 years ago
Blob Blame History Raw
# -*- coding: utf-8 -*-
# - a module with support methods for centpkg
# Copyright (C) 2021 Red Hat Inc.
# Author(s): Ondrej Nosek <>
# 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 for
# the full text of the license.

import json

import git
import requests
from pyrpkg import rpkgError
from requests.exceptions import ConnectionError
from six.moves.configparser import NoOptionError, NoSectionError
from six.moves.urllib.parse import quote_plus, urlparse

def do_fork(logger, base_url, token, repo_name, namespace, cli_name):
    Creates a fork of the project.
    :param logger: A logger object
    :param base_url: a string of the URL repository
    :param token: a string of the API token that has rights to make a fork
    :param repo_name: a string of the repository name
    :param namespace: a string determines a type of the repository
    :param cli_name: string of the CLI's name (e.g. centpkg)
    :return: a bool; True when fork was created, False when already exists
    api_url = '{0}/api/v4'.format(base_url.rstrip('/'))
    project_id = quote_plus("redhat/centos-stream/{0}/{1}".format(namespace, repo_name))
    fork_url = '{0}/projects/{1}/fork'.format(api_url, project_id)

    headers = {
        'PRIVATE-TOKEN': token,
        'Accept': 'application/json',
        'Content-Type': 'application/json'
    # define a new repository name/path to avoid collision with other projects
    safe_name = "centos_{0}_{1}".format(namespace, repo_name)
    payload = json.dumps({
        'name': safe_name,  # name of the project after forking
        'path': safe_name,
        rv =
            fork_url, headers=headers, data=payload, timeout=60)
    except ConnectionError as error:
        error_msg = ('The connection to API failed while trying to '
                     'create a new fork. The error was: {0}'.format(str(error)))
        raise rpkgError(error_msg)

        # Extract response json for debugging
        rv_json = rv.json()
        logger.debug("Pagure API response: '{0}'".format(rv_json))
    except Exception:

    base_error_msg = ('The following error occurred while creating a new fork: {0}')
    if not rv.ok:
        # fork was already created
        if rv.status_code == 409 or rv.reason == "Conflict":
            return False
        # show hint for invalid, expired or revoked token
        elif rv.status_code == 401 or rv.reason == "Unauthorized":
            base_error_msg += '\nFor invalid or expired token refer to ' \
                '"{0} fork -h" to set a token in your user ' \
        raise rpkgError(base_error_msg.format(rv.text))

    return True

def do_add_remote(base_url, remote_base_url, username, repo, repo_name,
    Adds remote tracked repository
    :param base_url: a string of the URL repository
    :param remote_base_url: a string of the remote tracked repository
    :param username: a string of the (FAS) user name
    :param repo: object, current project git repository
    :param repo_name: a string of the repository name
    :param namespace: a string determines a type of the repository
    :return: a bool; True if remote was created, False when already exists
    parsed_url = urlparse(remote_base_url)
    remote_url = '{0}://{1}/{2}/centos_{3}_{4}.git'.format(

    # check already existing remote
    for remote in repo.remotes:
        if == username:
            return False

        # create remote with username as its name
        repo.create_remote(username, url=remote_url)
    except git.exc.GitCommandError as e:
        error_msg = "During create remote:\n  {0}\n  {1}".format(
            " ".join(e.command), e.stderr)
        raise rpkgError(error_msg)
    return True

def config_get_safely(config, section, option):
    Returns option from the user's configuration file. In case of missing
    section or option method throws an exception with a human-readable
    warning and a possible hint.
    The method should be used especially in situations when there are newly
    added sections/options into the config. In this case, there is a risk that
    the user's config wasn't properly upgraded.

    :param config: ConfigParser object
    :param section: section name in the config
    :param option: name of the option
    :return: option value from the right section
    :rtype: str

    hint = (
        "First (if possible), refer to the help of the current command "
        "There also might be a new version of the config after upgrade.\n"
        "Hint: you can check if you have 'centpkg.conf.rpmnew' or "
        "'centpkg.conf.rpmsave' in the config directory. If yes, try to merge "
        "your changes to the config with the maintainer provided version "
        "(or replace centpkg.conf file with 'centpkg.conf.rpmnew')."

        return config.get(section, option)
    except NoSectionError:
        msg = "Missing section '{0}' in the config file.".format(section)
        raise rpkgError("{0}\n{1}".format(msg, hint))
    except NoOptionError:
        msg = "Missing option '{0}' in the section '{1}' of the config file.".format(
            option, section
        raise rpkgError("{0}\n{1}".format(msg, hint))
    except Exception: