diff --git a/src/kiwi/runner.py b/src/kiwi/runner.py index 026f25b..3fba2c9 100644 --- a/src/kiwi/runner.py +++ b/src/kiwi/runner.py @@ -1,8 +1,10 @@ # system import logging +import subprocess # local from . import subcommands +from .subcommands.utils.executable import Executable from .parser import Parser @@ -15,6 +17,16 @@ class Runner: __commands = [] def __init__(self): + # probe for Docker access + try: + Executable('docker').run( + ['ps'], + check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL + ) + + except subprocess.CalledProcessError: + raise PermissionError("Cannot access docker, please get into the docker group or run as root!") + # setup all subcommands for className in subcommands.__all__: cmd = getattr(subcommands, className) diff --git a/src/kiwi/subcommands/build.py b/src/kiwi/subcommands/build.py index 5676c17..ed4b4a6 100644 --- a/src/kiwi/subcommands/build.py +++ b/src/kiwi/subcommands/build.py @@ -1,6 +1,5 @@ # local from ._subcommand import ServiceCommand -from .utils.dockercommand import DockerCommand class BuildCommand(ServiceCommand): @@ -14,7 +13,6 @@ class BuildCommand(ServiceCommand): ) def _run_services(self, runner, args, project, services): - DockerCommand('docker-compose').run(project, [ - 'build', '--pull', *services - ]) + project.compose_run(['build', '--pull', *services]) + return True diff --git a/src/kiwi/subcommands/cmd.py b/src/kiwi/subcommands/cmd.py index f08c6dc..0b12122 100644 --- a/src/kiwi/subcommands/cmd.py +++ b/src/kiwi/subcommands/cmd.py @@ -3,7 +3,6 @@ import logging # local from ._subcommand import ProjectCommand -from .utils.dockercommand import DockerCommand class CmdCommand(ProjectCommand): @@ -36,8 +35,6 @@ class CmdCommand(ProjectCommand): logging.debug(f"Updated args: {args}") # run with split compose_cmd argument - DockerCommand('docker-compose').run(projects[0], [ - args.compose_cmd, *args.compose_args - ]) + projects[0].compose_run([args.compose_cmd, *args.compose_args]) return True diff --git a/src/kiwi/subcommands/down.py b/src/kiwi/subcommands/down.py index 286f65f..6023919 100644 --- a/src/kiwi/subcommands/down.py +++ b/src/kiwi/subcommands/down.py @@ -1,6 +1,5 @@ # local from ._subcommand import ServiceCommand -from .utils.dockercommand import DockerCommand from .utils.misc import are_you_sure @@ -27,16 +26,12 @@ class DownCommand(ServiceCommand): def _run_projects(self, runner, args, projects): for project in projects: - DockerCommand('docker-compose').run(project, [ - 'down' - ]) + project.compose_run(['down']) + return True def _run_services(self, runner, args, project, services): - DockerCommand('docker-compose').run(project, [ - 'stop', *services - ]) - DockerCommand('docker-compose').run(project, [ - 'rm', '-f', *services - ]) + project.compose_run(['stop', *services]) + project.compose_run(['rm', '-f', *services]) + return True diff --git a/src/kiwi/subcommands/logs.py b/src/kiwi/subcommands/logs.py index 280a966..fcb1d3f 100644 --- a/src/kiwi/subcommands/logs.py +++ b/src/kiwi/subcommands/logs.py @@ -1,6 +1,5 @@ # local from ._subcommand import ServiceCommand -from .utils.dockercommand import DockerCommand class LogsCommand(ServiceCommand): @@ -31,10 +30,11 @@ class LogsCommand(ServiceCommand): if services: compose_cmd = [*compose_cmd, *args.services] - # use 'less' viewer if output will be static if args.follow: - DockerCommand('docker-compose').run(project, compose_cmd) + project.compose_run(compose_cmd) + else: - DockerCommand('docker-compose').run_less(project, compose_cmd) + # use 'less' viewer if output is static + project.compose_run_less(compose_cmd) return True diff --git a/src/kiwi/subcommands/net.py b/src/kiwi/subcommands/net.py index 63236fc..8a630b1 100644 --- a/src/kiwi/subcommands/net.py +++ b/src/kiwi/subcommands/net.py @@ -4,7 +4,7 @@ import subprocess # local from ._subcommand import SubCommand -from .utils.dockercommand import DockerCommand +from .utils.executable import Executable from .utils.misc import are_you_sure # parent @@ -12,7 +12,7 @@ from ..config import LoadedConfig def _find_net(net_name): - ps = DockerCommand('docker').run(None, [ + ps = Executable('docker').run([ 'network', 'ls', '--filter', f"name={net_name}", '--format', '{{.Name}}' ], stdout=subprocess.PIPE) @@ -41,7 +41,7 @@ class NetUpCommand(SubCommand): return True try: - DockerCommand('docker').run(None, [ + Executable('docker').run([ 'network', 'create', '--driver', 'bridge', '--internal', @@ -77,7 +77,7 @@ class NetDownCommand(SubCommand): try: if are_you_sure("This will bring down this instance's hub network!"): if runner.run('down'): - DockerCommand('docker').run(None, [ + Executable('docker').run([ 'network', 'rm', net_name ], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) diff --git a/src/kiwi/subcommands/pull.py b/src/kiwi/subcommands/pull.py index debe467..a54144f 100644 --- a/src/kiwi/subcommands/pull.py +++ b/src/kiwi/subcommands/pull.py @@ -1,6 +1,5 @@ # local from ._subcommand import ServiceCommand -from .utils.dockercommand import DockerCommand class PullCommand(ServiceCommand): @@ -14,7 +13,6 @@ class PullCommand(ServiceCommand): ) def _run_services(self, runner, args, project, services): - DockerCommand('docker-compose').run(project, [ - 'pull', '--ignore-pull-failures', *services - ]) + project.compose_run(['pull', '--ignore-pull-failures', *services]) + return True diff --git a/src/kiwi/subcommands/push.py b/src/kiwi/subcommands/push.py index b026b1b..48c3ab2 100644 --- a/src/kiwi/subcommands/push.py +++ b/src/kiwi/subcommands/push.py @@ -1,6 +1,5 @@ # local from ._subcommand import ServiceCommand -from .utils.dockercommand import DockerCommand class PushCommand(ServiceCommand): @@ -14,7 +13,6 @@ class PushCommand(ServiceCommand): ) def _run_services(self, runner, args, project, services): - DockerCommand('docker-compose').run(project, [ - 'push', *services - ]) + project.compose_run(['push', *services]) + return True diff --git a/src/kiwi/subcommands/sh.py b/src/kiwi/subcommands/sh.py index bf1d586..08a41b1 100644 --- a/src/kiwi/subcommands/sh.py +++ b/src/kiwi/subcommands/sh.py @@ -4,7 +4,6 @@ import subprocess # local from ._subcommand import ServiceCommand -from .utils.dockercommand import DockerCommand # parent from ..config import LoadedConfig @@ -18,9 +17,10 @@ def _service_has_executable(project, service, exe_name): try: # test if desired shell exists - DockerCommand('docker-compose').run(project, [ - 'exec', service, '/bin/sh', '-c', f"which {exe_name}" - ], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + project.compose_run( + ['exec', service, '/bin/sh', '-c', f"which {exe_name}"], + check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL + ) return True except subprocess.CalledProcessError as e: @@ -92,9 +92,7 @@ class ShCommand(ServiceCommand): if shell is not None: # spawn shell - DockerCommand('docker-compose').run(project, [ - 'exec', service, shell - ]) + project.compose_run(['exec', service, shell]) return True return False diff --git a/src/kiwi/subcommands/up.py b/src/kiwi/subcommands/up.py index ae4261d..f28e310 100644 --- a/src/kiwi/subcommands/up.py +++ b/src/kiwi/subcommands/up.py @@ -1,6 +1,5 @@ # local from ._subcommand import ServiceCommand -from .utils.dockercommand import DockerCommand class UpCommand(ServiceCommand): @@ -21,9 +20,7 @@ class UpCommand(ServiceCommand): def _run_services(self, runner, args, project, services): if runner.run('net-up'): - DockerCommand('docker-compose').run(project, [ - 'up', '-d', *services - ]) + project.compose_run(['up', '-d', *services]) return True return False diff --git a/src/kiwi/subcommands/utils/dockercommand.py b/src/kiwi/subcommands/utils/dockercommand.py deleted file mode 100644 index ea2cc2f..0000000 --- a/src/kiwi/subcommands/utils/dockercommand.py +++ /dev/null @@ -1,67 +0,0 @@ -# system -import logging -import os -import subprocess - -# local -from .executable import Executable - -# parent -from ..._constants import CONF_DIRECTORY_NAME -from ...config import LoadedConfig - - -def _update_kwargs(project, **kwargs): - # enabled project given: command affects a project in this instance - if project is not None and project.is_enabled(): - config = LoadedConfig.get() - - # execute command in project directory - kwargs['cwd'] = project.dir_name() - - # ensure there is an environment - if 'env' not in kwargs: - kwargs['env'] = {} - - # create environment variables for docker commands - kwargs['env'].update({ - 'COMPOSE_PROJECT_NAME': project.get_name(), - 'KIWI_HUB_NAME': config['network:name'], - 'CONFDIR': os.path.join(config['runtime:storage'], CONF_DIRECTORY_NAME), - 'TARGETDIR': project.target_dir_name() - }) - - logging.debug(f"kwargs updated: {kwargs}") - - return kwargs - - -class DockerCommand(Executable): - __has_tried = False - - def __init__(self, exe_name): - super().__init__(exe_name) - - if not DockerCommand.__has_tried: - try: - Executable('docker').run( - ['ps'], - check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL - ) - DockerCommand.__has_tried = True - - except subprocess.CalledProcessError: - raise PermissionError("Cannot access docker, please get into the docker group or run as root!") - - def run(self, project, process_args, **kwargs): - # equivalent to 'super().run' but agnostic of nested class construct - return super().__getattr__("run")( - process_args, - **_update_kwargs(project, **kwargs) - ) - - def run_less(self, project, process_args, **kwargs): - return super().__getattr__("run_less")( - process_args, - **_update_kwargs(project, **kwargs) - ) diff --git a/src/kiwi/subcommands/utils/project.py b/src/kiwi/subcommands/utils/project.py index d371d83..1037770 100644 --- a/src/kiwi/subcommands/utils/project.py +++ b/src/kiwi/subcommands/utils/project.py @@ -1,6 +1,8 @@ import logging import os +from .executable import Executable + from ..._constants import CONF_DIRECTORY_NAME from ...config import LoadedConfig @@ -63,6 +65,41 @@ class Project: def has_configs(self): return os.path.isdir(self.conf_dir_name()) + def __update_kwargs(self, kwargs): + if not self.is_enabled(): + # cannot compose in a disabled project + logging.warning(f"Project '{self.get_name()}' is not enabled!") + return False + + config = LoadedConfig.get() + + # execute command in project directory + kwargs['cwd'] = self.dir_name() + + # ensure there is an environment + if 'env' not in kwargs: + kwargs['env'] = {} + + # create environment variables for docker commands + kwargs['env'].update({ + 'COMPOSE_PROJECT_NAME': self.get_name(), + 'KIWI_HUB_NAME': config['network:name'], + 'CONFDIR': os.path.join(config['runtime:storage'], CONF_DIRECTORY_NAME), + 'TARGETDIR': self.target_dir_name() + }) + + logging.debug(f"kwargs updated: {kwargs}") + + return True + + def compose_run(self, compose_args, **kwargs): + if self.__update_kwargs(kwargs): + Executable('docker-compose').run(compose_args, **kwargs) + + def compose_run_less(self, compose_args, **kwargs): + if self.__update_kwargs(kwargs): + Executable('docker-compose').run_less(compose_args, **kwargs) + def enable(self): if self.is_disabled(): logging.info(f"Enabling project '{self.get_name()}'") diff --git a/src/kiwi/subcommands/utils/rootkit.py b/src/kiwi/subcommands/utils/rootkit.py index 7f890eb..c286030 100644 --- a/src/kiwi/subcommands/utils/rootkit.py +++ b/src/kiwi/subcommands/utils/rootkit.py @@ -4,7 +4,7 @@ import os import subprocess # local -from .dockercommand import DockerCommand +from .executable import Executable # parent from ..._constants import IMAGES_DIRECTORY_NAME, LOCAL_IMAGES_NAME, DEFAULT_IMAGE_NAME @@ -38,7 +38,7 @@ class Rootkit: self.__image_tag = image_tag def __exists(self): - ps = DockerCommand('docker').run(None, [ + ps = Executable('docker').run([ 'images', '--filter', f"reference={_image_name(self.__image_tag)}", '--format', '{{.Repository}}:{{.Tag}}' @@ -52,13 +52,13 @@ class Rootkit: else: if self.__image_tag is None: logging.info(f"Pulling image {_image_name(self.__image_tag)}") - DockerCommand('docker').run(None, [ + Executable('docker').run([ 'pull', _image_name(self.__image_tag) ], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) else: logging.info(f"Building image {_image_name(self.__image_tag)}") - DockerCommand('docker').run(None, [ + Executable('docker').run([ 'build', '-t', _image_name(self.__image_tag), '-f', f"{IMAGES_DIRECTORY_NAME}/{self.__image_tag}.Dockerfile", @@ -67,7 +67,7 @@ class Rootkit: def run(self, process_args, **kwargs): self.__build_image() - DockerCommand('docker').run(None, [ + Executable('docker').run([ 'run', '--rm', '-v', '/:/mnt', '-u', 'root',