Source code for pydeployhelp.quickstart

#!/usr/bin/env python
import os
from dataclasses import dataclass
from pathlib import Path

import typer
from ruamel.yaml import YAML

from pydeployhelp import __version__
from pydeployhelp.base import CLIBase, Configs


[docs] @dataclass class QuickstartDefaults: deploy_dir: str deploy_tasks: set[str] dockerfile: str
[docs] class Quickstart(CLIBase): def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) self.defaults = QuickstartDefaults( deploy_dir="deploy", deploy_tasks={"build", "up", "down"}, dockerfile="""# use some base image FROM python:buster # run console commands inside image RUN python -m pip install --upgrade pip wheel setuptools RUN mkdir {project_name} # change current directory inside image WORKDIR {project_name} # copy files from host to image COPY requirements.txt . """, )
[docs] def start(self) -> None: """Receive info from user input and create deploy directory scripts""" try: project_name = self.enter_project_name() deploy_dir = self.enter_deploy_dir() deploy_tasks = self.enter_deploy_tasks() self.ask_to_continue() except (KeyboardInterrupt, InterruptedError, RuntimeError): self._print_service_message("Interrupted", color=typer.colors.RED, bold=True) else: self._print_service_message( f'Creating service files for project "{project_name}" at "{deploy_dir}":', color=typer.colors.GREEN ) self.create_config_file(deploy_dir, deploy_tasks) self.create_dockerfile(deploy_dir, project_name) self.create_compose(deploy_dir, project_name) self._print_service_message("Done!", color=typer.colors.GREEN, bold=True)
[docs] def enter_project_name(self) -> str: """Receive project name from user input""" project_name = Path(os.getcwd()).name if not self.silent: project_name = typer.prompt("Enter project name", default=project_name) return project_name
[docs] def enter_deploy_dir(self) -> Path: """Receive deploy directory path from user input""" deploy_dir = self.defaults.deploy_dir if not self.silent: deploy_dir = typer.prompt("Enter directory path where deploy scripts should be created", default=deploy_dir) deploy_dir = Path(deploy_dir) if deploy_dir.exists() and not deploy_dir.is_dir(): self._print_service_message( f'"{deploy_dir}"" is not a valid directory path, please try again', color=typer.colors.RED ) if self.silent: raise typer.Abort() # prevent from RecursionError return self.enter_deploy_dir() deploy_dir.mkdir(exist_ok=True, parents=True) self._add_permissions(deploy_dir) return deploy_dir
[docs] def enter_deploy_tasks(self) -> list[str]: """Receive deploy tasks names from user input""" allowed_tasks = list(self.defaults.deploy_tasks) return self.enter(allowed_items=allowed_tasks, default="all", items_name="deploy tasks")
[docs] def create_config_file(self, deploy_dir: Path, deploy_tasks: list[str]) -> None: """Create file with deploy configs and tasks pipeline""" configs = Configs( context=dict(env_file=".env", compose=f"{deploy_dir}/docker-compose-template.j2"), tasks={ task: [ dict( title=f"{task} all", pipeline=[f'docker-compose -f {deploy_dir}/docker-compose-{"{ENV}"}.yml {task}'], ) ] for task in deploy_tasks }, ) yaml = YAML() yaml.indent(mapping=2, sequence=4, offset=2) configs_path = Path(f"{deploy_dir}/config.yml") with configs_path.open("w", encoding="utf-8") as fp: yaml.dump(configs.dict(), fp) self._add_permissions(configs_path) self._print_service_message("\tconfigs\t\t\N{CHECK MARK}", color=typer.colors.GREEN)
[docs] def create_dockerfile(self, deploy_dir: Path, project_name: str) -> None: """Create file with instructions for Docker daemon to build an image""" dockerfile_path = Path(f"{deploy_dir}/Dockerfile") with dockerfile_path.open("w", encoding="utf-8") as fp: for line in self.defaults.dockerfile.format(project_name=project_name).splitlines(): fp.write(f"{line.strip()}\n") self._add_permissions(dockerfile_path) self._print_service_message("\tdockerfile\t\N{CHECK MARK}", color=typer.colors.GREEN)
[docs] def create_compose(self, deploy_dir: Path, project_name: str) -> None: data = { "version": "3", "services": { project_name + "-{{ ENV }}": { "build": {"context": "..", "dockerfile": f"{deploy_dir}/Dockerfile"}, "image": project_name + ":{{ ENV }}", "container_name": project_name + "-{{ ENV }}", } }, } yaml = YAML() yaml.indent(mapping=2, sequence=4, offset=2) compose_path = Path(f"{deploy_dir}/docker-compose-template.j2") with compose_path.open("w", encoding="utf-8") as fp: yaml.dump(data, fp) self._add_permissions(compose_path) self._print_service_message("\tdocker-compose\t\N{CHECK MARK}", color=typer.colors.GREEN)
[docs] def main( silent: bool = typer.Option(False, help="Ignore all communication with user and use default values"), version: bool = typer.Option(False, help="Print version and exit"), ) -> None: if version: typer.echo(f"pydeployhelp-quickstart version {__version__}") else: quickstart = Quickstart(silent=silent) quickstart.start()
[docs] def run() -> None: typer.run(main)
if __name__ == "__main__": run()