From c041f2e3a218bc77c4a00ebebb7d0ef7b3b07ec0 Mon Sep 17 00:00:00 2001 From: Marc Olivier Bergeron Date: Tue, 17 Mar 2026 16:32:55 -0400 Subject: [PATCH] Added no update check flag. --- .github/workflows/tests.yml | 38 ++++++++++---------- ctf/__init__.py | 56 ----------------------------- ctf/__main__.py | 70 ++++++++++++++++++++++++++++++++++-- ctf/models.py | 4 --- ctf/utils.py | 13 +++++++ ctf/validate_json_schemas.py | 2 +- ctf/version.py | 5 ++- pyproject.toml | 2 +- 8 files changed, 104 insertions(+), 86 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 79f8854..fbc852d 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -45,9 +45,9 @@ jobs: ruff format --check ./ctf ruff check ./ctf - - name: ctf init + - name: CTF init run: | - ctf init test-ctf + ctf --no-update-check init test-ctf - name: Copy CTF files run: | @@ -56,22 +56,22 @@ jobs: ls -al test-ctf/ ls -al test-ctf/challenges - - name: ctf version + - name: CTF version working-directory: test-ctf run: | - ctf version + ctf --no-update-check version - name: CTF stats # Run this in the test-ctf directory working-directory: test-ctf run: | - ctf stats + ctf --no-update-check stats - name: CTF list # Run this in the test-ctf directory working-directory: test-ctf run: | - ctf list + ctf --no-update-check list - name: Remove docker run: | @@ -148,17 +148,17 @@ jobs: # Run this in the test-ctf directory working-directory: test-ctf run: | - ctf validate + ctf --no-update-check validate - name: Deployment check working-directory: test-ctf run: | - ctf check + ctf --no-update-check check - name: File generation working-directory: test-ctf run: | - ctf generate + ctf --no-update-check generate - name: Test deployment looping through tracks working-directory: test-ctf @@ -169,33 +169,33 @@ jobs: for track in "${tracks[@]}" do - ctf deploy --production --tracks "$track" + ctf --no-update-check deploy --production --tracks "$track" incus --project="$track" info "$track" done - ctf destroy --force + ctf --no-update-check destroy --force - name: Test full deployment working-directory: test-ctf run: | - ctf deploy --production + ctf --no-update-check deploy --production [ "$(incus list --all-projects -cn -fcsv | wc -l)" -eq 2 ] || exit 1 - ctf destroy --force + ctf --no-update-check destroy --force - name: Test redeployment of Mock Track Apache PHP working-directory: test-ctf run: | - ctf deploy --production + ctf --no-update-check deploy --production [ "$(incus list --all-projects -cn -fcsv | wc -l)" -eq 2 ] || exit 1 - ctf redeploy --production --tracks mock-track-apache-php + ctf --no-update-check redeploy --production --tracks mock-track-apache-php [ "$(incus list --all-projects -cn -fcsv | wc -l)" -eq 2 ] || exit 1 - ctf destroy --force + ctf --no-update-check destroy --force - name: Test deployment of a track not deployed without destroying the rest working-directory: test-ctf run: | - ctf deploy --production --tracks mock-track-apache-php + ctf --no-update-check deploy --production --tracks mock-track-apache-php [ "$(incus list --all-projects -cn -fcsv | wc -l)" -eq 1 ] || exit 1 - ctf redeploy --production --tracks mock-track-python-service + ctf --no-update-check redeploy --production --tracks mock-track-python-service [ "$(incus list --all-projects -cn -fcsv | wc -l)" -eq 2 ] || exit 1 - ctf destroy --force + ctf --no-update-check destroy --force diff --git a/ctf/__init__.py b/ctf/__init__.py index 8be1ff6..0425db8 100644 --- a/ctf/__init__.py +++ b/ctf/__init__.py @@ -1,63 +1,7 @@ #!/usr/bin/env python3 -import importlib.metadata -import json import os -import sys -import urllib.request - -from ctf.logger import LOG - -VERSION = importlib.metadata.version("ctf-script") - -if len(sys.argv) > 1 and sys.argv[1] == "version": - print(VERSION) - exit(code=0) - ENV = {} STATE = {"verbose": False} for k, v in os.environ.items(): ENV[k] = v - - -def check_tool_version() -> None: - try: - r_context = urllib.request.urlopen( - url="https://api.github.com/repos/nsec/ctf-script/releases/latest" - ) - except Exception as e: - LOG.debug(e) - LOG.warning("Could not verify the latest release.") - return - with r_context as r: - try: - latest_version = json.loads(s=r.read().decode())["tag_name"] - except Exception as e: - LOG.debug(e) - LOG.error("Could not verify the latest release.") - return - - compare = 0 - for current_part, latest_part in zip( - [int(part) for part in VERSION.split(".")], - [int(part) for part in latest_version.split(".")], - ): - if current_part < latest_part: - compare = -1 - break - elif current_part > latest_part: - compare = 1 - break - - match compare: - case 0 | 1: - LOG.debug("Script is up to date.") - case -1: - LOG.warning( - f"Script is outdated (current: {VERSION}, upstream: {latest_version}). Please update to the latest release before continuing." - ) - if (input("Do you want to continue? [y/N] ").lower() or "n") == "n": - exit(code=0) - - -check_tool_version() diff --git a/ctf/__main__.py b/ctf/__main__.py index c1b27ce..cd4009b 100644 --- a/ctf/__main__.py +++ b/ctf/__main__.py @@ -1,13 +1,17 @@ #!/usr/bin/env python3 +import json import logging import os +import sys +import urllib.request import rich import typer +from rich.console import Console from typer import Typer from typing_extensions import Annotated -from ctf import ENV, LOG, STATE +from ctf import ENV, STATE from ctf.check import app as check_app from ctf.deploy import app as deploy_app from ctf.destroy import app as destroy_app @@ -15,11 +19,12 @@ from ctf.generate import app as generate_app from ctf.init import app as init_app from ctf.list import app as list_app +from ctf.logger import LOG from ctf.new import app as new_app from ctf.redeploy import app as redeploy_app from ctf.services import app as services_app from ctf.stats import app as stats_app -from ctf.utils import find_ctf_root_directory +from ctf.utils import find_ctf_root_directory, get_version, show_version from ctf.validate import app as validate_app from ctf.version import app as version_app @@ -41,14 +46,72 @@ app.add_typer(version_app) +def check_tool_version() -> None: + with Console().status("Checking for updates..."): + current_version = get_version() + try: + r_context = urllib.request.urlopen( + url="https://api.github.com/repos/nsec/ctf-script/releases/latest" + ) + except Exception as e: + LOG.debug(e) + LOG.warning("Could not verify the latest release.") + return + with r_context as r: + try: + latest_version = json.loads(s=r.read().decode())["tag_name"] + except Exception as e: + LOG.debug(e) + LOG.error("Could not verify the latest release.") + return + + compare = 0 + for current_part, latest_part in zip( + [int(part) for part in current_version.split(".")], + [int(part) for part in latest_version.split(".")], + ): + if current_part < latest_part: + compare = -1 + break + elif current_part > latest_part: + compare = 1 + break + + match compare: + case 0 | 1: + LOG.debug("Script is up to date.") + case -1: + LOG.warning( + f"Script is outdated (current: {current_version}, upstream: {latest_version}). Please update to the latest release before continuing." + ) + if (input("Do you want to continue? [y/N] ").lower() or "n") == "n": + exit(code=0) + + @app.callback() def global_options( location: Annotated[ str, typer.Option("--location", help="CTF root directory location.") ] = "", + no_update_check: Annotated[ + bool, + typer.Option( + "--no-update-check", help="Do not check for update.", is_flag=True + ), + ] = False, verbose: Annotated[ bool, typer.Option("--verbose", "-v", help="Enable DEBUG logging.") ] = False, + version: Annotated[ + bool | None, + typer.Option( + "--version", + show_default=False, + is_eager=True, + callback=show_version, + help="Show version", + ), + ] = None, ): if verbose: LOG.setLevel(logging.DEBUG) @@ -58,6 +121,9 @@ def global_options( if location: ENV["CTF_ROOT_DIR"] = location + if not no_update_check: + check_tool_version() + def main(): # Set console width to 150 if it's smaller to avoid "…" in output diff --git a/ctf/models.py b/ctf/models.py index 9480cd8..328fc2f 100644 --- a/ctf/models.py +++ b/ctf/models.py @@ -139,7 +139,3 @@ class TrackYaml(BaseModel): instances: TrackInstances | None = None flags: list[TrackFlag] services: list[DeprecatedTrackService] | None = None - - -# TrackInstances.model_rebuild() -# TrackYaml.model_rebuild() diff --git a/ctf/utils.py b/ctf/utils.py index 211580d..0a51e0d 100644 --- a/ctf/utils.py +++ b/ctf/utils.py @@ -1,3 +1,4 @@ +import importlib.metadata import os import re import shutil @@ -6,6 +7,7 @@ from typing import Any, Generator import jinja2 +import typer import yaml from ctf import ENV @@ -392,6 +394,17 @@ def is_ctf_dir(path): return ctf_dir +def get_version() -> str: + return importlib.metadata.version("ctf-script") + + +def show_version(value: bool) -> None: + # If --version option is present, show the version and exit + if value: + typer.echo(f"ctf-script v{get_version()}") + raise typer.Exit() + + def terraform_binary() -> str: path = shutil.which(cmd="tofu") if not path: diff --git a/ctf/validate_json_schemas.py b/ctf/validate_json_schemas.py index ad7a809..d0b4c9d 100644 --- a/ctf/validate_json_schemas.py +++ b/ctf/validate_json_schemas.py @@ -14,7 +14,7 @@ ) from rich.table import Table -from ctf import LOG +from ctf.logger import LOG def validate_with_json_schemas(schema: str, files_pattern: str) -> None: diff --git a/ctf/version.py b/ctf/version.py index c6ec126..9b7b38b 100644 --- a/ctf/version.py +++ b/ctf/version.py @@ -1,11 +1,10 @@ import typer -from ctf import VERSION +from ctf.utils import show_version app = typer.Typer() @app.command(help="Print the tool's version.") def version(): - print(VERSION) - exit(0) + show_version(True) diff --git a/pyproject.toml b/pyproject.toml index cd42c69..c62bc42 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,7 +19,7 @@ dependencies = [ "typer==0.16.0", "pydantic" ] -version = "4.3.2" +version = "4.3.3" classifiers = [ "Programming Language :: Python :: 3", "Operating System :: OS Independent",