diff --git a/src/kiwi/subcommands/_subcommand.py b/src/kiwi/subcommands/_subcommand.py index be50e67..34c6aae 100644 --- a/src/kiwi/subcommands/_subcommand.py +++ b/src/kiwi/subcommands/_subcommand.py @@ -2,7 +2,7 @@ import logging # local -from .utils.misc import get_first_project_name, get_services, list_projects +from .utils.project import Projects # parent from ..parser import Parser @@ -16,12 +16,13 @@ class SubCommand: # command parser _sub_parser = None - def __init__(self, name, **kwargs): + def __init__(self, name, add_parser=True, **kwargs): self.__name = name - self._sub_parser = Parser().get_subparsers().add_parser( - name, - **kwargs - ) + if add_parser: + self._sub_parser = Parser().get_subparsers().add_parser( + name, + **kwargs + ) def __str__(self): return self.__name @@ -34,9 +35,9 @@ class SubCommand: class ProjectCommand(SubCommand): """this command concerns a project in current instance""" - def __init__(self, name, num_projects, **kwargs): + def __init__(self, name, num_projects, add_parser=True, **kwargs): super().__init__( - name, + name, add_parser=add_parser, **kwargs ) @@ -54,9 +55,9 @@ class ProjectCommand(SubCommand): class ServiceCommand(ProjectCommand): """this command concerns service(s) in a project""" - def __init__(self, name, num_projects, num_services, **kwargs): + def __init__(self, name, num_projects, num_services, add_parser=True, **kwargs): super().__init__( - name, num_projects=num_projects, + name, num_projects=num_projects, add_parser=add_parser, **kwargs ) @@ -76,9 +77,9 @@ class FlexCommand(ServiceCommand): __action = None - def __init__(self, name, action='', **kwargs): + def __init__(self, name, action='', add_parser=True, **kwargs): super().__init__( - name, num_projects='?', num_services='*', + name, num_projects='?', num_services='*', add_parser=add_parser, **kwargs ) @@ -91,8 +92,8 @@ class FlexCommand(ServiceCommand): def _run_instance(self, runner, config, args): result = True - for project_name in list_projects(config): - args.projects = project_name + for project in Projects.from_args(args): + args.projects = project.get_name() result &= runner.run(str(self)) return result @@ -104,19 +105,18 @@ class FlexCommand(ServiceCommand): pass def run(self, runner, config, args): - project_name = get_first_project_name(args) - services = get_services(args) - - if project_name is None: + projects = Projects.from_args(args) + if not projects: # no project given, run for entire instance logging.info(f"{self.__action} this instance") return self._run_instance(runner, config, args) - if not services: + project = projects[0] + if args is None or 'services' not in args or not args.services: # no services given, run for whole project - logging.info(f"{self.__action} project '{project_name}'") + logging.info(f"{self.__action} project '{project.get_name()}'") return self._run_project(runner, config, args) # run for service(s) inside project - logging.info(f"{self.__action} services {services} in project '{project_name}'") - return self._run_services(runner, config, args, services) + logging.info(f"{self.__action} services {args.services} in project '{project.get_name()}'") + return self._run_services(runner, config, args, args.services) diff --git a/src/kiwi/subcommands/conf.py b/src/kiwi/subcommands/conf.py index d221943..7656f07 100644 --- a/src/kiwi/subcommands/conf.py +++ b/src/kiwi/subcommands/conf.py @@ -5,7 +5,7 @@ import subprocess # local from ._subcommand import SubCommand -from .utils.misc import list_projects, get_project_dir +from .utils.project import Projects from .utils.rootkit import Rootkit, prefix_path_mnt # parent @@ -22,13 +22,11 @@ class ConfCopyCommand(SubCommand): ) def run(self, runner, config, args): - conf_dirs = [] - - for project_name in list_projects(config): - project_conf = f"{get_project_dir(config, project_name)}/{CONF_DIRECTORY_NAME}" - - if os.path.isdir(project_conf): - conf_dirs.append(project_conf) + conf_dirs = [ + project.conf_dir_name() + for project in Projects.all() + if project.is_enabled() + ] if conf_dirs: # add target directory @@ -76,14 +74,11 @@ class ConfCleanCommand(SubCommand): def run(self, runner, config, args): result = True - # down all projects with config directories - affected_projects = [] - - for project_name in list_projects(config): - project_conf = f"{get_project_dir(config, project_name)}/{CONF_DIRECTORY_NAME}" - - if os.path.isdir(project_conf): - affected_projects.append(project_name) + affected_projects = [ + project.conf_dir_name() + for project in Projects.all() + if project.has_configs() + ] for project_name in affected_projects: args.projects = project_name @@ -91,7 +86,7 @@ class ConfCleanCommand(SubCommand): # cleanly sync configs result &= runner.run('conf-purge') - result &= runner.run('conf-purge') + result &= runner.run('conf-copy') # bring projects back up for project_name in affected_projects: diff --git a/src/kiwi/subcommands/disable.py b/src/kiwi/subcommands/disable.py index c7a00ea..29b4f88 100644 --- a/src/kiwi/subcommands/disable.py +++ b/src/kiwi/subcommands/disable.py @@ -1,5 +1,5 @@ # local -from .utils.project import Project +from .utils.project import Projects from ._subcommand import ProjectCommand @@ -13,9 +13,7 @@ class DisableCommand(ProjectCommand): ) def run(self, runner, config, args): - result = True - - for project in Project.from_args(args): - result = project.disable() - - return result + return all([ + project.disable() + for project in Projects.from_args(args) + ]) diff --git a/src/kiwi/subcommands/enable.py b/src/kiwi/subcommands/enable.py index 6778c4d..9392b95 100644 --- a/src/kiwi/subcommands/enable.py +++ b/src/kiwi/subcommands/enable.py @@ -1,5 +1,5 @@ # local -from .utils.project import Project +from .utils.project import Projects from ._subcommand import ProjectCommand @@ -13,9 +13,7 @@ class EnableCommand(ProjectCommand): ) def run(self, runner, config, args): - result = True - - for project in Project.from_args(args): - result = project.enable() - - return result + return all([ + project.enable() + for project in Projects.from_args(args) + ]) diff --git a/src/kiwi/subcommands/list.py b/src/kiwi/subcommands/list.py index 0f8e3aa..6e4cd9f 100644 --- a/src/kiwi/subcommands/list.py +++ b/src/kiwi/subcommands/list.py @@ -2,11 +2,12 @@ import logging import os import subprocess +import yaml # local from ._subcommand import FlexCommand from .utils.dockercommand import DockerCommand -from .utils.misc import list_projects, get_first_project_name, get_project_dir +from .utils.project import Projects def _print_list(strings): @@ -31,35 +32,61 @@ class ListCommand(FlexCommand): ) def _run_instance(self, runner, config, args): - print(f"Projects in instance {os.getcwd()}:") - print("") + print(f"kiwi-config instance at '{os.getcwd()}'") + print("#########") + projects = Projects.all() - _print_list(list_projects(config)) + enableds = [ + project.get_name() + for project in projects + if project.is_enabled() + ] + + if enableds: + print(f"Enabled projects:") + _print_list(enableds) + + disableds = [ + project.get_name() + for project in projects + if project.is_disabled() + ] + + if disableds: + print(f"Disabled projects:") + _print_list(disableds) return True def _run_project(self, runner, config, args): - project_name = get_first_project_name(args) - print(f"Services in project '{project_name}':") - print("") + project = Projects.from_args(args)[0] + + if not project.exists(): + logging.error(f"Project '{project.get_name()}' not found") + return False + + print(f"Services in project '{project.get_name()}':") + print("#########") ps = DockerCommand('docker-compose').run( config, args, ['config', '--services'], stdout=subprocess.PIPE ) - _print_list(ps.stdout) + _print_list(ps.stdout) return True def _run_services(self, runner, config, args, services): - import yaml + project = Projects.from_args(args)[0] - project_name = get_first_project_name(args) - project_dir = get_project_dir(config, project_name) - print(f"Configuration of services {services} in project '{project_name}':") - print("") + if not project.exists(): + logging.error(f"Project '{project.get_name()}' not found") + return False - with open(os.path.join(project_dir, 'docker-compose.yml'), 'r') as stream: + print(f"Configuration of services {services} in project '{project.get_name()}':") + print("#########") + + with open(project.compose_file_name(), 'r') as stream: try: docker_compose_yml = yaml.safe_load(stream) diff --git a/src/kiwi/subcommands/new.py b/src/kiwi/subcommands/new.py index e30a915..913f134 100644 --- a/src/kiwi/subcommands/new.py +++ b/src/kiwi/subcommands/new.py @@ -4,7 +4,7 @@ import os import shutil # local -from .utils.misc import get_project_names, get_project_dir, get_project_down_dir +from .utils.project import Projects from ._subcommand import ProjectCommand # parent @@ -22,18 +22,16 @@ class NewCommand(ProjectCommand): def run(self, runner, config, args): result = True + projects = Projects.from_args(args) - for project_name in get_project_names(args): - project_dir = get_project_dir(config, project_name) - project_down_dir = get_project_down_dir(config, project_name) - - if os.path.isdir(project_dir) or os.path.isdir(project_down_dir): - logging.error(f"Project '{project_name}' exists in this instance!") + for project in projects: + if project.exists(): + logging.error(f"Project '{project.get_name()}' exists in this instance!") result = False else: - logging.info(f"Creating project '{project_name}'") - os.mkdir(project_dir) - shutil.copy(DEFAULT_DOCKER_COMPOSE_NAME, os.path.join(project_dir, "docker-compose.yml")) + logging.info(f"Creating project '{project.get_name()}'") + os.mkdir(project.enabled_dir_name()) + shutil.copy(DEFAULT_DOCKER_COMPOSE_NAME, project.compose_file_name()) return result diff --git a/src/kiwi/subcommands/utils/dockercommand.py b/src/kiwi/subcommands/utils/dockercommand.py index 62ad1b0..5863f1d 100644 --- a/src/kiwi/subcommands/utils/dockercommand.py +++ b/src/kiwi/subcommands/utils/dockercommand.py @@ -5,18 +5,24 @@ import subprocess # local from .executable import Executable -from .misc import get_project_dir, get_first_project_name +from .project import Projects # parent from ..._constants import CONF_DIRECTORY_NAME +from ...parser import Parser +from ...config import LoadedConfig -def _update_kwargs(config, args, **kwargs): +def _update_kwargs(**kwargs): + config = LoadedConfig.get() + projects = Projects.from_args(Parser().get_args()) + # project given in args: command affects a project in this instance - project_name = get_first_project_name(args) - if project_name is not None: + if projects: + project = projects[0] + # execute command in project directory - kwargs['cwd'] = get_project_dir(config, project_name) + kwargs['cwd'] = project.dir_name() # ensure there is an environment if 'env' not in kwargs: @@ -24,10 +30,10 @@ def _update_kwargs(config, args, **kwargs): # create environment variables for docker commands kwargs['env'].update({ - 'COMPOSE_PROJECT_NAME': project_name, + 'COMPOSE_PROJECT_NAME': project.get_name(), 'KIWI_HUB_NAME': config['network:name'], 'CONFDIR': os.path.join(config['runtime:storage'], CONF_DIRECTORY_NAME), - 'TARGETDIR': os.path.join(config['runtime:storage'], get_project_dir(config, project_name)) + 'TARGETDIR': project.target_dir_name() }) logging.debug(f"kwargs updated: {kwargs}") @@ -53,7 +59,7 @@ class DockerCommand(Executable): raise PermissionError("Cannot access docker, please get into the docker group or run as root!") def run(self, config, args, process_args, **kwargs): - kwargs = _update_kwargs(config, args, **kwargs) + kwargs = _update_kwargs(**kwargs) # equivalent to 'super().run' but agnostic of nested class construct return super().__getattr__("run")( @@ -62,7 +68,7 @@ class DockerCommand(Executable): ) def run_less(self, config, args, process_args, **kwargs): - kwargs = _update_kwargs(config, args, **kwargs) + kwargs = _update_kwargs(**kwargs) return super().__getattr__("run_less")( process_args, config, diff --git a/src/kiwi/subcommands/utils/misc.py b/src/kiwi/subcommands/utils/misc.py index eb607fe..90753ab 100644 --- a/src/kiwi/subcommands/utils/misc.py +++ b/src/kiwi/subcommands/utils/misc.py @@ -1,76 +1,3 @@ -import os - - -def get_project_names(args): - """get project names from CLI args""" - - if args is not None and 'projects' in args: - if isinstance(args.projects, list): - if args.projects: - return args.projects - else: - return None - elif isinstance(args.projects, str): - return [args.projects] - - return None - - -def get_first_project_name(args): - """get first project name from CLI args""" - - names = get_project_names(args) - if names is not None: - return names[0] - - return None - - -def get_services(args): - """get services list from CLI args""" - - if args is not None and 'services' in args: - return args.services - - return None - - -def get_project_dir(config, project_name): - """get project directory""" - - return f"{project_name}{config['markers:project']}" - - -def get_project_down_dir(config, project_name): - """get project directory""" - - return f"{get_project_dir(config, project_name)}{config['markers:down']}" - - -def get_target_dir(config, project_name): - """get project's target directory""" - - return os.path.join(config['runtime:storage'], get_project_dir(config, project_name)) - - -def list_projects(config): - """list projects in current instance""" - - # complete dir listing - content = os.listdir() - - # filter directories - dirs = [d for d in content if os.path.isdir(d)] - - # filter suffix - project_dirs = [p for p in dirs if p.endswith(config['markers:project'])] - - # remove suffix - projects = [p[:-len(config['markers:project'])] for p in project_dirs] - - return projects - - def _surround(string, bang): midlane = f"{bang * 3} {string} {bang * 3}" sidelane = bang*len(midlane) @@ -86,6 +13,7 @@ def _emphasize(lines): else: return lines + def are_you_sure(prompt, default="no"): if default.lower() == 'yes': suffix = "[YES|no]" diff --git a/src/kiwi/subcommands/utils/project.py b/src/kiwi/subcommands/utils/project.py index 15d5377..7551de2 100644 --- a/src/kiwi/subcommands/utils/project.py +++ b/src/kiwi/subcommands/utils/project.py @@ -1,79 +1,58 @@ import logging import os -from kiwi._constants import CONF_DIRECTORY_NAME -from kiwi.config import LoadedConfig +from ..._constants import CONF_DIRECTORY_NAME +from ...config import LoadedConfig class Project: __name = None - __config = None def __init__(self, name): self.__name = name - self.__config = LoadedConfig.get() - - @classmethod - def from_names(cls, names): - return [cls(name) for name in names] - - @classmethod - def all(cls): - # current directory content - content = os.listdir() - - # filter subdirectories - dirs = [dir_name for dir_name in content if os.path.isdir(dir_name)] - - # filter by suffix - project_dirs = [dir_name for dir_name in dirs if dir_name.endswith(cls.__config['markers:project'])] - - # remove suffix - project_names = [project_name[:-len(cls.__config['markers:project'])] for project_name in project_dirs] - - return cls.from_names(project_names) - - @classmethod - def from_args(cls, args): - if args is not None and 'projects' in args: - if isinstance(args.projects, list) and args.projects: - return cls.from_names(args.projects) - elif isinstance(args.projects, str): - return cls.from_names([args.projects]) - - return [] def get_name(self): return self.__name def dir_name(self): - return f"{self.__name}{self.__config['markers:project']}" + if self.is_enabled(): + return self.enabled_dir_name() + elif self.is_disabled(): + return self.disabled_dir_name() + else: + return None - def down_dir_name(self): - return f"{self.dir_name()}{self.__config['markers:down']}" + def enabled_dir_name(self): + return f"{self.__name}{LoadedConfig.get()['markers:project']}" + + def disabled_dir_name(self): + return f"{self.enabled_dir_name()}{LoadedConfig.get()['markers:down']}" def conf_dir_name(self): return os.path.join(self.dir_name(), CONF_DIRECTORY_NAME) + def compose_file_name(self): + return os.path.join(self.dir_name(), 'docker-compose.yml') + def target_dir_name(self): - return os.path.join(self.__config['runtime:storage'], self.dir_name()) + return os.path.join(LoadedConfig.get()['runtime:storage'], self.enabled_dir_name()) def exists(self): - return os.path.isdir(self.dir_name()) or os.path.isdir(self.down_dir_name()) + return os.path.isdir(self.enabled_dir_name()) or os.path.isdir(self.disabled_dir_name()) def is_enabled(self): - return os.path.isdir(self.dir_name()) + return os.path.isdir(self.enabled_dir_name()) def is_disabled(self): - return os.path.isdir(self.down_dir_name()) + return os.path.isdir(self.disabled_dir_name()) def has_configs(self): - return os.path.isdir(self.dir_name()) + return os.path.isdir(self.conf_dir_name()) def enable(self): if self.is_disabled(): logging.info(f"Enabling project '{self.get_name()}'") - os.rename(self.down_dir_name(), self.dir_name()) + os.rename(self.dir_name(), self.enabled_dir_name()) elif self.is_enabled(): logging.warning(f"Project '{self.get_name()}' is enabled!") @@ -87,7 +66,7 @@ class Project: def disable(self): if self.is_enabled(): logging.info(f"Disabling project '{self.get_name()}'") - os.rename(self.dir_name(), self.down_dir_name()) + os.rename(self.dir_name(), self.disabled_dir_name()) elif self.is_disabled(): logging.warning(f"Project '{self.get_name()}' is disabled!") @@ -96,4 +75,53 @@ class Project: logging.warning(f"Project '{self.get_name()}' not found in instance!") return False - return True \ No newline at end of file + return True + + +def _extract_project_name(file_name): + config = LoadedConfig.get() + enabled_suffix = config['markers:project'] + disabled_suffix = f"{enabled_suffix}{config['markers:down']}" + + 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: + __projects = None + + def __init__(self, names): + self.__projects = [ + Project(name) + for name in names if isinstance(name, str) + ] + + def __getitem__(self, item): + return self.__projects[item] + + @classmethod + def all(cls): + return cls([ + _extract_project_name(file_name) + for file_name in os.listdir() + ]) + + @classmethod + def from_args(cls, args): + if args is not None and 'projects' in args: + if isinstance(args.projects, list) and args.projects: + return cls(args.projects) + + elif isinstance(args.projects, str): + return cls([args.projects]) + + return []