diff --git a/.vscode/settings.json b/.vscode/settings.json index 5450c4b..5f34325 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,5 +3,8 @@ "prettier.proseWrap": "always", "python.testing.nosetestsEnabled": false, "python.testing.pytestEnabled": true, - "python.testing.unittestEnabled": false + "python.testing.unittestEnabled": false, + "[python]": { + "editor.defaultFormatter": "ms-python.black-formatter" + } } diff --git a/docs/tools.md b/docs/tools.md index 43f9ae2..3d8834f 100644 --- a/docs/tools.md +++ b/docs/tools.md @@ -1,6 +1,34 @@ # Supported Tools -## make +## Embedded commands + +### changelog (github) + +`changelog` command will produce a `CHANGELOG.md` file based on Github Releases. +You can define `CHANGELOG_FILE` environment variable to make it generate the +file in a different location. + +This command is available only when `gh` command line utility is installed. + +### containers check + +You can use this command to verify if a specific container image has a maximum +size or maximum number of layers. This is useful when you want to prevent +accidental grow of an image your are producing. + +```bash +$ mk containers check your-image-id-or-name --max-size=200 --max-layers=1 +Image has too many layers: 3 > 1 +Image size exceeded the max required size (MB): 301 > 200 +FAIL: 1 +``` + +You can also specify the container engine to be used, the default is to use +docker if found or podman if docker is not found. + +## Recognized tools + +### make If a [makefile](https://www.gnu.org/software/make/manual/make.html) is found on the repository root, the tool will expose all its targets that have a trailing @@ -10,7 +38,7 @@ the command will not be exposed, as we assume that this is an internal target. A good example of a project using this pattern is [podman](https://github.com/containers/podman). -## npm +### npm If a [package.json](https://docs.npmjs.com/cli/v7/configuring-npm/package-json) file is found, the tool will expose all the scripts defined in the `scripts`. @@ -21,28 +49,28 @@ files, we are unable to provide descriptions for exposed commands. Still, if others will find a good way to do it that gets some adoption, we will be more than happy to add support for loading descriptions too. -## shell +### shell All shell scripts found inside the repository root and`(scripts|tools|bin)/` sub-folders will be exposed as commands. -## taskfile +### taskfile [Taskfile](https://taskfile.dev/#/) is a task runner that uses YAML files. It is similar to make, but it is written in Go and it is more flexible. -## tox +### tox All tox environments will be exposed as commands and their descriptions will also be shown. Internally, the tool will run `tox -lav` to get the list of available environments and their descriptions. -## ansible +### ansible Any playbook found inside the `playbooks/` sub-folder will be exposed as a command. -## git +### git Inside git repositories, the tool will expose the `up` command which can be used to create an upstream pull request. @@ -51,12 +79,12 @@ If the current git repository is using [Gerrit](https://www.gerritcodereview.com), it will run `git review` and if the repository is from GitHub, it will run `gh pr create` instead. -## pre-commit +### pre-commit If a [pre-commit](https://pre-commit.com/) configuration file is found, the tool will expose the `lint` command for running linting. -## py (python packages) +### py (python packages) If the current repository is a Python package, the tool will expose a set of basic commands: @@ -65,15 +93,7 @@ basic commands: - `uninstall`: Uninstall the current package - `build`: Run `python -m build` -## pytest +### pytest If a [pytest](https://docs.pytest.org/en/stable/) configuration file is found, a `test` command will be exposed that runs `pytest`. - -## changelog (github) - -`changelog` command will produce a `CHANGELOG.md` file based on Github Releases. -You can define `CHANGELOG_FILE` environment variable to make it generate the -file in a different location. - -This command is available only when `gh` command line utility is installed. diff --git a/pyproject.toml b/pyproject.toml index 41a1e96..2f922b2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -63,7 +63,9 @@ repository = "https://github.com/pycontribs/mk" changelog = "https://github.com/pycontribs/mk/releases" [tool.black] - +# keep this value because typer does not accept new annotations such str | None +# from https://peps.python.org/pep-0604/ +target-version = ["py39"] # Keep this default because xml/report do not know to use load it from config file: # data_file = ".coverage" @@ -74,7 +76,7 @@ source = ["src", ".tox/*/site-packages"] exclude_also = ["pragma: no cover", "if TYPE_CHECKING:"] omit = ["test/*"] # Increase it just so it would pass on any single-python run -fail_under = 46 +fail_under = 45 skip_covered = true skip_empty = true # During development we might remove code (files) with coverage data, and we dont want to fail: @@ -135,10 +137,13 @@ disable = [ ] [tool.ruff] -target-version = "py310" +# keep this as typer does not support new annotations format +target-version = "py39" # Same as Black. line-length = 88 lint.ignore = [ + # Disabled due to typer not supporting new annotations format + "UP007", # temporary disabled until we fix them: "ANN", "B", diff --git a/src/mk/__main__.py b/src/mk/__main__.py index 6588611..7172a8c 100644 --- a/src/mk/__main__.py +++ b/src/mk/__main__.py @@ -4,10 +4,12 @@ import argparse import itertools +import json import logging import os import shlex -from typing import Any +import shutil +from typing import Annotated, Any, Optional import typer from rich.console import Console @@ -16,6 +18,7 @@ from mk import __version__ from mk._typer import CustomTyper from mk.ctx import ctx +from mk.exec import run_or_fail handlers: list[logging.Handler] console_err = Console(stderr=True) @@ -87,6 +90,54 @@ def commands() -> None: print(action.name) +@app.command() +def containers( + command: Annotated[ + Optional[str], + typer.Argument(help="Command to run, possible values: check"), + ] = None, + image: Annotated[ + str, + typer.Argument(help="Specify image name or identifier"), + ] = "", + engine: Annotated[ + str, + typer.Option(help="Comma separated list of container engines to look for."), + ] = "docker,podman", + max_size: Annotated[int, typer.Option(help="Maximum image size in MB")] = 0, + max_layers: Annotated[int, typer.Option(help="Maximum number of layers")] = 0, +) -> None: + """Provide some container related helpers.""" + if command != "check": + typer.echo("Invalid command.") + raise typer.Exit(code=1) + if image: + executable = None + for v in engine.split(","): + if shutil.which(v): + executable = v + break + if not engine: + typer.echo(f"Failed to find any container engine. ({engine})") + raise typer.Exit(code=1) + result = run_or_fail(f"{executable} image inspect {image}") + inspect_json = json.loads(result.stdout) + size = int(inspect_json[0]["Size"] / 1024 / 1024) + layers = len(inspect_json[0]["RootFS"]["Layers"]) + failed = False + if max_layers and layers > max_layers: + typer.echo(f"Image has too many layers: {layers} > {max_layers}") + failed = True + if max_size and size > max_size: + typer.echo( + f"Image size exceeded the max required size (MB): {size} > {max_size}", + ) + failed = True + if failed: + raise typer.Exit(code=1) + typer.echo("Image check passed") + + def cli() -> None: # pylint: disable=too-many-locals parser = argparse.ArgumentParser( description="Preprocess arguments to set log level.",