|
|
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 |
|
|
|
5fd982 |
import gitlab
|
|
|
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()
|
|
|
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
|
|
|
aededb |
cfg = ConfigParser.SafeConfigParser()
|
|
|
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:
|
|
|
aededb |
active_y, 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)
|
|
|
aededb |
divergent_branch = centpkg.utils.does_divergent_branch_exist(
|
|
|
aededb |
self.cmd.repo_name,
|
|
|
aededb |
rhel_version,
|
|
|
aededb |
active_y,
|
|
|
aededb |
rhel_dist_git,
|
|
|
aededb |
pp_api_url,
|
|
|
aededb |
"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.")
|
|
|
aededb |
if divergent_branch :
|
|
|
aededb |
self.log.info(" a divergent branch was found.")
|
|
|
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
|
|
|
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:
|
|
|
739c74 |
self.log.error(" Error: centpkg cannot determine the development phase.")
|
|
|
739c74 |
self.log.error(" Please file an issue at https://git.centos.org/centos/centpkg")
|
|
|
739c74 |
self.log.error(" Workaround: Use the --rhel-target option")
|
|
|
739c74 |
self.log.error("Exiting")
|
|
|
739c74 |
raise SystemExit(1)
|
|
|
f6364c |
divergent_branch = centpkg.utils.does_divergent_branch_exist(
|
|
|
f6364c |
self.cmd.repo_name,
|
|
|
f6364c |
rhel_version,
|
|
|
6b939c |
active_y,
|
|
|
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:
|
|
|
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()
|