from abc import ABC, abstractmethod import argparse import inspect import logging import os import pathlib import sys from typing import Final, Optional, Sequence from platypush.config import Config from platypush.utils.manifest import ( Dependencies, InstallContext, ) logging.basicConfig(stream=sys.stdout) logger = logging.getLogger() class BaseBuilder(ABC): """ Base interface and utility methods for Platypush builders. A Platypush builder is a script/piece of logic that can build a Platypush installation, with all its base and required extra dependencies, given a configuration file. This class is currently implemented by the :module:`platypush.platyvenv` and :module:`platypush.platydock` modules/scripts. """ REPO_URL: Final[str] = 'https://github.com/BlackLight/platypush.git' """ We use the Github URL here rather than the self-hosted Gitea URL to prevent too many requests to the Gitea server. """ def __init__( self, cfgfile: str, gitref: str, output: str, install_context: InstallContext, *_, verbose: bool = False, device_id: Optional[str] = None, **__, ) -> None: """ :param cfgfile: The path to the configuration file. :param gitref: The git reference to use. It can be a branch name, a tag name or a commit hash. :param output: The path to the output file or directory. :param install_context: The installation context for this builder. :param verbose: Whether to log debug traces. :param device_id: A device name that will be used to uniquely identify this installation. """ self.cfgfile = os.path.abspath(os.path.expanduser(cfgfile)) self.output = os.path.abspath(os.path.expanduser(output)) self.gitref = gitref self.install_context = install_context self.device_id = device_id logger.setLevel(logging.DEBUG if verbose else logging.INFO) @classmethod @abstractmethod def get_name(cls) -> str: """ :return: The name of the builder. """ @classmethod @abstractmethod def get_description(cls) -> str: """ :return: The description of the builder. """ @property def deps(self) -> Dependencies: """ :return: The dependencies for this builder, given the configuration file and the installation context. """ return Dependencies.from_config( self.cfgfile, install_context=self.install_context, ) def _print_instructions(self, s: str) -> None: GREEN = '\033[92m' NORM = '\033[0m' helper_lines = s.split('\n') wrapper_line = '=' * max(len(t) for t in helper_lines) helper = '\n' + '\n'.join([wrapper_line, *helper_lines, wrapper_line]) + '\n' print(GREEN + helper + NORM) @abstractmethod def build(self): """ Builds the application. To be implemented by the subclasses. """ @classmethod def _get_arg_parser(cls) -> argparse.ArgumentParser: parser = argparse.ArgumentParser( prog=cls.get_name(), add_help=False, description=cls.get_description(), ) parser.add_argument( '-h', '--help', dest='show_usage', action='store_true', help='Show usage' ) parser.add_argument( '-v', '--verbose', dest='verbose', action='store_true', help='Enable debug traces', ) parser.add_argument( '-c', '--config', type=str, dest='cfgfile', required=False, default=None, help='The path to the configuration file. If not specified, a minimal ' 'installation including only the base dependencies will be generated.', ) parser.add_argument( '-o', '--output', dest='output', type=str, required=False, default='.', help='Target directory (default: current directory). For Platydock, ' 'this is the directory where the Dockerfile will be generated. For ' 'Platyvenv, this is the base directory of the new virtual ' 'environment.', ) parser.add_argument( '-d', '--device-id', dest='device_id', type=str, required=False, default=None, help='A name that will be used to uniquely identify this device. ' 'Default: a random name for Docker containers, and the ' 'hostname of the machine for virtual environments.', ) parser.add_argument( '-r', '--ref', dest='gitref', required=False, type=str, default='master', help='If the script is not run from a Platypush installation directory, ' 'it will clone the sources via git. You can specify through this ' 'option which branch, tag or commit hash to use. Defaults to master.', ) return parser @classmethod def from_cmdline(cls, args: Sequence[str]): """ Create a builder instance from command line arguments. :param args: Command line arguments. :return: A builder instance. """ parser = cls._get_arg_parser() opts, _ = parser.parse_known_args(args) if opts.show_usage: parser.print_help() sys.exit(0) if not opts.cfgfile: opts.cfgfile = os.path.join( str(pathlib.Path(inspect.getfile(Config)).parent), 'config.yaml', ) logger.info('No configuration file specified. Using %s.', opts.cfgfile) return cls(**vars(opts))