#!/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 = "site-support" IMAGE_VERSION = ("0", "1", "1") REGISTRY_HOST = "git.sofus.io" REGISTRY_USER = "python-support" REGISTRY_PASSWORD = lambda: 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, to support the use of the project. Usage: echo -e "./run.py [OPTION] [EXTRAS]" The following commands must be available: podman => This project is developed and run in podman containers. => https://podman.io/ git => This project uses git for versioning, and collaboration. => https://git-scm.com/ pre-commit => Enforces that certain checks pass at each commit. => https://pre-commit.com/ Options: ./run.py => Equivilant to run.py help. ./run.py -h|h|help => Shows this text. Options, Run Locally: ./run.py app => Will run the app live on port 8787. Options, Check: ./run.py check => Will run all checks, including static analysis and tests. ./run.py analyze-quality [OPTIONS] => Not yet implemented... => Potentially, could run ruff on Python snippets in the book. ./run.py analyze-types [OPTIONS] => Not yet implemented... => Potentially, could run mypy on Python snippets in the book. ./run.py analyze-security => Will run the static security analysis suite. ./run.py analyze-format [--overwrite] [OPTIONS] => Not yet implemented... => Potentially, could format Python snippets in the book. ./run.py test => Not yet impelmented... => Potentially, would test distro commands in an appropriate container. => Potentially, would turn Python snippets into pytest units. - See https://github.com/modal-labs/pytest-markdown-docs => Will run all Markdown Python snippets as tests. Options, Build & Deploy: ./run.py build => Will build a docker image, and tag it :dev. Options, Housekeeping: ./run.py clean => Not yet implemented... => 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 - Build #################### def action_build(release = False) -> None: tag = get_git_revision_hash() if release else "dev" subprocess.run([ "podman", "build", ".", # : - Tag Commit ID "--tag", f"{IMAGE_NAME}:{tag}", ]) def action_publish() -> None: action_build(release = True) # Login to Registry subprocess.run([ "podman", "login", REGISTRY_HOST, "--username", REGISTRY_USER, "--password", REGISTRY_PASSWORD(), ]) # Tag & 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]}", ]: # Tag Image 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}", ]) #################### # - Actions - Run #################### def action_app() -> None: action_build() subprocess.run([ "podman", "run", "--rm", "-it", "--publish", "8787:8787", f"{IMAGE_NAME}:dev", ]) #################### # - Actions - Analyze #################### def action_analyze_security() -> None: Path('.cache-trivy').mkdir(parents=True, exist_ok=True) subprocess.run([ "podman", "run", "--rm", "-it", "--workdir", "/src", "--volume", ".:/src", "--volume", "./.cache-trivy:/root/.cache", "ghcr.io/aquasecurity/trivy:latest", "fs", "--quiet", "--scanners", "vuln,secret,config,license", "--exit-code", "1", "./" ]) #################### # - Script #################### if __name__ == "__main__": # Check Available Commands for cmd in CMD_DEPENDENCIES: if not cmd_exists(cmd) : print(f"{cmd} is not installed. Please see --help for instructions.") sys.exit(1) with cd_script_dir(): if len(sys.argv) > 1: { "build": action_build, "publish": action_publish, "app": action_app, "dev": lambda: print("TBD"), "analyze-security": action_analyze_security, "help": action_help, "-h": action_help, "--help": action_help, }[sys.argv[1]]() else: action_help()