tdawson / centos / centpkg

Forked from centos/centpkg 3 years ago
Clone
85a850
'''
85a850
    Command line behavior for centpkg
85a850
'''
85a850
#
85a850
# Author(s):
85a850
#            Jesse Keating <jkeating@redhat.com>
85a850
#            Pat Riehecky <riehecky@fnal.gov>
85a850
#            Brian Stinson <bstinson@ksu.edu>
85a850
#
85a850
# This program is free software; you can redistribute it and/or modify it
85a850
# under the terms of the GNU General Public License as published by the
85a850
# Free Software Foundation; either version 2 of the License, or (at your
85a850
# option) any later version.  See http://www.gnu.org/copyleft/gpl.html for
85a850
# the full text of the license.
85a850
a394b1
from __future__ import print_function
80f38d
80f38d
import argparse
80f38d
import textwrap
80f38d
80f38d
from centpkg.utils import config_get_safely, do_add_remote, do_fork
5a7f92
import centpkg.utils
Brian Stinson aa8548
from pyrpkg.cli import cliClient
794ab0
from pyrpkg import rpkgError
b66e8a
from urllib.parse import urlparse
b66e8a
from configparser import ConfigParser
Brian Stinson aa8548
5fd982
import gitlab
1e7ef8
import json
1e7ef8
import os
00baeb
import sys
a394b1
794ab0
_DEFAULT_API_BASE_URL = 'https://gitlab.com'
794ab0
794ab0
Brian Stinson aa8548
class centpkgClient(cliClient):
794ab0
    def __init__(self, config, name='centpkg'):
794ab0
        self.DEFAULT_CLI_NAME = name
794ab0
5a7f92
        # Save the config for utilities such as get_repo_name()
5a7f92
        centpkg.utils.dist_git_config = config
5a7f92
        centpkg.utils.dist_git_config.add_section('__default')
5a7f92
        centpkg.utils.dist_git_config.set('__default', 'cli_name', name)
5a7f92
Brian Stinson aa8548
        super(centpkgClient, self).__init__(config, name)
Brian Stinson 3edf28
Brian Stinson 3edf28
        self.setup_centos_subparsers()
Brian Stinson 3edf28
Brian Stinson 3edf28
    def setup_centos_subparsers(self):
80f38d
        self.register_do_fork()
615d8f
        self.register_request_gated_side_tag()
aededb
        self.register_current_state()
Brian Stinson 3edf28
80f38d
    def register_do_fork(self):
80f38d
        help_msg = 'Create a new fork of the current repository'
80f38d
        distgit_section = '{0}.distgit'.format(self.name)
794ab0
        # uses default dist-git url in case section is not present
794ab0
        try:
794ab0
            distgit_api_base_url = config_get_safely(self.config, distgit_section, "apibaseurl")
794ab0
        except rpkgError:
794ab0
            distgit_api_base_url = _DEFAULT_API_BASE_URL
80f38d
        description = textwrap.dedent('''
80f38d
            Create a new fork of the current repository
80f38d
80f38d
            Before the operation, you need to generate an API token at
5b7b46
            https://{1}/-/profile/personal_access_tokens, select the "api"
5b7b46
            scope and save it in your local user configuration located
80f38d
            at ~/.config/rpkg/{0}.conf. For example:
80f38d
80f38d
                [{0}.distgit]
80f38d
                token = <api_key_here>
80f38d
80f38d
            Below is a basic example of the command to fork a current repository:
80f38d
80f38d
                {0} fork
80f38d
        '''.format(self.name, urlparse(distgit_api_base_url).netloc))
80f38d
80f38d
        fork_parser = self.subparsers.add_parser(
80f38d
            'fork',
80f38d
            formatter_class=argparse.RawDescriptionHelpFormatter,
80f38d
            help=help_msg,
80f38d
            description=description)
80f38d
        fork_parser.set_defaults(command=self.do_distgit_fork)
80f38d
80f38d
    def do_distgit_fork(self):
80f38d
        """create fork of the distgit repository
80f38d
        That includes creating fork itself using API call and then adding
80f38d
        remote tracked repository
80f38d
        """
80f38d
        distgit_section = '{0}.distgit'.format(self.name)
80f38d
        distgit_api_base_url = config_get_safely(self.config, distgit_section, "apibaseurl")
80f38d
        distgit_token = config_get_safely(self.config, distgit_section, 'token')
80f38d
5b7b46
        ret, repo_path = do_fork(
80f38d
            logger=self.log,
80f38d
            base_url=distgit_api_base_url,
80f38d
            token=distgit_token,
80f38d
            repo_name=self.cmd.repo_name,
80f38d
            namespace=self.cmd.ns,
80f38d
            cli_name=self.name,
80f38d
        )
80f38d
80f38d
        # assemble url of the repo in web browser
5b7b46
        fork_url = '{0}/{1}'.format(
80f38d
            distgit_api_base_url.rstrip('/'),
5b7b46
            repo_path,
80f38d
        )
80f38d
80f38d
        if ret:
80f38d
            msg = "Fork of the repository has been created: '{0}'"
80f38d
        else:
80f38d
            msg = "Repo '{0}' already exists."
80f38d
        self.log.info(msg.format(fork_url))
80f38d
5b7b46
        distgit_remote_base_url = self.config.get(
5b7b46
            '{0}'.format(self.name),
5b7b46
            "gitbaseurl",
5b7b46
            vars={'repo': '{0}/{1}'.format(self.cmd.ns, self.cmd.repo_name)},
5b7b46
        )
5b7b46
        remote_name = repo_path.split('/')[0]
5b7b46
80f38d
        ret = do_add_remote(
80f38d
            base_url=distgit_api_base_url,
80f38d
            remote_base_url=distgit_remote_base_url,
80f38d
            repo=self.cmd.repo,
5b7b46
            repo_path=repo_path,
5b7b46
            remote_name=remote_name,
80f38d
        )
80f38d
        if ret:
80f38d
            msg = "Adding as remote '{0}'."
80f38d
        else:
80f38d
            msg = "Remote with name '{0}' already exists."
5b7b46
        self.log.info(msg.format(remote_name))
615d8f
aededb
    def register_current_state(self):
aededb
        """Register command line parser for subcommand current-state"""
aededb
        parser = self.subparsers.add_parser(
aededb
            "current-state",
aededb
            help="Show the phase and state of current dist-git branch - Internal Only",
aededb
        )
aededb
        parser.set_defaults(command=self.request_current_state)
aededb
aededb
    # FIXME: This is alot of duplication.
aededb
    #        Get it working now.  De-duplicate later
aededb
    def request_current_state(self):
aededb
        # Only run if we have internal configuraions, or scratch
aededb
        internal_config_file = "/etc/rpkg/centpkg_internal.conf"
aededb
        if os.path.exists(internal_config_file):
aededb
            # Get our internal only variables
b66e8a
            cfg = ConfigParser()
aededb
            cfg.read(internal_config_file)
aededb
            pp_api_url = config_get_safely(cfg, "centpkg.internal", 'pp_api_url')
aededb
            gitbz_query_url = config_get_safely(cfg, "centpkg.internal", 'gitbz_query_url')
aededb
            rhel_dist_git = config_get_safely(cfg, "centpkg.internal", 'rhel_dist_git')
aededb
aededb
            # Find out divergent branch and stabalization
aededb
            self.log.info("Checking current state:")
aededb
            stream_version = self.cmd.target.split('-')[0]
aededb
            rhel_version = centpkg.utils.stream_mapping(stream_version)
aededb
            try:
3c9d74
                x_version, active_y, is_beta, in_stabilization = centpkg.utils.determine_active_y_version(rhel_version, pp_api_url)
aededb
            except AssertionError as e:
aededb
                self.log.error("  Error: centpkg cannot determine the development phase.")
aededb
                self.log.error("         Please file an issue at https://git.centos.org/centos/centpkg")
aededb
                self.log.error("  Workaround: Use the --rhel-target option")
aededb
                self.log.error("Exiting")
aededb
                raise SystemExit(1)
3c9d74
            if is_beta:
3c9d74
                # Special case: for X.0 betas, there will never be a prior branch
3c9d74
                # In this case, always work on the active branch.
3c9d74
                divergent_branch = True
3c9d74
            else:
3c9d74
                divergent_branch = centpkg.utils.does_divergent_branch_exist(
3c9d74
                                        self.cmd.repo_name,
3c9d74
                                        x_version,
3c9d74
                                        active_y,
3c9d74
                                        rhel_dist_git,
3c9d74
                                        "rpms")
aededb
            # Good to know
aededb
            if in_stabilization :
aededb
                self.log.info("    we are in stabilization mode.")
aededb
            else:
aededb
                self.log.info("    we are not in stabilization mode.")
3c9d74
            if divergent_branch and not is_beta:
aededb
                self.log.info("    a divergent branch was found.")
3c9d74
            elif divergent_branch and is_beta:
3c9d74
                self.log.info("    we are working on a beta release.")
aededb
            else:
aededb
                self.log.info("    a divergent branch was not found.")
aededb
        else:
aededb
            self.log.error("NO INTERNAL CONFIGURATION")
aededb
            self.log.error("Hint: If you are internal, install the rhel-packager package from https://download.devel.redhat.com/rel-eng/RCMTOOLS/latest-RCMTOOLS-2-RHEL-9/compose/BaseOS/x86_64/os/Packages/")
aededb
            self.log.error("Exiting")
aededb
            raise SystemExit(1)
aededb
1e7ef8
    # Overloaded build
1e7ef8
    def register_build(self):
1e7ef8
        # Do all the work from the super class
1e7ef8
        super(centpkgClient, self).register_build()
1e7ef8
        build_parser = self.subparsers.choices['build']
1e7ef8
        build_parser.formatter_class = argparse.RawDescriptionHelpFormatter
1e7ef8
        build_parser.description = textwrap.dedent('''
1e7ef8
            {0}
1e7ef8
1e7ef8
            centpkg now sets the rhel metadata with --rhel-target.
1e7ef8
             * exception - This will build for the current in-development Y-stream release.
1e7ef8
               It is equivalent to passing latest when not in the Blocker and Exception Phase.
1e7ef8
             * zstream - If pre-GA of a y-stream release, this will build for 0day.
1e7ef8
               If post-GA of a Y-stream release, this will build for the Z-stream of that release.
1e7ef8
             * latest - This will always build for the next Y-stream release
e25a63
             * none - This will not build in brew.  No rhel metadata is set.
1e7ef8
1e7ef8
        '''.format('\n'.join(textwrap.wrap(build_parser.description))))
1e7ef8
1e7ef8
        # Now add our additional option
1e7ef8
        build_parser.add_argument(
1e7ef8
            '--rhel-target',
e25a63
            choices=['exception', 'zstream', 'latest', 'none'],
1e7ef8
            help='Set the rhel-target metadata')
1e7ef8
1e7ef8
    # Overloaded _build
1e7ef8
    def _build(self, sets=None):
1e7ef8
e25a63
        # Only do rhel-target if we are centpkg, not centpkg-sig, or if we are doing scratch
bd2c30
        if not os.path.basename(sys.argv[0]).endswith('-sig') and not self.args.scratch:
00baeb
e25a63
            # Only run if we have internal configuraions
00baeb
            internal_config_file = "/etc/rpkg/centpkg_internal.conf"
00baeb
            if os.path.exists(internal_config_file):
f6364c
f6364c
                # If rhel-target is set, no need to check, just use it
f6364c
                if hasattr(self.args, 'rhel_target') and self.args.rhel_target:
e25a63
                    # If rhel-target set to none, do nothing with metadata
e25a63
                    if self.args.rhel_target.lower() != "none":
e25a63
                        # If custom-user-metadata set, add onto it
e25a63
                        if hasattr(self.args, 'custom_user_metadata') and self.args.custom_user_metadata:
e25a63
                            try:
e25a63
                                temp_custom_user_metadata = json.loads(self.args.custom_user_metadata)
e25a63
                            # Use ValueError instead of json.JSONDecodeError for Python 2 and 3 compatibility
e25a63
                            except ValueError as e:
e25a63
                                self.parser.error("--custom-user-metadata is not valid JSON: %s" % e)
e25a63
                            if not isinstance(temp_custom_user_metadata, dict):
e25a63
                                self.parser.error("--custom-user-metadata must be a JSON object")
e25a63
                            temp_custom_user_metadata["rhel-target"] = self.args.rhel_target
e25a63
                        else:
e25a63
                            temp_custom_user_metadata = {"rhel-target": self.args.rhel_target}
e25a63
                        self.args.custom_user_metadata = json.dumps(temp_custom_user_metadata)
f6364c
f6364c
                else:
f6364c
                    # Get our internal only variables
b66e8a
                    cfg = ConfigParser()
f6364c
                    cfg.read(internal_config_file)
f6364c
                    pp_api_url = config_get_safely(cfg, "centpkg.internal", 'pp_api_url')
f6364c
                    gitbz_query_url = config_get_safely(cfg, "centpkg.internal", 'gitbz_query_url')
f6364c
                    rhel_dist_git = config_get_safely(cfg, "centpkg.internal", 'rhel_dist_git')
f6364c
f6364c
                    # Find out divergent branch and stabalization
f6364c
                    self.log.info("Checking rhel-target information:")
f6364c
                    stream_version = self.cmd.target.split('-')[0]
f6364c
                    rhel_version = centpkg.utils.stream_mapping(stream_version)
9f10a8
3c9d74
                    try:
3c9d74
                        x_version, active_y, is_beta, in_stabilization = centpkg.utils.determine_active_y_version(rhel_version, pp_api_url)
3c9d74
                    except AssertionError as e:
3c9d74
                        self.log.error("  Error: centpkg cannot determine the development phase.")
3c9d74
                        self.log.error("         Please file an issue at https://git.centos.org/centos/centpkg")
3c9d74
                        self.log.error("  Workaround: Use the --rhel-target option")
3c9d74
                        self.log.error("Exiting")
3c9d74
                        raise SystemExit(1)
3c9d74
3c9d74
                    if is_beta:
3c9d74
                        # Special case: for X.0 betas, there will never be a prior branch
3c9d74
                        # In this case, always work on the active branch.
9f10a8
                        divergent_branch = True
f6364c
                    else:
9f10a8
                        divergent_branch = centpkg.utils.does_divergent_branch_exist(
9f10a8
                                                self.cmd.repo_name,
3c9d74
                                                x_version,
9f10a8
                                                active_y,
9f10a8
                                                rhel_dist_git,
9f10a8
                                                "rpms")
9f10a8
3c9d74
                    # Good to know
3c9d74
                    if divergent_branch and not is_beta:
3c9d74
                        self.log.info("    a divergent branch was found.")
3c9d74
                    elif divergent_branch and is_beta:
3c9d74
                        self.log.info("    we are working on a beta release.")
3c9d74
                    else:
3c9d74
                        self.log.info("    a divergent branch was not found.")
3c9d74
                    if in_stabilization :
3c9d74
                        self.log.info("    we are in stabilization mode.")
3c9d74
                    else:
3c9d74
                        self.log.info("    we are not in stabilization mode.")
f6364c
f6364c
                    # Update args.custom_user_metadata
f6364c
                    if hasattr(self.args, 'custom_user_metadata') and self.args.custom_user_metadata:
f6364c
                        try:
f6364c
                            temp_custom_user_metadata = json.loads(self.args.custom_user_metadata)
f6364c
                        # Use ValueError instead of json.JSONDecodeError for Python 2 and 3 compatibility
f6364c
                        except ValueError as e:
f6364c
                            self.parser.error("--custom-user-metadata is not valid JSON: %s" % e)
f6364c
                        if not isinstance(temp_custom_user_metadata, dict):
f6364c
                            self.parser.error("--custom-user-metadata must be a JSON object")
00baeb
                        if divergent_branch:
00baeb
                            temp_custom_user_metadata["rhel-target"] = "latest"
00baeb
                        elif not divergent_branch and not in_stabilization :
00baeb
                            temp_custom_user_metadata["rhel-target"] = "zstream"
00baeb
                        else:
739c74
                            self.log.error("We are currently in Stabalization mode")
739c74
                            self.log.error("You must either set the rhel-target (--rhel-target)")
739c74
                            self.log.error("or branch for the previous version.")
739c74
                            self.log.error("Exiting")
739c74
                            raise SystemExit(1)
00baeb
                        self.args.custom_user_metadata = json.dumps(temp_custom_user_metadata)
1e7ef8
                    else:
00baeb
                        if divergent_branch:
00baeb
                            self.args.custom_user_metadata = '{"rhel-target": "latest"}'
00baeb
                        elif not divergent_branch and not in_stabilization :
00baeb
                            self.args.custom_user_metadata = '{"rhel-target": "zstream"}'
00baeb
                        else:
739c74
                            self.log.error("We are currently in Stabalization mode")
739c74
                            self.log.error("You must either set the rhel-target (--rhel-target)")
739c74
                            self.log.error("or branch for the previous version.")
739c74
                            self.log.error("Exiting")
739c74
                            raise SystemExit(1)
00baeb
f6364c
                    # Good to know
f6364c
                    self.log.info("    rhel-target: " + json.loads(self.args.custom_user_metadata)['rhel-target'])
00baeb
00baeb
            else:
739c74
                self.log.error("NO INTERNAL CONFIGURATION")
739c74
                self.log.error("Only scratch builds are allowed without internal configurations")
739c74
                self.log.error("Hint: Install the rhel-packager package from https://download.devel.redhat.com/rel-eng/RCMTOOLS/latest-RCMTOOLS-2-RHEL-9/compose/BaseOS/x86_64/os/Packages/ if you want to build the package via internal (Red Hat) configuration.")
739c74
                self.log.error("Exiting")
739c74
                raise SystemExit(1)
00baeb
1e7ef8
        # Proceed with build
1e7ef8
        return super(centpkgClient, self)._build(sets)
1e7ef8
1e7ef8
615d8f
    def register_request_gated_side_tag(self):
615d8f
        """Register command line parser for subcommand request-gated-side-tag"""
615d8f
        parser = self.subparsers.add_parser(
615d8f
            "request-gated-side-tag",
615d8f
            help="Create a new dynamic gated side tag",
615d8f
        )
615d8f
        parser.add_argument("--base-tag", help="name of base tag")
615d8f
        parser.set_defaults(command=self.request_gated_side_tag)
615d8f
615d8f
    def request_gated_side_tag(self):
615d8f
        self.args.suffix = "stack-gate"
615d8f
        super(centpkgClient, self).request_side_tag()
5fd982
5fd982
    def clone(self):
5fd982
        # Since gitlab allows git repos to be checked out with incorrect capitilization
5fd982
        #   we need to check the spelling when cloning
5fd982
        gl = gitlab.Gitlab(_DEFAULT_API_BASE_URL)
75ceb3
        if "/" in self.args.repo[0]:
fb42b2
            project_name_with_namespace = "redhat/centos-stream/"+self.args.repo[0]
fb42b2
            short_name=self.args.repo[0].split("/")[1]
5fd982
        else:
5fd982
            project_name_with_namespace = "redhat/centos-stream/rpms/"+self.args.repo[0]
5fd982
            short_name=self.args.repo[0]
5fd982
        try:
5fd982
            project = gl.projects.get(project_name_with_namespace)
5fd982
        except gitlab.exceptions.GitlabGetError as e:
739c74
            self.log.error("There is no repository with the given name. Did you spell it correctly?")
739c74
            self.log.error("Fatal: ")
739c74
            self.log.error(e)
739c74
            raise SystemExit(1)
5fd982
        if not project.name == short_name:
739c74
            self.log.error("Fatal: ")
739c74
            self.log.error("Project name is wrong: " + short_name + " it should be: " + project.name)
739c74
            raise SystemExit(1)
5fd982
        super(centpkgClient, self).clone()