Blame src/centpkg/cli.py

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
80f38d
from six.moves.urllib_parse import urlparse
1e7ef8
import six.moves.configparser as ConfigParser
Brian Stinson aa8548
1e7ef8
import json
1e7ef8
import koji
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()
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
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
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',
1e7ef8
            choices=['exception', 'zstream', 'latest'],
1e7ef8
            help='Set the rhel-target metadata')
1e7ef8
1e7ef8
    # Overloaded _build
1e7ef8
    def _build(self, sets=None):
1e7ef8
00baeb
        # Only do rhel-target if we are centpkg, not centpkg-sig
bd2c30
        if not os.path.basename(sys.argv[0]).endswith('-sig') and not self.args.scratch:
00baeb
00baeb
            # Only run if we have internal configuraions, or scratch
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:
f6364c
                    # If custom-user-metadata set, add onto it
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
                        temp_custom_user_metadata["rhel-target"] = self.args.rhel_target
1e7ef8
                    else:
f6364c
                        temp_custom_user_metadata = {"rhel-target": self.args.rhel_target}
f6364c
                    self.args.custom_user_metadata = json.dumps(temp_custom_user_metadata)
f6364c
f6364c
                else:
f6364c
                    # Get our internal only variables
f6364c
                    cfg = ConfigParser.SafeConfigParser()
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)
37fb23
                    try:
37fb23
                        active_y, in_stabilization = centpkg.utils.determine_active_y_version(rhel_version, pp_api_url)
37fb23
                    except AssertionError as e:
37fb23
                        self.log.info("  Error: centpkg cannot determine the development phase.")
37fb23
                        self.log.info("         Please file an issue at https://git.centos.org/centos/centpkg")
37fb23
                        self.log.info("  Workaround: Use the --rhel-target option")
37fb23
                        self.log.info("Exiting")
37fb23
                        raise SystemExit
f6364c
                    divergent_branch = centpkg.utils.does_divergent_branch_exist(
f6364c
                                            self.cmd.repo_name,
f6364c
                                            rhel_version,
f6364c
                                            rhel_dist_git,
f6364c
                                            pp_api_url,
f6364c
                                            "rpms")
f6364c
f6364c
                    # Good to know
f6364c
                    if divergent_branch :
f6364c
                        self.log.info("    a divergent branch was found.")
f6364c
                    else:
f6364c
                        self.log.info("    a divergent branch was not found.")
f6364c
                    if in_stabilization :
f6364c
                        self.log.info("    we are in stabilization mode.")
f6364c
                    else:
f6364c
                        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:
00baeb
                            self.log.info("We are currently in Stabalization mode")
00baeb
                            self.log.info("You must either set the rhel-target (--rhel-target)")
00baeb
                            self.log.info("or branch for the previous version.")
00baeb
                            self.log.info("Exiting")
00baeb
                            raise SystemExit
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:
00baeb
                            self.log.info("We are currently in Stabalization mode")
00baeb
                            self.log.info("You must either set the rhel-target (--rhel-target)")
00baeb
                            self.log.info("or branch for the previous version.")
00baeb
                            self.log.info("Exiting")
00baeb
                            raise SystemExit
00baeb
f6364c
                    # Good to know
f6364c
                    self.log.info("    rhel-target: " + json.loads(self.args.custom_user_metadata)['rhel-target'])
00baeb
00baeb
            else:
bd2c30
                self.log.info("NO INTERNAL CONFIGURATION")
bd2c30
                self.log.info("Only scratch builds are allowed without internal configurations")
bd2c30
                self.log.info("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.")
bd2c30
                self.log.info("Exiting")
bd2c30
                raise SystemExit
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()