From 45c227aac80740d9ab9670038f2d0e999fd7cec5 Mon Sep 17 00:00:00 2001 From: Vignesh Rao Date: Sun, 18 Aug 2024 21:46:56 -0500 Subject: [PATCH] Add CLI functionality Convert to an SDK Add GH workflows --- .github/workflows/python-publish.yml | 41 +++++++++++++++++ pyproject.toml | 43 +++++++++++++++++ vaultapi/__init__.py | 69 ++++++++++++++++++++++++++++ vaultapi/main.py | 9 ++-- 4 files changed, 157 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/python-publish.yml create mode 100644 pyproject.toml diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml new file mode 100644 index 0000000..5c1fab5 --- /dev/null +++ b/.github/workflows/python-publish.yml @@ -0,0 +1,41 @@ +name: pypi-publish + +on: + release: + types: + - published + push: + branches: + - main + paths: + - '**/*.py' + workflow_dispatch: + inputs: + dry_run: + type: choice + description: Dry run mode + required: true + options: + - "true" + - "false" + +jobs: + pypi-publisher: + runs-on: thevickypedia-lite + steps: + - name: Set dry-run + run: | + if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then + echo "::notice title=DryRun::Setting dry run to ${{ inputs.dry_run }} for '${{ github.event_name }}' event" + echo "dry_run=${{ inputs.dry_run }}" >> $GITHUB_ENV + elif [[ "${{ github.event_name }}" == "push" ]]; then + echo "::notice title=DryRun::Setting dry run to true for '${{ github.event_name }}' event" + echo "dry_run=true" >> $GITHUB_ENV + else + echo "::notice title=DryRun::Setting dry run to false for '${{ github.event_name }}' event" + echo "dry_run=false" >> $GITHUB_ENV + fi + - uses: thevickypedia/pypi-publisher@v3 + with: + token: ${{ secrets.PYPI_TOKEN }} + dry-run: ${{ env.dry_run }} diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..c113497 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,43 @@ +[project] +name = "VaultAPI" +dynamic = ["version", "dependencies"] +description = "API to create and retrieve secrets from an encrypted vault." +readme = "README.md" +authors = [{ name = "Vignesh Rao", email = "svignesh1793@gmail.com" }] +license = { file = "LICENSE" } +classifiers = [ + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Development Status :: 2 - Pre-Alpha", + "Operating System :: MacOS :: MacOS X", + "Operating System :: Microsoft :: Windows", + "Operating System :: POSIX :: Linux" +] +keywords = ["vaultapi", "vault", "fastapi"] +requires-python = ">=3.10" + +[tool.setuptools] +packages = ["vaultapi"] + +[tool.setuptools.dynamic] +version = {attr = "vaultapi.version"} +dependencies = { file = ["requirements.txt"] } + +[project.optional-dependencies] +dev = ["sphinx==5.1.1", "pre-commit", "recommonmark", "gitverse"] + +[project.scripts] +# sends all the args to commandline function, where the arbitary commands as processed accordingly +vaultapi = "vaultapi:commandline" + +[build-system] +requires = ["setuptools", "wheel"] +build-backend = "setuptools.build_meta" + +[project.urls] +Homepage = "https://github.com/thevickypedia/VaultAPI" +Docs = "https://thevickypedia.github.io/VaultAPI" +Source = "https://github.com/thevickypedia/VaultAPI" +"Bug Tracker" = "https://github.com/thevickypedia/VaultAPI/issues" +"Release Notes" = "https://github.com/thevickypedia/VaultAPI/blob/main/release_notes.rst" diff --git a/vaultapi/__init__.py b/vaultapi/__init__.py index 912f9f6..1d0b750 100644 --- a/vaultapi/__init__.py +++ b/vaultapi/__init__.py @@ -1,3 +1,72 @@ +import sys + +import click +from cryptography.fernet import Fernet + from vaultapi.main import start # noqa: F401 version = "0.0.0" + + +@click.command() +@click.argument("run", required=False) +@click.argument("start", required=False) +@click.argument("keygen", required=False) +@click.option("--version", "-V", is_flag=True, help="Prints the version.") +@click.option("--help", "-H", is_flag=True, help="Prints the help section.") +@click.option( + "--env", + "-E", + type=click.Path(exists=True), + help="Environment configuration filepath.", +) +def commandline(*args, **kwargs) -> None: + """Starter function to invoke vaultapi via CLI commands. + + **Flags** + - ``--version | -V``: Prints the version. + - ``--help | -H``: Prints the help section. + - ``--env | -E``: Environment configuration filepath. + + **Commands** + ``encryptor | decryptor``: Initiates the API server. + """ + assert sys.argv[0].lower().endswith("vaultapi"), "Invalid commandline trigger!!" + options = { + "--version | -V": "Prints the version.", + "--help | -H": "Prints the help section.", + "--env | -E": "Environment configuration filepath.", + "start | run": "Initiates the API server.", + } + # weird way to increase spacing to keep all values monotonic + _longest_key = len(max(options.keys())) + _pretext = "\n\t* " + choices = _pretext + _pretext.join( + f"{k} {'·' * (_longest_key - len(k) + 8)}→ {v}".expandtabs() + for k, v in options.items() + ) + if kwargs.get("version"): + click.echo(f"vaultapi {version}") + sys.exit(0) + if kwargs.get("help"): + click.echo( + f"\nUsage: vaultapi [arbitrary-command]\nOptions (and corresponding behavior):{choices}" + ) + sys.exit(0) + trigger = kwargs.get("start") or kwargs.get("run") or kwargs.get("keygen") or "" + if trigger and trigger.lower() in ("start", "run"): + # Click doesn't support assigning defaults like traditional dictionaries, so kwargs.get("max", 100) won't work + start(env_file=kwargs.get("env")) + sys.exit(0) + elif trigger.lower() == "keygen": + key = Fernet.generate_key() + click.secho( + f"\nStore this as 'secret' or pass it as kwargs\n\n{key.decode()}\n" + ) + sys.exit(0) + else: + click.secho(f"\n{kwargs}\nNo command provided", fg="red") + click.echo( + f"Usage: vaultapi [arbitrary-command]\nOptions (and corresponding behavior):{choices}" + ) + sys.exit(1) diff --git a/vaultapi/main.py b/vaultapi/main.py index 0202498..f75eab0 100644 --- a/vaultapi/main.py +++ b/vaultapi/main.py @@ -15,12 +15,11 @@ def start(**kwargs) -> None: Keyword Args: env_file: Env filepath to load the environment variables. - apikey: API Key for authentication. - ninja_host: Hostname for the API server. - ninja_port: Port number for the API server. + apikey: API Key to authenticate the server. + secret: Secret access key to access the secret content. + host: Hostname for the API server. + port: Port number for the API server. workers: Number of workers for the uvicorn server. - remote_execution: Boolean flag to enable remote execution. - api_secret: Secret access key for running commands on server remotely. database: FilePath to store the auth database that handles the authentication errors. rate_limit: List of dictionaries with ``max_requests`` and ``seconds`` to apply as rate limit. log_config: Logging configuration as a dict or a FilePath. Supports .yaml/.yml, .json or .ini formats.