From d833981c70fcbcfce159f564d0757e86216f4b02 Mon Sep 17 00:00:00 2001 From: Davlet Panech Date: Thu, 19 May 2022 12:17:20 -0400 Subject: [PATCH] debian: new command: stx shell New command "stx shell", a replacement for "stx control enter" with better syntax & semantics: - Accepts an optional shell command to execute inside the container - Disables TTY emulaltion & shell's interactive mode if current STDIN is not a terminal - Old form, "stx control enter" remains for compatibility, but prints a deprecation warning. TESTS ============================================== - Made sure "stx control enter" and "echo command | stx control enter" still work. - Manually tested "stx shell" with various parameters Story: 2010055 Task: 45486 Change-Id: I6f8fd1fc2d02e01c1d81bef97e3ddbe9181e1708 Signed-off-by: Davlet Panech --- stx/lib/stx/config.py | 5 ++ stx/lib/stx/stx_control.py | 33 ++---------- stx/lib/stx/stx_main.py | 21 ++++++++ stx/lib/stx/stx_shell.py | 105 +++++++++++++++++++++++++++++++++++++ 4 files changed, 134 insertions(+), 30 deletions(-) create mode 100644 stx/lib/stx/stx_shell.py diff --git a/stx/lib/stx/config.py b/stx/lib/stx/config.py index 54f6c3d3c..210a59e83 100644 --- a/stx/lib/stx/config.py +++ b/stx/lib/stx/config.py @@ -23,6 +23,8 @@ from stx import utils logger = logging.getLogger('STX-Config') utils.set_logger(logger) +ALL_CONTAINER_NAMES = ['builder', 'pkgbuilder', 'lat', 'docker', 'repomgr'] + def require_env(var): value = os.getenv(var) @@ -108,6 +110,9 @@ class Config: assert self.data return self.helm_cmd + def all_container_names(self): + return ALL_CONTAINER_NAMES + [] + @property def insecure_docker_reg_list(self): """List of insecure docker registries we are allowed to access""" diff --git a/stx/lib/stx/stx_control.py b/stx/lib/stx/stx_control.py index 6ff42e2e8..8035167be 100644 --- a/stx/lib/stx/stx_control.py +++ b/stx/lib/stx/stx_control.py @@ -23,6 +23,7 @@ import time from stx import helper # pylint: disable=E0611 from stx.k8s import KubeHelper +from stx import stx_shell from stx import utils # pylint: disable=E0611 helmchartdir = 'stx/stx-build-tools-chart/stx-builder' @@ -38,6 +39,7 @@ class HandleControlTask: self.logger = logging.getLogger('STX-Control') self.abs_helmchartdir = os.path.join(os.environ['PRJDIR'], helmchartdir) + self.shell = stx_shell.HandleShellTask(config) utils.set_logger(self.logger) def configurePulp(self): @@ -235,36 +237,7 @@ stx-pkgbuilder/configmap/') sys.exit(1) def handleEnterTask(self, args): - default_docker = 'builder' - container_list = ['builder', 'pkgbuilder', 'repomgr', 'lat', 'docker'] - prefix_exec_cmd = self.config.kubectl() + ' exec -ti ' - - if args.dockername: - if args.dockername not in container_list: - self.logger.error('Please input the correct docker name \ -argument. eg: %s \n', container_list) - sys.exit(1) - default_docker = args.dockername - - podname = self.k8s.get_pod_name(default_docker) - if podname: - if default_docker == 'builder': - cmd = prefix_exec_cmd + podname - cmd = cmd + ' -- bash -l -c \'runuser -u ${MYUNAME} -- bash \ ---rcfile /home/$MYUNAME/userenv\'' - elif default_docker == 'docker': - cmd = prefix_exec_cmd + podname + ' -- sh' - else: - cmd = prefix_exec_cmd + podname + ' -- bash' - self.logger.debug('Execute the enter command: %s', cmd) - # Return exit status to shell w/o raising an exception - # in case the user did "echo COMMAND ARGS | stx control enter" - ret = subprocess.call(cmd, shell=True) - sys.exit(ret) - else: - self.logger.error('Please ensure the docker container you want to \ -enter has been started!!!\n') - sys.exit(1) + self.shell.cmd_control_enter(args) def handleControl(self, args): diff --git a/stx/lib/stx/stx_main.py b/stx/lib/stx/stx_main.py index 605de4db2..6ad7e0298 100644 --- a/stx/lib/stx/stx_main.py +++ b/stx/lib/stx/stx_main.py @@ -20,6 +20,7 @@ from stx import stx_build # pylint: disable=E0611 from stx import stx_configparser # pylint: disable=E0611 from stx import stx_control # pylint: disable=E0611 from stx import stx_repomgr # pylint: disable=E0611 +from stx import stx_shell # pylint: disable=E0611 from stx import utils # pylint: disable=E0611 logger = logging.getLogger('STX') @@ -39,6 +40,7 @@ class CommandLine: self.handlecontrol = stx_control.HandleControlTask(self.config) self.handlebuild = stx_build.HandleBuildTask(self.config) self.handlerepomgr = stx_repomgr.HandleRepomgrTask(self.config) + self.handleshell = stx_shell.HandleShellTask(self.config) self.parser = self.parseCommandLine() def parseCommandLine(self): @@ -157,6 +159,25 @@ remove_repo|search_pkg|upload_pkg|delete_pkg ]') parser.add_argument('-v', '--version', help='Stx build tools version\n\n', action='version', version='%(prog)s 1.0.0') + + shell_subparser = subparsers.add_parser( + 'shell', + help='Run a shell command or start an interactive shell') + shell_subparser.add_argument( + '-c', '--command', + help='Shell snippet to execute inside a container. If omitted ' + + 'start a shell that reads commands from STDIN.') + shell_subparser.add_argument( + '--no-tty', + help="Disable terminal emulation for STDIN and start shell in " + + "non-interactive mode, even if STDIN is a TTY", + action='store_const', const=True) + shell_subparser.add_argument( + '--container', + metavar='builder|pkgbuilder|lat|repomgr|docker', + help='Container name (default: builder)') + shell_subparser.set_defaults(handle=self.handleshell.cmd_shell) + return parser def parseArgs(self): diff --git a/stx/lib/stx/stx_shell.py b/stx/lib/stx/stx_shell.py new file mode 100644 index 000000000..6251db665 --- /dev/null +++ b/stx/lib/stx/stx_shell.py @@ -0,0 +1,105 @@ +# Copyright (c) 2021 Wind River Systems, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging +import os +import shlex +from stx.k8s import KubeHelper +from stx import utils # pylint: disable=E0611 +import subprocess +import sys + +logger = logging.getLogger('STX-Shell') +utils.set_logger(logger) + + +def quote(wordlist): + if hasattr(wordlist, '__iter__') and not isinstance(wordlist, str): + return ' '.join([shlex.quote(w) for w in wordlist]) + return shlex.quote(wordlist) + + +class HandleShellTask: + + def __init__(self, config): + self.config = config + self.k8s = KubeHelper(config) + self.all_container_names = self.config.all_container_names() + + def __get_container_name(self, container): + if container not in self.config.all_container_names(): + raise NameError('Invalid container %s, expecting one of: %s' + % (container, self.all_container_names)) + name = self.k8s.get_pod_name(container) + if not name: + raise RuntimeError('Container "%s" is not running' % container) + return name + + def create_shell_command(self, container, command, no_tty): + kubectl_args = ['exec', '--stdin'] + + if not no_tty and sys.stdin.isatty(): + kubectl_args += ['--tty'] + + kubectl_args += [self.__get_container_name(container)] + + # builder + if container == 'builder': + + # This environment script is always required + req_env_file = '/home/$MYUNAME/userenv' + + user_cmd = 'runuser -u $MYUNAME -- ' + + # No command given & STDIN is a terminal: interactive mode + if command is None and not no_tty and sys.stdin.isatty(): + user_cmd += 'bash --rcfile %s -i' % req_env_file + # Command given, or STDIN is not a terminal: non-interactive mode + else: + user_cmd += 'env BASH_ENV=%s bash --norc' % req_env_file + if command is not None: + user_cmd += ' -c ' + user_cmd += quote(command) + + kubectl_args += ['--', 'bash', '-l', '-c', user_cmd] + + elif container == 'docker': + kubectl_args += ['--', 'sh', '-l'] + if command: + kubectl_args += ['-c', command] + else: + kubectl_args += ['--', 'bash', '-l'] + if command: + kubectl_args += ['-c', command] + + return self.config.kubectl() + ' ' + quote(kubectl_args) + + def _do_shell(self, args, no_tty, command, container_arg='container'): + container = getattr(args, container_arg) or 'builder' + if container not in self.all_container_names: + logger.error("--%s must be one of: %s", + container_arg, self.all_container_names) + sys.exit(1) + + shell_command = self.create_shell_command(container, command, no_tty) + logger.debug('%s', shell_command) + shell_status = subprocess.call(shell_command, shell=True) + sys.exit(shell_status) + + def cmd_shell(self, args): + self._do_shell(args, args.no_tty, args.command) + + def cmd_control_enter(self, args): + logger.warn("""This command is deprecated, please use "stx shell" instead""") + self._do_shell(args, False, None, 'dockername')