diff --git a/example/Makefile b/example/Makefile new file mode 120000 index 0000000..d0b0e8e --- /dev/null +++ b/example/Makefile @@ -0,0 +1 @@ +../Makefile \ No newline at end of file diff --git a/base.conf b/example/base.conf similarity index 77% rename from base.conf rename to example/base.conf index 7d392e4..0c8a157 100644 --- a/base.conf +++ b/example/base.conf @@ -2,6 +2,6 @@ export SUFFIX_PROJECT=.project export SUFFIX_DOWN=.down export DOCKERNET=kiwinet -export DOCKERCIDR=10.13.37.0/24 +export DOCKERCIDR=10.22.46.0/24 export TARGETROOT=/var/kiwi diff --git a/example/hello-world.project/docker-compose.yml b/example/hello-world.project/docker-compose.yml new file mode 100644 index 0000000..201bd21 --- /dev/null +++ b/example/hello-world.project/docker-compose.yml @@ -0,0 +1,15 @@ +version: "2" + +networks: + # reachable from outside + default: + driver: bridge + # interconnects projects + kiwihub: + external: + name: $DOCKERNET + +services: + hello-world: + image: alpine:latest + command: sh -c 'while :; do echo Hello World; sleep 10; done' diff --git a/example/kiwi.yml b/example/kiwi.yml new file mode 100644 index 0000000..4a18c26 --- /dev/null +++ b/example/kiwi.yml @@ -0,0 +1,22 @@ +###################################### +# kiwi-config instance configuration # +###################################### + +version: '0.1' + +runtime: + storage: /var/kiwi + env: null + +markers: + project: .project + down: .down + +network: + name: kiwinet + cidr: 10.22.46.0/24 + +executables: + docker: /usr/bin/docker + docker-compose: /usr/local/bin/docker-compose + sudo: /usr/bin/sudo diff --git a/kiwi.yml b/kiwi.yml deleted file mode 100644 index 57bf16d..0000000 --- a/kiwi.yml +++ /dev/null @@ -1 +0,0 @@ -version: '0.1' \ No newline at end of file diff --git a/src/default.kiwi.yml b/src/default.kiwi.yml index b642fda..06384df 100644 --- a/src/default.kiwi.yml +++ b/src/default.kiwi.yml @@ -2,12 +2,16 @@ # kiwi-config instance configuration # ###################################### version: +runtime: + storage: /var/kiwi + env: null markers: project: .project down: .down network: name: kiwinet cidr: 10.22.46.0/24 -runtime: - storage: /var/kiwi - env: null +executables: + docker: null + docker-compose: null + sudo: null diff --git a/src/kiwi/config.py b/src/kiwi/config.py index 7f32b09..2d23d99 100644 --- a/src/kiwi/config.py +++ b/src/kiwi/config.py @@ -9,6 +9,7 @@ from .core import KIWI_ROOT, KIWI_CONF_NAME # CONSTANTS DEFAULT_KIWI_CONF_NAME = KIWI_ROOT + "/default.kiwi.yml" +VERSION_TAG_NAME = KIWI_ROOT + "/version-tag" class Config: @@ -49,35 +50,39 @@ class Config: return yml_string - def __update_from_file(self, filename): + def _update_from_file(self, filename): with open(filename, 'r') as stream: try: self.__yml_content.update(yaml.safe_load(stream)) except yaml.YAMLError as exc: logging.error(exc) - def __save_to_file(self, filename): - with open(filename, 'w') as stream: + def save(self): + with open(KIWI_CONF_NAME, 'w') as stream: stream.write(str(self)) - @classmethod - def default(cls): - result = cls() - result.__update_from_file(DEFAULT_KIWI_CONF_NAME) - with open(KIWI_ROOT + "/version-tag", 'r') as stream: - result.__yml_content["version"] = stream.read().strip() - - return result +class DefaultConfig(Config): + __instance = None @classmethod - def load(cls): - result = cls.default() + def get(cls): + if cls.__instance is None: + cls.__instance = cls() + cls.__instance._update_from_file(DEFAULT_KIWI_CONF_NAME) + + with open(VERSION_TAG_NAME, 'r') as stream: + cls.__instance["version"] = stream.read().strip() + + return cls.__instance + + +class LoadedConfig(Config): + @classmethod + def get(cls): + result = DefaultConfig.get() if os.path.isfile(KIWI_CONF_NAME): - result.__update_from_file(KIWI_CONF_NAME) + result._update_from_file(KIWI_CONF_NAME) return result - - def save(self): - self.__save_to_file(KIWI_CONF_NAME) diff --git a/src/kiwi/runner.py b/src/kiwi/runner.py index c51606a..553f497 100644 --- a/src/kiwi/runner.py +++ b/src/kiwi/runner.py @@ -7,7 +7,8 @@ from .subcommands import * class Runner: __commands: List[SubCommand] = [ InitCommand, - ShowCommand + ShowCommand, + LogsCommand ] @classmethod diff --git a/src/kiwi/subcommands/__init__.py b/src/kiwi/subcommands/__init__.py index a8cc797..d1a8d83 100644 --- a/src/kiwi/subcommands/__init__.py +++ b/src/kiwi/subcommands/__init__.py @@ -1,6 +1,12 @@ -from .subcommand import SubCommand +from ._utils import SubCommand from .init import InitCommand from .show import ShowCommand +from .logs import LogsCommand -__all__ = ['SubCommand', 'InitCommand', 'ShowCommand'] \ No newline at end of file +__all__ = [ + 'SubCommand', + 'InitCommand', + 'ShowCommand', + 'LogsCommand' +] \ No newline at end of file diff --git a/src/kiwi/subcommands/_utils.py b/src/kiwi/subcommands/_utils.py new file mode 100644 index 0000000..c985e57 --- /dev/null +++ b/src/kiwi/subcommands/_utils.py @@ -0,0 +1,53 @@ +import subprocess + +from ..config import LoadedConfig + + +class SubCommand: + @classmethod + def get_cmd(cls): + pass + + @classmethod + def setup(cls): + pass + + @classmethod + def run(cls): + pass + + +class Docker: + __requires_root = None + + @classmethod + def __check_requires_root(cls): + if cls.__requires_root is None: + try: + config = LoadedConfig.get() + subprocess.run( + [config['executables:docker'], 'ps'], + check=True, + stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL + ) + cls.__requires_root = False + except subprocess.CalledProcessError: + cls.__requires_root = True + + return cls.__requires_root + + @classmethod + def run_command(cls, program, args, cwd=None, env=None): + config = LoadedConfig.get() + cmd = [config['executables:' + program], *args] + + if cls.__check_requires_root(): + cmd = [config['executables:sudo'], *cmd] + + print(cmd) + return subprocess.run( + cmd, + # stdout=subprocess.PIPE, + # stderr=subprocess.PIPE, + cwd=cwd, env=env + ) diff --git a/src/kiwi/subcommands/init.py b/src/kiwi/subcommands/init.py index d8485d6..4e342d6 100644 --- a/src/kiwi/subcommands/init.py +++ b/src/kiwi/subcommands/init.py @@ -1,10 +1,10 @@ import logging import os -from kiwi.core import KIWI_CONF_NAME, Parser -from kiwi.config import Config +from ..core import KIWI_CONF_NAME, Parser +from ..config import DefaultConfig -from .subcommand import SubCommand +from ._utils import SubCommand def user_input(config, key, prompt): @@ -16,6 +16,25 @@ def user_input(config, key, prompt): config[key] = result +def find_exe(program_name): + for path in os.environ["PATH"].split(os.pathsep): + exe_file = os.path.join(path, program_name) + if os.path.isfile(exe_file) and os.access(exe_file, os.X_OK): + return exe_file + + return None + + +def user_input_exe(config, program_name): + exe_file = find_exe(program_name) + key = 'executables:' + program_name + + if exe_file is not None: + config[key] = exe_file + else: + user_input(config, key, "Enter path to '{}' executable".format(program_name)) + + class InitCommand(SubCommand): __parser = None @@ -30,19 +49,23 @@ class InitCommand(SubCommand): @classmethod def run(cls): - config = Config.default() + config = DefaultConfig.get() if os.path.isfile(KIWI_CONF_NAME): logging.warning("Overwriting existing '%s'!", KIWI_CONF_NAME) user_input(config, 'version', "Choose kiwi-config version") + user_input(config, 'runtime:storage', "Enter main directory for local data") + user_input(config, 'markers:project', "Enter marker string for project directories") user_input(config, 'markers:down', "Enter marker string for disabled projects") user_input(config, 'network:name', "Enter name for local docker network") user_input(config, 'network:cidr', "Enter CIDR block for local docker network") - user_input(config, 'runtime:storage', "Enter main directory for local data") + user_input_exe(config, 'docker') + user_input_exe(config, 'docker-compose') + user_input_exe(config, 'sudo') config.save() diff --git a/src/kiwi/subcommands/logs.py b/src/kiwi/subcommands/logs.py new file mode 100644 index 0000000..f9bf622 --- /dev/null +++ b/src/kiwi/subcommands/logs.py @@ -0,0 +1,19 @@ +from ..core import Parser + +from ._utils import SubCommand, Docker + + +class LogsCommand(SubCommand): + __parser = None + + @classmethod + def get_cmd(cls): + return 'logs' + + @classmethod + def setup(cls): + cls.__parser = Parser.get_subparsers().add_parser(cls.get_cmd(), help="Show logs of a project") + + @classmethod + def run(cls): + print(Docker.run_command('docker-compose', ['logs', '-tf', '--tail=10'], cwd='hello-world.project', env={'COMPOSE_PROJECT_NAME': 'hello-world'})) diff --git a/src/kiwi/subcommands/show.py b/src/kiwi/subcommands/show.py index 34b7df9..593a949 100644 --- a/src/kiwi/subcommands/show.py +++ b/src/kiwi/subcommands/show.py @@ -1,7 +1,7 @@ -from kiwi.config import Config -from kiwi.core import Parser +from ..config import LoadedConfig +from ..core import Parser -from .subcommand import SubCommand +from ._utils import SubCommand class ShowCommand(SubCommand): @@ -17,5 +17,5 @@ class ShowCommand(SubCommand): @classmethod def run(cls): - config = Config.load() + config = LoadedConfig.get() print(config) diff --git a/src/kiwi/subcommands/subcommand.py b/src/kiwi/subcommands/subcommand.py deleted file mode 100644 index 21535e5..0000000 --- a/src/kiwi/subcommands/subcommand.py +++ /dev/null @@ -1,12 +0,0 @@ -class SubCommand: - @classmethod - def get_cmd(cls): - pass - - @classmethod - def setup(cls): - pass - - @classmethod - def run(cls): - pass