#!/usr/bin/python3 # Copyright (C) 2023 Sofus Albert Høgsbro Rose # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import os import sys if not all([ sys.version_info.major == 3, sys.version_info.minor in [9, 10, 11, 12, 13], ]): sys.exit(1) from pathlib import Path import platform import shutil import subprocess import contextlib #################### # - Constants #################### SCRIPT_PATH = Path(__file__).resolve().parent CMD_DEPENDENCIES = [ 'podman', 'git', 'pre-commit' ] IMAGE_NAME = "docker-mdbook" IMAGE_VERSION = ("0", "1", "0") REGISTRY_HOST = "git.sofus.io" REGISTRY_USER = "python-support" REGISTRY_PASSWORD = subprocess.check_output( ['pass', 'services/home/git.sofus.io/container-registry-token'] ).decode('ascii').strip() #################### # - Help Text #################### def action_help() -> None: print("""This script provides one-click development, CI, and deployment. Usage: echo -e "./run.py [OPTION] [EXTRAS]" The following commands must be available: podman => The project is developed and run in podman containers. git => This project uses git for versioning, and collaboration. pre-commit => Enforces that certain checks pass at each commit. Options: ./run.sh => Equivilant to run.sh help. ./run.sh -h|h|help => Shows this text. Options, Run Locally: ./run.sh app => Will run the app on port 9500, for local development. ./run.sh cli => Will run a console in the dev container, with port 9500 closed. Options, Check: ./run.sh check => Will run all checks, including static analysis and tests. ./run.sh analyze-quality [OPTIONS] => Will run the static code-quality analysis suite. => OPTIONS will be passed directly to the tool (ruff). ./run.sh analyze-types [OPTIONS] => Will run the static type checking suite. => OPTIONS will be passed directly to the tool (pyright). ./run.sh analyze-security => Will run the static security analysis suite. ./run.sh analyze-format [--overwrite] [OPTIONS] => Will run the code formatter in read-only omde. => '--overwrite' will cause the formater to reformat all files. => OPTIONS will be passed directly to the tool (tan). ./run.sh test [OPTIONS] => Will run the test suite locally. => OPTIONS will be passed directly to the tool (pytest). Options, Build & Deploy: ./run.sh build => Will build, tag and upload a docker image appropriately. ./run.sh build-release => Will build, tag and upload a docker image appropriate for release. Options, Housekeeping: ./run.sh clean => Will delete all data caused by this project's presence on your system. """) #################### # - Utilities #################### @contextlib.contextmanager def cd_script_dir() -> None: cwd_orig = Path.cwd() os.chdir(SCRIPT_PATH) try: yield finally: os.chdir(cwd_orig) def get_git_revision_hash(short = True) -> str: commit_id = subprocess.check_output( ['git', 'rev-parse', 'HEAD'] ).decode('ascii').strip() return commit_id[:16] if short else commit_id def cmd_exists(cmd: str) -> bool: """Checks if a command exists. Supports Linux, Mac, Windows. """ return shutil.which(cmd) is not None #################### # - Actions #################### def action_build() -> None: subprocess.run([ "podman", "build", ".", "--target", "base", # : - Tag Commit ID "--tag", f"{IMAGE_NAME}:{get_git_revision_hash()}", ]) def action_run() -> None: print(" ".join([ "podman", "run", "--rm", "-it", "--volume", "./test:/src", "--workdir", "/src", "--publish", "3000:3000", f"{IMAGE_NAME}:{get_git_revision_hash()}", ] + sys.argv[2:], )) def action_publish() -> None: action_build() subprocess.run([ "podman", "login", REGISTRY_HOST, "--username", REGISTRY_USER, "--password", REGISTRY_PASSWORD, ]) # Publish Image @ : subprocess.run([ "podman", "image", "push", f"{IMAGE_NAME}:{get_git_revision_hash()}", f"{REGISTRY_HOST}/{REGISTRY_USER}/{IMAGE_NAME}:{get_git_revision_hash()}", ]) # Publish Image @ :M, :M.m, :M.m.p for image_tag in [ f"{IMAGE_NAME}:{'.'.join(IMAGE_VERSION)}", f"{IMAGE_NAME}:{'.'.join(IMAGE_VERSION[:2])}", f"{IMAGE_NAME}:{IMAGE_VERSION[0]}", ]: subprocess.run([ "podman", "tag", f"{IMAGE_NAME}:{get_git_revision_hash()}", image_tag ]) # Publish Image subprocess.run([ "podman", "image", "push", image_tag, f"{REGISTRY_HOST}/{REGISTRY_USER}/{image_tag}", ]) #################### # - Script #################### if __name__ == "__main__": # Check Available Commands for cmd in CMD_DEPENDENCIES: if not cmd_exists(cmd) : print("One or more dependencies are not installed. Please see --help.") sys.exit(1) with cd_script_dir(): if len(sys.argv) > 1: { "run": action_run, "build": action_build, "publish": action_publish, "help": action_help, "-h": action_help, "--help": action_help, }[sys.argv[1]]() else: action_help()