Rework: FlexCommand defaults into lower hierarchy (override protected _run variants by default)
This commit is contained in:
parent
0060bf8878
commit
93d0b56eb7
24 changed files with 414 additions and 348 deletions
|
@ -46,7 +46,8 @@ class Parser:
|
||||||
def get_args(self):
|
def get_args(self):
|
||||||
if self.__args is None:
|
if self.__args is None:
|
||||||
# parse args if needed
|
# parse args if needed
|
||||||
self.__args = self.__parser.parse_args()
|
self.__args, unknowns = self.__parser.parse_known_args()
|
||||||
|
self.__args.unknowns = unknowns
|
||||||
|
|
||||||
return self.__args
|
return self.__args
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,6 @@ import logging
|
||||||
|
|
||||||
# local
|
# local
|
||||||
from . import subcommands
|
from . import subcommands
|
||||||
from .config import LoadedConfig
|
|
||||||
from .parser import Parser
|
from .parser import Parser
|
||||||
|
|
||||||
|
|
||||||
|
@ -13,7 +12,6 @@ class Runner:
|
||||||
class __Runner:
|
class __Runner:
|
||||||
"""Singleton type"""
|
"""Singleton type"""
|
||||||
|
|
||||||
__parser = None
|
|
||||||
__commands = []
|
__commands = []
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
@ -22,10 +20,11 @@ class Runner:
|
||||||
cmd = getattr(subcommands, className)
|
cmd = getattr(subcommands, className)
|
||||||
self.__commands.append(cmd())
|
self.__commands.append(cmd())
|
||||||
|
|
||||||
def run(self, command=None):
|
def run(self, command=None, args=None):
|
||||||
"""run the desired subcommand"""
|
"""run the desired subcommand"""
|
||||||
|
|
||||||
args = Parser().get_args()
|
if args is None:
|
||||||
|
args = Parser().get_args()
|
||||||
|
|
||||||
if command is None:
|
if command is None:
|
||||||
command = args.command
|
command = args.command
|
||||||
|
@ -36,7 +35,7 @@ class Runner:
|
||||||
logging.debug(f"Running '{cmd}' with args: {args}")
|
logging.debug(f"Running '{cmd}' with args: {args}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
result = cmd.run(self, LoadedConfig.get(), args)
|
result = cmd.run(self, args)
|
||||||
|
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print()
|
print()
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
# system
|
# system
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
# local
|
# local
|
||||||
from .utils.project import Projects
|
from .utils.project import Projects
|
||||||
|
@ -16,7 +17,9 @@ class SubCommand:
|
||||||
# command parser
|
# command parser
|
||||||
_sub_parser = None
|
_sub_parser = None
|
||||||
|
|
||||||
def __init__(self, name, add_parser=True, **kwargs):
|
_action = None
|
||||||
|
|
||||||
|
def __init__(self, name, action='', add_parser=True, **kwargs):
|
||||||
self.__name = name
|
self.__name = name
|
||||||
if add_parser:
|
if add_parser:
|
||||||
self._sub_parser = Parser().get_subparsers().add_parser(
|
self._sub_parser = Parser().get_subparsers().add_parser(
|
||||||
|
@ -24,26 +27,38 @@ class SubCommand:
|
||||||
**kwargs
|
**kwargs
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if not action:
|
||||||
|
# default action string
|
||||||
|
self._action = f"Running '{str(self)}' for"
|
||||||
|
else:
|
||||||
|
self._action = action
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.__name
|
return self.__name
|
||||||
|
|
||||||
def run(self, runner, config, args):
|
def _run_instance(self, runner, args):
|
||||||
"""actually run command with this dir's config and parsed CLI args"""
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def run(self, runner, args):
|
||||||
|
"""actually run command with parsed CLI args"""
|
||||||
|
|
||||||
|
# run for entire instance
|
||||||
|
logging.info(f"{self._action} kiwi-config instance at '{os.getcwd()}'")
|
||||||
|
return self._run_instance(runner, args)
|
||||||
|
|
||||||
|
|
||||||
class ProjectCommand(SubCommand):
|
class ProjectCommand(SubCommand):
|
||||||
"""this command concerns a project in current instance"""
|
"""this command concerns a project in current instance"""
|
||||||
|
|
||||||
def __init__(self, name, num_projects, add_parser=True, **kwargs):
|
def __init__(self, name, num_projects, action='', add_parser=True, **kwargs):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
name, add_parser=add_parser,
|
name, action=action, add_parser=add_parser,
|
||||||
**kwargs
|
**kwargs
|
||||||
)
|
)
|
||||||
|
|
||||||
projects = "a project"
|
if num_projects == 1:
|
||||||
|
projects = "a project"
|
||||||
if not num_projects == 1:
|
else:
|
||||||
projects = "project(s)"
|
projects = "project(s)"
|
||||||
|
|
||||||
self._sub_parser.add_argument(
|
self._sub_parser.add_argument(
|
||||||
|
@ -51,19 +66,41 @@ class ProjectCommand(SubCommand):
|
||||||
help=f"select {projects} in this instance"
|
help=f"select {projects} in this instance"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _run_instance(self, runner, args):
|
||||||
|
# default: run for all enabled projects
|
||||||
|
return self._run_projects(runner, args, Projects.from_dir().filter_enabled())
|
||||||
|
|
||||||
|
def _run_projects(self, runner, args, projects):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def run(self, runner, args):
|
||||||
|
projects = Projects.from_args(args)
|
||||||
|
|
||||||
|
if not projects.empty():
|
||||||
|
# project(s) given
|
||||||
|
logging.info(f"{self._action} projects {projects}")
|
||||||
|
return self._run_projects(runner, args, projects)
|
||||||
|
|
||||||
|
else:
|
||||||
|
return super().run(runner, args)
|
||||||
|
|
||||||
|
|
||||||
class ServiceCommand(ProjectCommand):
|
class ServiceCommand(ProjectCommand):
|
||||||
"""this command concerns service(s) in a project"""
|
"""this command concerns service(s) in a project"""
|
||||||
|
|
||||||
def __init__(self, name, num_projects, num_services, add_parser=True, **kwargs):
|
def __init__(self, name, num_projects, num_services, action='', add_parser=True, **kwargs):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
name, num_projects=num_projects, add_parser=add_parser,
|
name, num_projects=num_projects, action=action, add_parser=add_parser,
|
||||||
**kwargs
|
**kwargs
|
||||||
)
|
)
|
||||||
|
|
||||||
services = "a service"
|
if (isinstance(num_projects, str) and num_projects == '*') \
|
||||||
|
or (isinstance(num_projects, int) and num_projects > 1):
|
||||||
|
logging.warning(f"Invalid choice for project count: {num_projects}")
|
||||||
|
|
||||||
if not num_services == 1:
|
if num_services == 1:
|
||||||
|
services = "a service"
|
||||||
|
else:
|
||||||
services = "service(s)"
|
services = "service(s)"
|
||||||
|
|
||||||
self._sub_parser.add_argument(
|
self._sub_parser.add_argument(
|
||||||
|
@ -71,52 +108,25 @@ class ServiceCommand(ProjectCommand):
|
||||||
help=f"select {services} in a project"
|
help=f"select {services} in a project"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _run_projects(self, runner, args, projects):
|
||||||
class FlexCommand(ServiceCommand):
|
|
||||||
"""this command concerns the entire instance, a whole project or just service(s) in a project"""
|
|
||||||
|
|
||||||
__action = None
|
|
||||||
|
|
||||||
def __init__(self, name, action='', add_parser=True, **kwargs):
|
|
||||||
super().__init__(
|
|
||||||
name, num_projects='?', num_services='*', add_parser=add_parser,
|
|
||||||
**kwargs
|
|
||||||
)
|
|
||||||
|
|
||||||
if not action:
|
|
||||||
# default action string
|
|
||||||
self.__action = f"Running '{str(self)}' for"
|
|
||||||
else:
|
|
||||||
self.__action = action
|
|
||||||
|
|
||||||
def _run_instance(self, runner, config, args):
|
|
||||||
result = True
|
result = True
|
||||||
|
|
||||||
for project in Projects.from_args(args):
|
# default: run without services for all given
|
||||||
args.projects = project.get_name()
|
for project in projects:
|
||||||
result &= runner.run(str(self))
|
result &= self._run_services(runner, args, project, [])
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def _run_project(self, runner, config, args):
|
def _run_services(self, runner, args, project, services):
|
||||||
return self._run_services(runner, config, args, [])
|
|
||||||
|
|
||||||
def _run_services(self, runner, config, args, services):
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def run(self, runner, config, args):
|
def run(self, runner, args):
|
||||||
projects = Projects.from_args(args)
|
if 'services' in args and args.services:
|
||||||
if not projects:
|
project = Projects.from_args(args)[0]
|
||||||
# no project given, run for entire instance
|
|
||||||
logging.info(f"{self.__action} this instance")
|
|
||||||
return self._run_instance(runner, config, args)
|
|
||||||
|
|
||||||
project = projects[0]
|
# run for service(s) inside project
|
||||||
if args is None or 'services' not in args or not args.services:
|
logging.info(f"{self._action} project '{project.get_name()}', services {args.services}")
|
||||||
# no services given, run for whole project
|
return self._run_services(runner, args, project, args.services)
|
||||||
logging.info(f"{self.__action} project '{project.get_name()}'")
|
|
||||||
return self._run_project(runner, config, args)
|
|
||||||
|
|
||||||
# run for service(s) inside project
|
else:
|
||||||
logging.info(f"{self.__action} services {args.services} in project '{project.get_name()}'")
|
return super().run(runner, args)
|
||||||
return self._run_services(runner, config, args, args.services)
|
|
||||||
|
|
|
@ -1,19 +1,20 @@
|
||||||
# local
|
# local
|
||||||
from ._subcommand import FlexCommand
|
from ._subcommand import ServiceCommand
|
||||||
from .utils.dockercommand import DockerCommand
|
from .utils.dockercommand import DockerCommand
|
||||||
|
|
||||||
|
|
||||||
class BuildCommand(FlexCommand):
|
class BuildCommand(ServiceCommand):
|
||||||
"""kiwi build"""
|
"""kiwi build"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
'build', "Building images for",
|
'build', num_projects='?', num_services='*',
|
||||||
|
action="Building images for",
|
||||||
description="Build images for the whole instance, a project or service(s) inside a project"
|
description="Build images for the whole instance, a project or service(s) inside a project"
|
||||||
)
|
)
|
||||||
|
|
||||||
def _run_services(self, runner, config, args, services):
|
def _run_services(self, runner, args, project, services):
|
||||||
DockerCommand('docker-compose').run(
|
DockerCommand('docker-compose').run(project, [
|
||||||
config, args, ['build', '--pull', *services]
|
'build', '--pull', *services
|
||||||
)
|
])
|
||||||
return True
|
return True
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
# system
|
||||||
|
import logging
|
||||||
|
|
||||||
# local
|
# local
|
||||||
from ._subcommand import ProjectCommand
|
from ._subcommand import ProjectCommand
|
||||||
from .utils.dockercommand import DockerCommand
|
from .utils.dockercommand import DockerCommand
|
||||||
|
@ -9,19 +12,32 @@ class CmdCommand(ProjectCommand):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
'cmd', num_projects=1,
|
'cmd', num_projects=1,
|
||||||
|
action="Running docker-compose in",
|
||||||
description="Run raw docker-compose command in a project"
|
description="Run raw docker-compose command in a project"
|
||||||
)
|
)
|
||||||
|
|
||||||
# command string after docker-compose
|
# command for docker-compose
|
||||||
self._sub_parser.add_argument(
|
self._sub_parser.add_argument(
|
||||||
'compose_cmd', metavar='cmd', type=str,
|
'compose_cmd', metavar='cmd', type=str,
|
||||||
help="runs `docker-compose <cmd>`"
|
help="command for 'docker-compose'"
|
||||||
)
|
)
|
||||||
|
|
||||||
def run(self, runner, config, args):
|
# arguments for docker-compose command
|
||||||
import shlex
|
self._sub_parser.add_argument(
|
||||||
|
'compose_args', metavar='arg', nargs='*', type=str,
|
||||||
|
help="arguments for 'docker-compose' commands"
|
||||||
|
)
|
||||||
|
|
||||||
|
def _run_projects(self, runner, args, projects):
|
||||||
|
if args.unknowns:
|
||||||
|
args.compose_args = [*args.compose_args, *args.unknowns]
|
||||||
|
args.unknowns = []
|
||||||
|
|
||||||
|
logging.debug(f"Updated args: {args}")
|
||||||
|
|
||||||
# run with split compose_cmd argument
|
# run with split compose_cmd argument
|
||||||
DockerCommand('docker-compose').run(config, args, shlex.split(args.compose_cmd))
|
DockerCommand('docker-compose').run(projects[0], [
|
||||||
|
args.compose_cmd, *args.compose_args
|
||||||
|
])
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
|
@ -10,6 +10,7 @@ from .utils.rootkit import Rootkit, prefix_path_mnt
|
||||||
|
|
||||||
# parent
|
# parent
|
||||||
from .._constants import CONF_DIRECTORY_NAME
|
from .._constants import CONF_DIRECTORY_NAME
|
||||||
|
from ..config import LoadedConfig
|
||||||
|
|
||||||
|
|
||||||
class ConfCopyCommand(SubCommand):
|
class ConfCopyCommand(SubCommand):
|
||||||
|
@ -21,22 +22,20 @@ class ConfCopyCommand(SubCommand):
|
||||||
description="Synchronize all config files to target directory"
|
description="Synchronize all config files to target directory"
|
||||||
)
|
)
|
||||||
|
|
||||||
def run(self, runner, config, args):
|
def _run_instance(self, runner, args):
|
||||||
conf_dirs = [
|
conf_dirs = [
|
||||||
project.conf_dir_name()
|
project.conf_dir_name()
|
||||||
for project in Projects.all()
|
for project in Projects.from_dir().filter_enabled()
|
||||||
if project.is_enabled()
|
|
||||||
]
|
]
|
||||||
|
|
||||||
if conf_dirs:
|
if conf_dirs:
|
||||||
# add target directory
|
# add target directory
|
||||||
conf_dirs.append(config['runtime:storage'])
|
conf_dirs.append(LoadedConfig.get()['runtime:storage'])
|
||||||
logging.info(f"Sync directories: {conf_dirs}")
|
logging.info(f"Sync directories: {conf_dirs}")
|
||||||
|
|
||||||
Rootkit('rsync').run(
|
Rootkit('rsync').run([
|
||||||
config, args, ['rsync', '-r', *prefix_path_mnt(conf_dirs)],
|
'rsync', '-r', *prefix_path_mnt(conf_dirs)
|
||||||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
|
], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||||
)
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -50,14 +49,13 @@ class ConfPurgeCommand(SubCommand):
|
||||||
description="Remove all config files in target directory"
|
description="Remove all config files in target directory"
|
||||||
)
|
)
|
||||||
|
|
||||||
def run(self, runner, config, args):
|
def _run_instance(self, runner, args):
|
||||||
conf_target = f"{config['runtime:storage']}/{CONF_DIRECTORY_NAME}"
|
conf_target = f"{LoadedConfig.get()['runtime:storage']}/{CONF_DIRECTORY_NAME}"
|
||||||
logging.info(f"Purging directories: {conf_target}")
|
logging.info(f"Purging directories: {conf_target}")
|
||||||
|
|
||||||
Rootkit().run(
|
Rootkit().run([
|
||||||
config, args, ['rm', '-rf', prefix_path_mnt(conf_target)],
|
'rm', '-rf', prefix_path_mnt(conf_target)
|
||||||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
|
], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||||
)
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -71,12 +69,12 @@ class ConfCleanCommand(SubCommand):
|
||||||
description="Cleanly sync all configs to target folder, relaunch affected projects"
|
description="Cleanly sync all configs to target folder, relaunch affected projects"
|
||||||
)
|
)
|
||||||
|
|
||||||
def run(self, runner, config, args):
|
def _run_instance(self, runner, args):
|
||||||
result = True
|
result = True
|
||||||
|
|
||||||
affected_projects = [
|
affected_projects = [
|
||||||
project.conf_dir_name()
|
project.conf_dir_name()
|
||||||
for project in Projects.all()
|
for project in Projects.from_dir()
|
||||||
if project.has_configs()
|
if project.has_configs()
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -9,11 +9,12 @@ class DisableCommand(ProjectCommand):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
'disable', num_projects='+',
|
'disable', num_projects='+',
|
||||||
|
action="Disabling",
|
||||||
description="Disable whole project(s) in this instance"
|
description="Disable whole project(s) in this instance"
|
||||||
)
|
)
|
||||||
|
|
||||||
def run(self, runner, config, args):
|
def _run_projects(self, runner, args, projects):
|
||||||
return all([
|
return all([
|
||||||
project.disable()
|
project.disable()
|
||||||
for project in Projects.from_args(args)
|
for project in projects
|
||||||
])
|
])
|
||||||
|
|
|
@ -1,40 +1,42 @@
|
||||||
# local
|
# local
|
||||||
from ._subcommand import FlexCommand
|
from ._subcommand import ServiceCommand
|
||||||
from .utils.dockercommand import DockerCommand
|
from .utils.dockercommand import DockerCommand
|
||||||
from .utils.misc import are_you_sure
|
from .utils.misc import are_you_sure
|
||||||
|
|
||||||
|
|
||||||
class DownCommand(FlexCommand):
|
class DownCommand(ServiceCommand):
|
||||||
"""kiwi down"""
|
"""kiwi down"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
'down', "Bringing down",
|
'down', num_projects='?', num_services='*',
|
||||||
|
action="Bringing down",
|
||||||
description="Bring down the whole instance, a project or service(s) inside a project"
|
description="Bring down the whole instance, a project or service(s) inside a project"
|
||||||
)
|
)
|
||||||
|
|
||||||
def _run_instance(self, runner, config, args):
|
def _run_instance(self, runner, args):
|
||||||
if are_you_sure([
|
if are_you_sure([
|
||||||
"This will bring down the entire instance.",
|
"This will bring down the entire instance.",
|
||||||
"",
|
"",
|
||||||
"This may not be what you intended, because:",
|
"This may not be what you intended, because:",
|
||||||
" - Bringing down the instance stops ALL services in here",
|
" - Bringing down the instance stops ALL services in here",
|
||||||
]):
|
]):
|
||||||
return super()._run_instance(runner, config, args)
|
return super()._run_instance(runner, args)
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _run_project(self, runner, config, args):
|
def _run_projects(self, runner, args, projects):
|
||||||
DockerCommand('docker-compose').run(
|
for project in projects:
|
||||||
config, args, ['down']
|
DockerCommand('docker-compose').run(project, [
|
||||||
)
|
'down'
|
||||||
|
])
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _run_services(self, runner, config, args, services):
|
def _run_services(self, runner, args, project, services):
|
||||||
DockerCommand('docker-compose').run(
|
DockerCommand('docker-compose').run(project, [
|
||||||
config, args, ['stop', *services]
|
'stop', *services
|
||||||
)
|
])
|
||||||
DockerCommand('docker-compose').run(
|
DockerCommand('docker-compose').run(project, [
|
||||||
config, args, ['rm', '-f', *services]
|
'rm', '-f', *services
|
||||||
)
|
])
|
||||||
return True
|
return True
|
||||||
|
|
|
@ -9,11 +9,12 @@ class EnableCommand(ProjectCommand):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
'enable', num_projects='+',
|
'enable', num_projects='+',
|
||||||
|
action="Enabling",
|
||||||
description="Enable whole project(s) in this instance"
|
description="Enable whole project(s) in this instance"
|
||||||
)
|
)
|
||||||
|
|
||||||
def run(self, runner, config, args):
|
def _run_projects(self, runner, args, projects):
|
||||||
return all([
|
return all([
|
||||||
project.enable()
|
project.enable()
|
||||||
for project in Projects.from_args(args)
|
for project in projects
|
||||||
])
|
])
|
||||||
|
|
|
@ -7,6 +7,7 @@ from ._subcommand import SubCommand
|
||||||
|
|
||||||
# parent (display purposes only)
|
# parent (display purposes only)
|
||||||
from .._constants import KIWI_CONF_NAME
|
from .._constants import KIWI_CONF_NAME
|
||||||
|
from ..config import DefaultConfig, LoadedConfig
|
||||||
|
|
||||||
|
|
||||||
def user_input(config, key, prompt):
|
def user_input(config, key, prompt):
|
||||||
|
@ -40,12 +41,12 @@ class InitCommand(SubCommand):
|
||||||
help=f"use default values even if {KIWI_CONF_NAME} is present"
|
help=f"use default values even if {KIWI_CONF_NAME} is present"
|
||||||
)
|
)
|
||||||
|
|
||||||
def run(self, runner, config, args):
|
def _run_instance(self, runner, args):
|
||||||
logging.info(f"Initializing '{KIWI_CONF_NAME}' in '{os.getcwd()}'")
|
logging.info(f"Initializing '{KIWI_CONF_NAME}' in '{os.getcwd()}'")
|
||||||
|
config = LoadedConfig.get()
|
||||||
|
|
||||||
# check force switch
|
# check force switch
|
||||||
if args.force and os.path.isfile(KIWI_CONF_NAME):
|
if args.force and os.path.isfile(KIWI_CONF_NAME):
|
||||||
from ..config import DefaultConfig
|
|
||||||
|
|
||||||
logging.warning(f"Overwriting existing '{KIWI_CONF_NAME}'!")
|
logging.warning(f"Overwriting existing '{KIWI_CONF_NAME}'!")
|
||||||
config = DefaultConfig.get()
|
config = DefaultConfig.get()
|
||||||
|
|
|
@ -1,84 +1,75 @@
|
||||||
# system
|
# system
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import subprocess
|
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
# local
|
# local
|
||||||
from ._subcommand import FlexCommand
|
from ._subcommand import ServiceCommand
|
||||||
from .utils.dockercommand import DockerCommand
|
from .utils.project import Project, Projects
|
||||||
from .utils.project import Projects
|
|
||||||
|
|
||||||
|
|
||||||
def _print_list(strings):
|
def _print_list(strings):
|
||||||
if isinstance(strings, list):
|
if isinstance(strings, str):
|
||||||
|
print(f" - {strings}")
|
||||||
|
|
||||||
|
elif isinstance(strings, Project):
|
||||||
|
_print_list(strings.get_name())
|
||||||
|
|
||||||
|
elif isinstance(strings, list):
|
||||||
for string in strings:
|
for string in strings:
|
||||||
print(f" - {string}")
|
_print_list(string)
|
||||||
|
|
||||||
elif isinstance(strings, str):
|
else:
|
||||||
_print_list(strings.strip().split('\n'))
|
_print_list(list(strings))
|
||||||
|
|
||||||
elif isinstance(strings, bytes):
|
|
||||||
_print_list(str(strings, 'utf-8'))
|
|
||||||
|
|
||||||
|
|
||||||
class ListCommand(FlexCommand):
|
class ListCommand(ServiceCommand):
|
||||||
"""kiwi list"""
|
"""kiwi list"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
'list', "Listing",
|
'list', num_projects='?', num_services='*',
|
||||||
|
action="Listing",
|
||||||
description="List projects in this instance, services inside a project or service(s) inside a project"
|
description="List projects in this instance, services inside a project or service(s) inside a project"
|
||||||
)
|
)
|
||||||
|
|
||||||
def _run_instance(self, runner, config, args):
|
def _run_instance(self, runner, args):
|
||||||
print(f"kiwi-config instance at '{os.getcwd()}'")
|
print(f"kiwi-config instance at '{os.getcwd()}'")
|
||||||
print("#########")
|
print("#########")
|
||||||
projects = Projects.all()
|
projects = Projects.from_dir()
|
||||||
|
|
||||||
enableds = [
|
enabled_projects = projects.filter_enabled()
|
||||||
project.get_name()
|
if not enabled_projects.empty():
|
||||||
for project in projects
|
|
||||||
if project.is_enabled()
|
|
||||||
]
|
|
||||||
|
|
||||||
if enableds:
|
|
||||||
print(f"Enabled projects:")
|
print(f"Enabled projects:")
|
||||||
_print_list(enableds)
|
_print_list(enabled_projects)
|
||||||
|
|
||||||
disableds = [
|
disabled_projects = projects.filter_disabled()
|
||||||
project.get_name()
|
if not disabled_projects.empty():
|
||||||
for project in projects
|
|
||||||
if project.is_disabled()
|
|
||||||
]
|
|
||||||
|
|
||||||
if disableds:
|
|
||||||
print(f"Disabled projects:")
|
print(f"Disabled projects:")
|
||||||
_print_list(disableds)
|
_print_list(disabled_projects)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _run_project(self, runner, config, args):
|
def _run_projects(self, runner, args, projects):
|
||||||
project = Projects.from_args(args)[0]
|
project = projects[0]
|
||||||
|
|
||||||
if not project.exists():
|
if not project.exists():
|
||||||
logging.error(f"Project '{project.get_name()}' not found")
|
logging.warning(f"Project '{project.get_name()}' not found")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
print(f"Services in project '{project.get_name()}':")
|
print(f"Services in project '{project.get_name()}':")
|
||||||
print("#########")
|
print("#########")
|
||||||
|
|
||||||
ps = DockerCommand('docker-compose').run(
|
with open(project.compose_file_name(), 'r') as stream:
|
||||||
config, args, ['config', '--services'],
|
try:
|
||||||
stdout=subprocess.PIPE
|
docker_compose_yml = yaml.safe_load(stream)
|
||||||
)
|
_print_list(docker_compose_yml['services'].keys())
|
||||||
|
|
||||||
|
except yaml.YAMLError as exc:
|
||||||
|
logging.error(exc)
|
||||||
|
|
||||||
_print_list(ps.stdout)
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _run_services(self, runner, config, args, services):
|
def _run_services(self, runner, args, project, services):
|
||||||
project = Projects.from_args(args)[0]
|
|
||||||
|
|
||||||
if not project.exists():
|
if not project.exists():
|
||||||
logging.error(f"Project '{project.get_name()}' not found")
|
logging.error(f"Project '{project.get_name()}' not found")
|
||||||
return False
|
return False
|
||||||
|
@ -91,10 +82,13 @@ class ListCommand(FlexCommand):
|
||||||
docker_compose_yml = yaml.safe_load(stream)
|
docker_compose_yml = yaml.safe_load(stream)
|
||||||
|
|
||||||
for service_name in services:
|
for service_name in services:
|
||||||
print(yaml.dump(
|
try:
|
||||||
{service_name: docker_compose_yml['services'][service_name]},
|
print(yaml.dump(
|
||||||
default_flow_style=False, sort_keys=False
|
{service_name: docker_compose_yml['services'][service_name]},
|
||||||
).strip())
|
default_flow_style=False, sort_keys=False
|
||||||
|
).strip())
|
||||||
|
except KeyError:
|
||||||
|
logging.error(f"Service '{service_name}' not found")
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ class LogsCommand(ServiceCommand):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
'logs', num_projects=1, num_services='*',
|
'logs', num_projects=1, num_services='*',
|
||||||
|
action="Showing logs of",
|
||||||
description="Show logs of a project or service(s) of a project"
|
description="Show logs of a project or service(s) of a project"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -18,7 +19,7 @@ class LogsCommand(ServiceCommand):
|
||||||
help="output appended data as log grows"
|
help="output appended data as log grows"
|
||||||
)
|
)
|
||||||
|
|
||||||
def run(self, runner, config, args):
|
def _run_services(self, runner, args, project, services):
|
||||||
# include timestamps
|
# include timestamps
|
||||||
compose_cmd = ['logs', '-t']
|
compose_cmd = ['logs', '-t']
|
||||||
|
|
||||||
|
@ -27,13 +28,13 @@ class LogsCommand(ServiceCommand):
|
||||||
compose_cmd = [*compose_cmd, '-f', '--tail=10']
|
compose_cmd = [*compose_cmd, '-f', '--tail=10']
|
||||||
|
|
||||||
# append if one or more services are given
|
# append if one or more services are given
|
||||||
if args.services:
|
if services:
|
||||||
compose_cmd = [*compose_cmd, *args.services]
|
compose_cmd = [*compose_cmd, *args.services]
|
||||||
|
|
||||||
# use 'less' viewer if output will be static
|
# use 'less' viewer if output will be static
|
||||||
if args.follow:
|
if args.follow:
|
||||||
DockerCommand('docker-compose').run(config, args, compose_cmd)
|
DockerCommand('docker-compose').run(project, compose_cmd)
|
||||||
else:
|
else:
|
||||||
DockerCommand('docker-compose').run_less(config, args, compose_cmd)
|
DockerCommand('docker-compose').run_less(project, compose_cmd)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
|
@ -7,16 +7,18 @@ from ._subcommand import SubCommand
|
||||||
from .utils.dockercommand import DockerCommand
|
from .utils.dockercommand import DockerCommand
|
||||||
from .utils.misc import are_you_sure
|
from .utils.misc import are_you_sure
|
||||||
|
|
||||||
|
# parent
|
||||||
|
from ..config import LoadedConfig
|
||||||
|
|
||||||
def _find_net(config, args):
|
|
||||||
ps = DockerCommand('docker').run(
|
def _find_net(net_name):
|
||||||
config, args, ['network', 'ls', '--filter', f"name={config['network:name']}", '--format', '{{.Name}}'],
|
ps = DockerCommand('docker').run(None, [
|
||||||
stdout=subprocess.PIPE
|
'network', 'ls', '--filter', f"name={net_name}", '--format', '{{.Name}}'
|
||||||
)
|
], stdout=subprocess.PIPE)
|
||||||
|
|
||||||
net_found = str(ps.stdout, 'utf-8').strip()
|
net_found = str(ps.stdout, 'utf-8').strip()
|
||||||
|
|
||||||
return net_found == config['network:name']
|
return net_found == net_name
|
||||||
|
|
||||||
|
|
||||||
class NetUpCommand(SubCommand):
|
class NetUpCommand(SubCommand):
|
||||||
|
@ -25,30 +27,31 @@ class NetUpCommand(SubCommand):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
'net-up',
|
'net-up',
|
||||||
|
action="Creating the local network hub",
|
||||||
description="Create the local network hub for this instance"
|
description="Create the local network hub for this instance"
|
||||||
)
|
)
|
||||||
|
|
||||||
def run(self, runner, config, args):
|
def _run_instance(self, runner, args):
|
||||||
if _find_net(config, args):
|
config = LoadedConfig.get()
|
||||||
logging.info(f"Network '{config['network:name']}' already exists")
|
net_name = config['network:name']
|
||||||
|
net_cidr = config['network:cidr']
|
||||||
|
|
||||||
|
if _find_net(net_name):
|
||||||
|
logging.info(f"Network '{net_name}' already exists")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
try:
|
try:
|
||||||
DockerCommand('docker').run(
|
DockerCommand('docker').run(None, [
|
||||||
config, args,
|
'network', 'create',
|
||||||
[
|
'--driver', 'bridge',
|
||||||
'network', 'create',
|
'--internal',
|
||||||
'--driver', 'bridge',
|
'--subnet', net_cidr,
|
||||||
'--internal',
|
net_name
|
||||||
'--subnet', config['network:cidr'],
|
], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||||
config['network:name']
|
logging.info(f"Network '{net_name}' created")
|
||||||
],
|
|
||||||
check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
|
|
||||||
)
|
|
||||||
logging.info(f"Network '{config['network:name']}' created")
|
|
||||||
|
|
||||||
except subprocess.CalledProcessError:
|
except subprocess.CalledProcessError:
|
||||||
logging.error(f"Error creating network '{config['network:name']}'")
|
logging.error(f"Error creating network '{net_name}'")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
@ -63,25 +66,26 @@ class NetDownCommand(SubCommand):
|
||||||
description="Remove the local network hub for this instance"
|
description="Remove the local network hub for this instance"
|
||||||
)
|
)
|
||||||
|
|
||||||
def run(self, runner, config, args):
|
def _run_instance(self, runner, args):
|
||||||
if not _find_net(config, args):
|
net_name = LoadedConfig.get()['network:name']
|
||||||
logging.info(f"Network '{config['network:name']}' does not exist")
|
|
||||||
|
if not _find_net(net_name):
|
||||||
|
logging.info(f"Network '{net_name}' does not exist")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if are_you_sure("This will bring down this instance's hub network!"):
|
if are_you_sure("This will bring down this instance's hub network!"):
|
||||||
if runner.run('down'):
|
if runner.run('down'):
|
||||||
DockerCommand('docker').run(
|
DockerCommand('docker').run(None, [
|
||||||
config, args,
|
'network', 'rm', net_name
|
||||||
['network', 'rm', config['network:name']],
|
], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||||
check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
|
|
||||||
)
|
logging.info(f"Network '{net_name}' removed")
|
||||||
logging.info(f"Network '{config['network:name']}' removed")
|
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
except subprocess.CalledProcessError:
|
except subprocess.CalledProcessError:
|
||||||
logging.error(f"Error removing network '{config['network:name']}'")
|
logging.error(f"Error removing network '{net_name}'")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
|
@ -17,12 +17,12 @@ class NewCommand(ProjectCommand):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
'new', num_projects='+',
|
'new', num_projects='+',
|
||||||
|
action="Creating",
|
||||||
description="Create new empty project(s) in this instance"
|
description="Create new empty project(s) in this instance"
|
||||||
)
|
)
|
||||||
|
|
||||||
def run(self, runner, config, args):
|
def _run_projects(self, runner, args, projects):
|
||||||
result = True
|
result = True
|
||||||
projects = Projects.from_args(args)
|
|
||||||
|
|
||||||
for project in projects:
|
for project in projects:
|
||||||
if project.exists():
|
if project.exists():
|
||||||
|
@ -31,7 +31,7 @@ class NewCommand(ProjectCommand):
|
||||||
|
|
||||||
else:
|
else:
|
||||||
logging.info(f"Creating project '{project.get_name()}'")
|
logging.info(f"Creating project '{project.get_name()}'")
|
||||||
os.mkdir(project.enabled_dir_name())
|
os.mkdir(project.disabled_dir_name())
|
||||||
shutil.copy(DEFAULT_DOCKER_COMPOSE_NAME, project.compose_file_name())
|
shutil.copy(DEFAULT_DOCKER_COMPOSE_NAME, project.compose_file_name())
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
|
@ -1,19 +1,20 @@
|
||||||
# local
|
# local
|
||||||
from ._subcommand import FlexCommand
|
from ._subcommand import ServiceCommand
|
||||||
from .utils.dockercommand import DockerCommand
|
from .utils.dockercommand import DockerCommand
|
||||||
|
|
||||||
|
|
||||||
class PullCommand(FlexCommand):
|
class PullCommand(ServiceCommand):
|
||||||
"""kiwi pull"""
|
"""kiwi pull"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
'pull', "Pulling images for",
|
'pull', num_projects='?', num_services='*',
|
||||||
|
action="Pulling images for",
|
||||||
description="Pull images for the whole instance, a project or service(s) inside a project"
|
description="Pull images for the whole instance, a project or service(s) inside a project"
|
||||||
)
|
)
|
||||||
|
|
||||||
def _run_services(self, runner, config, args, services):
|
def _run_services(self, runner, args, project, services):
|
||||||
DockerCommand('docker-compose').run(
|
DockerCommand('docker-compose').run(project, [
|
||||||
config, args, ['pull', '--ignore-pull-failures', *services]
|
'pull', '--ignore-pull-failures', *services
|
||||||
)
|
])
|
||||||
return True
|
return True
|
||||||
|
|
|
@ -1,19 +1,20 @@
|
||||||
# local
|
# local
|
||||||
from ._subcommand import FlexCommand
|
from ._subcommand import ServiceCommand
|
||||||
from .utils.dockercommand import DockerCommand
|
from .utils.dockercommand import DockerCommand
|
||||||
|
|
||||||
|
|
||||||
class PushCommand(FlexCommand):
|
class PushCommand(ServiceCommand):
|
||||||
"""kiwi push"""
|
"""kiwi push"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
'push', "Pushing images for",
|
'push', num_projects='?', num_services='*',
|
||||||
|
action="Pushing images for",
|
||||||
description="Push images for the whole instance, a project or service(s) inside a project"
|
description="Push images for the whole instance, a project or service(s) inside a project"
|
||||||
)
|
)
|
||||||
|
|
||||||
def _run_services(self, runner, config, args, services):
|
def _run_services(self, runner, args, project, services):
|
||||||
DockerCommand('docker-compose').run(
|
DockerCommand('docker-compose').run(project, [
|
||||||
config, args, ['push', *services]
|
'push', *services
|
||||||
)
|
])
|
||||||
return True
|
return True
|
||||||
|
|
|
@ -6,19 +6,21 @@ import subprocess
|
||||||
from ._subcommand import ServiceCommand
|
from ._subcommand import ServiceCommand
|
||||||
from .utils.dockercommand import DockerCommand
|
from .utils.dockercommand import DockerCommand
|
||||||
|
|
||||||
|
# parent
|
||||||
|
from ..config import LoadedConfig
|
||||||
|
|
||||||
def _service_has_executable(config, args, compose_cmd, exe_name):
|
|
||||||
|
def _service_has_executable(project, service, exe_name):
|
||||||
"""
|
"""
|
||||||
Test if container (as of compose_cmd array) has an executable exe_name in its PATH.
|
Test if service in project has an executable exe_name in its PATH.
|
||||||
Requires /bin/sh and which.
|
Requires /bin/sh and which.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# test if desired shell exists
|
# test if desired shell exists
|
||||||
DockerCommand('docker-compose').run(
|
DockerCommand('docker-compose').run(project, [
|
||||||
config, args, [*compose_cmd, '/bin/sh', '-c', f"which {exe_name}"],
|
'exec', service, '/bin/sh', '-c', f"which {exe_name}"
|
||||||
check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
|
], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||||
)
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
except subprocess.CalledProcessError as e:
|
except subprocess.CalledProcessError as e:
|
||||||
|
@ -26,13 +28,14 @@ def _service_has_executable(config, args, compose_cmd, exe_name):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def _find_shell(config, args, compose_cmd):
|
def _find_shell(args, project, service):
|
||||||
"""find first working shell (provided by config and args) in container (as of compose_cmd array)"""
|
"""find first working shell (provided by config and args) in service in project"""
|
||||||
|
|
||||||
# builtin shells: as a last resort, fallback to '/bin/sh' and 'sh'
|
# builtin shells: as a last resort, fallback to '/bin/sh' and 'sh'
|
||||||
shells = ['/bin/sh', 'sh']
|
shells = ['/bin/sh', 'sh']
|
||||||
|
|
||||||
# load favorite shells from config
|
# load favorite shells from config
|
||||||
|
config = LoadedConfig.get()
|
||||||
if config['runtime:shells']:
|
if config['runtime:shells']:
|
||||||
shells = [*config['runtime:shells'], *shells]
|
shells = [*config['runtime:shells'], *shells]
|
||||||
|
|
||||||
|
@ -44,7 +47,7 @@ def _find_shell(config, args, compose_cmd):
|
||||||
|
|
||||||
# actually try shells
|
# actually try shells
|
||||||
for i, shell in enumerate(shells):
|
for i, shell in enumerate(shells):
|
||||||
if _service_has_executable(config, args, compose_cmd, shell):
|
if _service_has_executable(project, service, shell):
|
||||||
# found working shell
|
# found working shell
|
||||||
logging.debug(f"Using shell '{shell}'")
|
logging.debug(f"Using shell '{shell}'")
|
||||||
return shell
|
return shell
|
||||||
|
@ -82,15 +85,15 @@ class ShCommand(ServiceCommand):
|
||||||
help="shell to spawn"
|
help="shell to spawn"
|
||||||
)
|
)
|
||||||
|
|
||||||
def run(self, runner, config, args):
|
def _run_services(self, runner, args, project, services):
|
||||||
compose_cmd = ['exec', args.services[0]]
|
service = services[0]
|
||||||
shell = _find_shell(config, args, compose_cmd)
|
shell = _find_shell(args, project, service)
|
||||||
|
|
||||||
if shell is not None:
|
if shell is not None:
|
||||||
# spawn shell
|
# spawn shell
|
||||||
DockerCommand('docker-compose').run(
|
DockerCommand('docker-compose').run(project, [
|
||||||
config, args, [*compose_cmd, shell]
|
'exec', service, shell
|
||||||
)
|
])
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
# local
|
# local
|
||||||
from ._subcommand import SubCommand
|
from ._subcommand import SubCommand
|
||||||
|
|
||||||
|
# parent
|
||||||
|
from ..config import LoadedConfig
|
||||||
|
|
||||||
|
|
||||||
class ShowCommand(SubCommand):
|
class ShowCommand(SubCommand):
|
||||||
"""kiwi show"""
|
"""kiwi show"""
|
||||||
|
@ -11,6 +14,6 @@ class ShowCommand(SubCommand):
|
||||||
description="Show effective kiwi.yml"
|
description="Show effective kiwi.yml"
|
||||||
)
|
)
|
||||||
|
|
||||||
def run(self, runner, config, args):
|
def _run_instance(self, runner, args):
|
||||||
print(config)
|
print(LoadedConfig.get())
|
||||||
return True
|
return True
|
||||||
|
|
|
@ -1,28 +1,29 @@
|
||||||
# local
|
# local
|
||||||
from ._subcommand import FlexCommand
|
from ._subcommand import ServiceCommand
|
||||||
from .utils.dockercommand import DockerCommand
|
from .utils.dockercommand import DockerCommand
|
||||||
|
|
||||||
|
|
||||||
class UpCommand(FlexCommand):
|
class UpCommand(ServiceCommand):
|
||||||
"""kiwi up"""
|
"""kiwi up"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
'up', "Bringing up",
|
'up', num_projects='?', num_services='*',
|
||||||
|
action="Bringing up",
|
||||||
description="Bring up the whole instance, a project or service(s) inside a project"
|
description="Bring up the whole instance, a project or service(s) inside a project"
|
||||||
)
|
)
|
||||||
|
|
||||||
def _run_instance(self, runner, config, args):
|
def _run_instance(self, runner, args):
|
||||||
if runner.run('conf-copy'):
|
if runner.run('conf-copy'):
|
||||||
return super()._run_instance(runner, config, args)
|
return super()._run_instance(runner, args)
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _run_services(self, runner, config, args, services):
|
def _run_services(self, runner, args, project, services):
|
||||||
if runner.run('net-up'):
|
if runner.run('net-up'):
|
||||||
DockerCommand('docker-compose').run(
|
DockerCommand('docker-compose').run(project, [
|
||||||
config, args, ['up', '-d', *services]
|
'up', '-d', *services
|
||||||
)
|
])
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
|
@ -1,18 +1,19 @@
|
||||||
# local
|
# local
|
||||||
from ._subcommand import FlexCommand
|
from ._subcommand import ServiceCommand
|
||||||
from .utils.misc import are_you_sure
|
from .utils.misc import are_you_sure
|
||||||
|
|
||||||
|
|
||||||
class UpdateCommand(FlexCommand):
|
class UpdateCommand(ServiceCommand):
|
||||||
"""kiwi update"""
|
"""kiwi update"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
'update', "Updating",
|
'update', num_projects='?', num_services='*',
|
||||||
|
action="Updating",
|
||||||
description="Update the whole instance, a project or service(s) inside a project"
|
description="Update the whole instance, a project or service(s) inside a project"
|
||||||
)
|
)
|
||||||
|
|
||||||
def _run_instance(self, runner, config, args):
|
def _run_instance(self, runner, args):
|
||||||
if are_you_sure([
|
if are_you_sure([
|
||||||
"This will update the entire instance at once.",
|
"This will update the entire instance at once.",
|
||||||
"",
|
"",
|
||||||
|
@ -20,12 +21,14 @@ class UpdateCommand(FlexCommand):
|
||||||
" - Updates may take a long time",
|
" - Updates may take a long time",
|
||||||
" - Updates may break beloved functionality",
|
" - Updates may break beloved functionality",
|
||||||
]):
|
]):
|
||||||
return super()._run_instance(runner, config, args)
|
return super()._run_instance(runner, args)
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _run_services(self, runner, config, args, services):
|
def _run_services(self, runner, args, project, services):
|
||||||
result = runner.run('build')
|
result = True
|
||||||
|
|
||||||
|
result &= runner.run('build')
|
||||||
result &= runner.run('pull')
|
result &= runner.run('pull')
|
||||||
result &= runner.run('conf-copy')
|
result &= runner.run('conf-copy')
|
||||||
result &= runner.run('down')
|
result &= runner.run('down')
|
||||||
|
|
|
@ -5,21 +5,16 @@ import subprocess
|
||||||
|
|
||||||
# local
|
# local
|
||||||
from .executable import Executable
|
from .executable import Executable
|
||||||
from .project import Projects
|
|
||||||
|
|
||||||
# parent
|
# parent
|
||||||
from ..._constants import CONF_DIRECTORY_NAME
|
from ..._constants import CONF_DIRECTORY_NAME
|
||||||
from ...parser import Parser
|
|
||||||
from ...config import LoadedConfig
|
from ...config import LoadedConfig
|
||||||
|
|
||||||
|
|
||||||
def _update_kwargs(**kwargs):
|
def _update_kwargs(project, **kwargs):
|
||||||
config = LoadedConfig.get()
|
# enabled project given: command affects a project in this instance
|
||||||
projects = Projects.from_args(Parser().get_args())
|
if project is not None and project.is_enabled():
|
||||||
|
config = LoadedConfig.get()
|
||||||
# project given in args: command affects a project in this instance
|
|
||||||
if projects:
|
|
||||||
project = projects[0]
|
|
||||||
|
|
||||||
# execute command in project directory
|
# execute command in project directory
|
||||||
kwargs['cwd'] = project.dir_name()
|
kwargs['cwd'] = project.dir_name()
|
||||||
|
@ -58,19 +53,15 @@ class DockerCommand(Executable):
|
||||||
except subprocess.CalledProcessError:
|
except subprocess.CalledProcessError:
|
||||||
raise PermissionError("Cannot access docker, please get into the docker group or run as root!")
|
raise PermissionError("Cannot access docker, please get into the docker group or run as root!")
|
||||||
|
|
||||||
def run(self, config, args, process_args, **kwargs):
|
def run(self, project, process_args, **kwargs):
|
||||||
kwargs = _update_kwargs(**kwargs)
|
|
||||||
|
|
||||||
# equivalent to 'super().run' but agnostic of nested class construct
|
# equivalent to 'super().run' but agnostic of nested class construct
|
||||||
return super().__getattr__("run")(
|
return super().__getattr__("run")(
|
||||||
process_args, config,
|
process_args,
|
||||||
**kwargs
|
**_update_kwargs(project, **kwargs)
|
||||||
)
|
)
|
||||||
|
|
||||||
def run_less(self, config, args, process_args, **kwargs):
|
def run_less(self, project, process_args, **kwargs):
|
||||||
kwargs = _update_kwargs(**kwargs)
|
|
||||||
|
|
||||||
return super().__getattr__("run_less")(
|
return super().__getattr__("run_less")(
|
||||||
process_args, config,
|
process_args,
|
||||||
**kwargs
|
**_update_kwargs(project, **kwargs)
|
||||||
)
|
)
|
||||||
|
|
|
@ -3,18 +3,22 @@ import logging
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
|
# parent
|
||||||
|
from ...config import LoadedConfig
|
||||||
|
|
||||||
def _update_kwargs(config, **kwargs):
|
|
||||||
if config is not None:
|
|
||||||
# ensure there is an environment
|
|
||||||
if 'env' not in kwargs:
|
|
||||||
kwargs['env'] = {}
|
|
||||||
|
|
||||||
# add common environment from config
|
def _update_kwargs(**kwargs):
|
||||||
if config['runtime:env'] is not None:
|
config = LoadedConfig.get()
|
||||||
kwargs['env'].update(config['runtime:env'])
|
|
||||||
|
|
||||||
logging.debug(f"kwargs updated: {kwargs}")
|
# ensure there is an environment
|
||||||
|
if 'env' not in kwargs:
|
||||||
|
kwargs['env'] = {}
|
||||||
|
|
||||||
|
# add common environment from config
|
||||||
|
if config['runtime:env'] is not None:
|
||||||
|
kwargs['env'].update(config['runtime:env'])
|
||||||
|
|
||||||
|
logging.debug(f"kwargs updated: {kwargs}")
|
||||||
|
|
||||||
return kwargs
|
return kwargs
|
||||||
|
|
||||||
|
@ -48,28 +52,24 @@ class Executable:
|
||||||
logging.debug(f"Executable cmd{cmd}, kwargs{kwargs}")
|
logging.debug(f"Executable cmd{cmd}, kwargs{kwargs}")
|
||||||
return cmd
|
return cmd
|
||||||
|
|
||||||
def run(self, process_args, config=None, **kwargs):
|
def run(self, process_args, **kwargs):
|
||||||
kwargs = _update_kwargs(config, **kwargs)
|
|
||||||
|
|
||||||
return subprocess.run(
|
return subprocess.run(
|
||||||
self.__build_cmd(process_args, **kwargs),
|
self.__build_cmd(process_args, **_update_kwargs(**kwargs)),
|
||||||
**kwargs
|
**kwargs
|
||||||
)
|
)
|
||||||
|
|
||||||
def Popen(self, process_args, config=None, **kwargs):
|
def Popen(self, process_args, **kwargs):
|
||||||
kwargs = _update_kwargs(config, **kwargs)
|
|
||||||
|
|
||||||
return subprocess.Popen(
|
return subprocess.Popen(
|
||||||
self.__build_cmd(process_args, **kwargs),
|
self.__build_cmd(process_args, **_update_kwargs(**kwargs)),
|
||||||
**kwargs
|
**kwargs
|
||||||
)
|
)
|
||||||
|
|
||||||
def run_less(self, process_args, config=None, **kwargs):
|
def run_less(self, process_args, **kwargs):
|
||||||
kwargs['stdout'] = subprocess.PIPE
|
kwargs['stdout'] = subprocess.PIPE
|
||||||
kwargs['stderr'] = subprocess.DEVNULL
|
kwargs['stderr'] = subprocess.DEVNULL
|
||||||
|
|
||||||
process = self.Popen(
|
process = self.Popen(
|
||||||
process_args, config,
|
process_args,
|
||||||
**kwargs
|
**kwargs
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,20 @@ class Project:
|
||||||
def __init__(self, name):
|
def __init__(self, name):
|
||||||
self.__name = name
|
self.__name = name
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_file_name(cls, file_name):
|
||||||
|
if os.path.isdir(file_name):
|
||||||
|
config = LoadedConfig.get()
|
||||||
|
|
||||||
|
if file_name.endswith(config['markers:disabled']):
|
||||||
|
file_name = file_name[:-len(config['markers:disabled'])]
|
||||||
|
|
||||||
|
if file_name.endswith(config['markers:project']):
|
||||||
|
file_name = file_name[:-len(config['markers:project'])]
|
||||||
|
return cls(file_name)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
def get_name(self):
|
def get_name(self):
|
||||||
return self.__name
|
return self.__name
|
||||||
|
|
||||||
|
@ -78,40 +92,41 @@ class Project:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def _extract_project_name(file_name):
|
|
||||||
config = LoadedConfig.get()
|
|
||||||
enabled_suffix = config['markers:project']
|
|
||||||
disabled_suffix = f"{enabled_suffix}{config['markers:disabled']}"
|
|
||||||
|
|
||||||
if os.path.isdir(file_name):
|
|
||||||
# all subdirectories
|
|
||||||
if file_name.endswith(enabled_suffix):
|
|
||||||
# enabled projects
|
|
||||||
return file_name[:-len(enabled_suffix)]
|
|
||||||
|
|
||||||
elif file_name.endswith(disabled_suffix):
|
|
||||||
# disabled projects
|
|
||||||
return file_name[:-len(disabled_suffix)]
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
class Projects:
|
class Projects:
|
||||||
__projects = None
|
__projects = None
|
||||||
|
|
||||||
def __init__(self, names):
|
|
||||||
self.__projects = [
|
|
||||||
Project(name)
|
|
||||||
for name in names if isinstance(name, str)
|
|
||||||
]
|
|
||||||
|
|
||||||
def __getitem__(self, item):
|
def __getitem__(self, item):
|
||||||
return self.__projects[item]
|
return self.__projects[item]
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return str([
|
||||||
|
project.get_name()
|
||||||
|
for project
|
||||||
|
in self.__projects
|
||||||
|
])
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def all(cls):
|
def from_names(cls, project_names):
|
||||||
return cls([
|
result = cls()
|
||||||
_extract_project_name(file_name)
|
result.__projects = [
|
||||||
|
Project(name)
|
||||||
|
for name in project_names if isinstance(name, str)
|
||||||
|
]
|
||||||
|
return result
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_projects(cls, projects):
|
||||||
|
result = cls()
|
||||||
|
result.__projects = [
|
||||||
|
project
|
||||||
|
for project in projects if isinstance(project, Project)
|
||||||
|
]
|
||||||
|
return result
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dir(cls):
|
||||||
|
return cls.from_projects([
|
||||||
|
Project.from_file_name(file_name)
|
||||||
for file_name in os.listdir()
|
for file_name in os.listdir()
|
||||||
])
|
])
|
||||||
|
|
||||||
|
@ -119,9 +134,39 @@ class Projects:
|
||||||
def from_args(cls, args):
|
def from_args(cls, args):
|
||||||
if args is not None and 'projects' in args:
|
if args is not None and 'projects' in args:
|
||||||
if isinstance(args.projects, list) and args.projects:
|
if isinstance(args.projects, list) and args.projects:
|
||||||
return cls(args.projects)
|
return cls.from_names(args.projects)
|
||||||
|
|
||||||
elif isinstance(args.projects, str):
|
elif isinstance(args.projects, str):
|
||||||
return cls([args.projects])
|
return cls.from_names([args.projects])
|
||||||
|
|
||||||
return []
|
return cls()
|
||||||
|
|
||||||
|
def empty(self):
|
||||||
|
return not self.__projects
|
||||||
|
|
||||||
|
def filter_exists(self):
|
||||||
|
result = Projects()
|
||||||
|
result.__projects = [
|
||||||
|
project
|
||||||
|
for project in self.__projects
|
||||||
|
if project.exists()
|
||||||
|
]
|
||||||
|
return result
|
||||||
|
|
||||||
|
def filter_enabled(self):
|
||||||
|
result = Projects()
|
||||||
|
result.__projects = [
|
||||||
|
project
|
||||||
|
for project in self.__projects
|
||||||
|
if project.is_enabled()
|
||||||
|
]
|
||||||
|
return result
|
||||||
|
|
||||||
|
def filter_disabled(self):
|
||||||
|
result = Projects()
|
||||||
|
result.__projects = [
|
||||||
|
project
|
||||||
|
for project in self.__projects
|
||||||
|
if project.is_disabled()
|
||||||
|
]
|
||||||
|
return result
|
||||||
|
|
|
@ -14,6 +14,7 @@ def _prefix_path(prefix, path):
|
||||||
if isinstance(path, str):
|
if isinstance(path, str):
|
||||||
abs_path = os.path.abspath(path)
|
abs_path = os.path.abspath(path)
|
||||||
return os.path.realpath(f"{prefix}/{abs_path}")
|
return os.path.realpath(f"{prefix}/{abs_path}")
|
||||||
|
|
||||||
elif isinstance(path, list):
|
elif isinstance(path, list):
|
||||||
return [_prefix_path(prefix, p) for p in path]
|
return [_prefix_path(prefix, p) for p in path]
|
||||||
|
|
||||||
|
@ -36,55 +37,43 @@ class Rootkit:
|
||||||
def __init__(self, image_tag=None):
|
def __init__(self, image_tag=None):
|
||||||
self.__image_tag = image_tag
|
self.__image_tag = image_tag
|
||||||
|
|
||||||
def __exists(self, config, args):
|
def __exists(self):
|
||||||
ps = DockerCommand('docker').run(
|
ps = DockerCommand('docker').run(None, [
|
||||||
config, args, [
|
'images',
|
||||||
'images',
|
'--filter', f"reference={_image_name(self.__image_tag)}",
|
||||||
'--filter', f"reference={_image_name(self.__image_tag)}",
|
'--format', '{{.Repository}}:{{.Tag}}'
|
||||||
'--format', '{{.Repository}}:{{.Tag}}'
|
], stdout=subprocess.PIPE)
|
||||||
],
|
|
||||||
stdout=subprocess.PIPE
|
|
||||||
)
|
|
||||||
|
|
||||||
return str(ps.stdout, 'utf-8').strip() == _image_name(self.__image_tag)
|
return str(ps.stdout, 'utf-8').strip() == _image_name(self.__image_tag)
|
||||||
|
|
||||||
def __build_image(self, config, args):
|
def __build_image(self):
|
||||||
if self.__exists(config, args):
|
if self.__exists():
|
||||||
logging.info(f"Using image {_image_name(self.__image_tag)}")
|
logging.info(f"Using image {_image_name(self.__image_tag)}")
|
||||||
else:
|
else:
|
||||||
if self.__image_tag is None:
|
if self.__image_tag is None:
|
||||||
logging.info(f"Pulling image {_image_name(self.__image_tag)}")
|
logging.info(f"Pulling image {_image_name(self.__image_tag)}")
|
||||||
DockerCommand('docker').run(
|
DockerCommand('docker').run(None, [
|
||||||
config, args, ['pull', _image_name(self.__image_tag)],
|
'pull', _image_name(self.__image_tag)
|
||||||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
|
], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||||
)
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
logging.info(f"Building image {_image_name(self.__image_tag)}")
|
logging.info(f"Building image {_image_name(self.__image_tag)}")
|
||||||
DockerCommand('docker').run(
|
DockerCommand('docker').run(None, [
|
||||||
config, args,
|
'build',
|
||||||
[
|
'-t', _image_name(self.__image_tag),
|
||||||
'build',
|
'-f', f"{IMAGES_DIRECTORY_NAME}/{self.__image_tag}.Dockerfile",
|
||||||
'-t', _image_name(self.__image_tag),
|
f"{IMAGES_DIRECTORY_NAME}"
|
||||||
'-f', f"{IMAGES_DIRECTORY_NAME}/{self.__image_tag}.Dockerfile",
|
], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||||
f"{IMAGES_DIRECTORY_NAME}"
|
|
||||||
],
|
|
||||||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
|
|
||||||
)
|
|
||||||
|
|
||||||
def run(self, config, args, process_args, **kwargs):
|
def run(self, process_args, **kwargs):
|
||||||
self.__build_image(config, args)
|
self.__build_image()
|
||||||
DockerCommand('docker').run(
|
DockerCommand('docker').run(None, [
|
||||||
config, args,
|
'run', '--rm',
|
||||||
[
|
'-v', '/:/mnt',
|
||||||
'run', '--rm',
|
'-u', 'root',
|
||||||
'-v', '/:/mnt',
|
_image_name(self.__image_tag),
|
||||||
'-u', 'root',
|
*process_args
|
||||||
_image_name(self.__image_tag),
|
], **kwargs)
|
||||||
*process_args
|
|
||||||
],
|
|
||||||
**kwargs
|
|
||||||
)
|
|
||||||
|
|
||||||
__image_tag = None
|
__image_tag = None
|
||||||
__instances = {}
|
__instances = {}
|
||||||
|
|
Loading…
Reference in a new issue