Skip to content

Tidy 2025 #4

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ jobs:
hatch run cov-ci
- name: Lint
run: |
hatch run lint:style
hatch run lint:all
- name: Upload to Codecov
uses: codecov/codecov-action@v3
with:
Expand Down
48 changes: 30 additions & 18 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@ build-backend = "hatchling.build"

[project]
name = "vault-dev"
dynamic = ["version"]
description = ''
description = "Test helpers for using vault"
readme = "README.md"
requires-python = ">=3.7"
requires-python = ">=3.10"
license = "MIT"
keywords = []
authors = [
Expand All @@ -16,11 +15,10 @@ authors = [
classifiers = [
"Development Status :: 4 - Beta",
"Programming Language :: Python",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
]
Expand All @@ -29,6 +27,7 @@ dependencies = [
"hvac",
"requests"
]
dynamic = ["version"]

[project.urls]
Documentation = "https://github.com/vimc/vault-dev#readme"
Expand All @@ -40,8 +39,11 @@ path = "src/vault_dev/__about__.py"

[tool.hatch.envs.default]
dependencies = [
"coverage[toml]>=6.5",
"pytest",
"pytest-cov",
"pytest-explicit",
"pytest-mock",
"coverage[toml]>=6.5",
]
[tool.hatch.envs.default.scripts]
test = "pytest {args:tests}"
Expand All @@ -64,24 +66,23 @@ cov-ci = [
]

[[tool.hatch.envs.all.matrix]]
python = ["3.7", "3.8", "3.9", "3.10", "3.11"]
python = ["3.10", "3.11", "3.12", "3.13"]

[tool.hatch.envs.lint]
detached = true
dependencies = [
extra-dependencies = [
"black>=23.1.0",
"mypy>=1.0.0",
"ruff>=0.0.243",
]
[tool.hatch.envs.lint.scripts]
typing = "mypy --install-types --non-interactive {args:src/vault_dev tests}"
typing = "mypy --install-types --non-interactive {args:src tests}"
style = [
"ruff {args:.}",
"ruff check {args:.}",
"black --check --diff {args:.}",
]
fmt = [
"black {args:.}",
"ruff --fix {args:.}",
"ruff check --fix {args:.}",
"style",
]
all = [
Expand All @@ -90,13 +91,13 @@ all = [
]

[tool.black]
target-version = ["py37"]
line-length = 80
skip-string-normalization = true

[tool.ruff]
target-version = "py37"
line-length = 80

[tool.ruff.lint]
select = [
"A",
"ARG",
Expand Down Expand Up @@ -143,13 +144,13 @@ unfixable = [
"F401",
]

[tool.ruff.isort]
[tool.ruff.lint.isort]
known-first-party = ["vault_dev"]

[tool.ruff.flake8-tidy-imports]
[tool.ruff.lint.flake8-tidy-imports]
ban-relative-imports = "all"

[tool.ruff.per-file-ignores]
[tool.ruff.lint.per-file-ignores]
# Tests can use magic values, assertions, and relative imports
"tests/**/*" = ["PLR2004", "S101", "TID252"]

Expand All @@ -171,3 +172,14 @@ exclude_lines = [
"if __name__ == .__main__.:",
"if TYPE_CHECKING:",
]


[tool.pytest.ini_options]
markers = [
"slow: slower integration tests",
"internet: tests that require internet",
]
explicit-only = [
"internet",
"slow",
]
2 changes: 1 addition & 1 deletion src/vault_dev/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ class server(Server): # noqa N801
pass


__all__ = ["Server", "VaultDevServerError", "server", "ensure_installed"]
__all__ = ["Server", "VaultDevServerError", "ensure_installed", "server"]
169 changes: 112 additions & 57 deletions src/vault_dev/install.py
Original file line number Diff line number Diff line change
@@ -1,92 +1,147 @@
## This whole file struggles with the problem of installing a global
## resource that is somewhat expensive (a vault binary, needed for
## installing tests) and we want to just use a system binary if it's
## already found.
##
## The only entrypoint that other packages need to be concerned with
## is "ensure_installed()" which can be run with no arguments and
## ensures that a suitable vault binary is installed.
##
## This package then uses the "vault_path()" function to get the path
## to either the system vault or the one that was installed.
"""Support for finding and installing the vault binary.

Use of this package requires that you have vault installed and
available. It is only a single binary, so not very hard to install,
but the package will be used in situations where we don't want to
think about this too much.

If the environment variable `$VAULT_DEV_BIN_PATH` is set, then this is
preferred (and if vault is not found here an error is thrown). This
can be the full path to the vault binary (include `.exe` on windows)
or to the directory in which `vault` or `vault.exe` is found.

If vault is present on `$PATH` we will use that.

We provide some support for downloading and installing vault, by
passing `install=True` to `ensure_installed()`.

If using GitHub actions, you can use an action
(e.g. `eLco/setup-vault`) to do this.

"""

import os
import platform
import shutil
import tempfile
import zipfile
from pathlib import Path

import requests


def ensure_installed():
if not shutil.which("vault"):
print("Did not find system vault, installing one for tests")
global_vault_dev_exe.install()
def vault_path(*, required: bool = True) -> Path | None:
"""Compute path to vault binary

Args:
required: Throw if vault is not found

def vault_path():
vault = shutil.which("vault")
if not vault:
vault = global_vault_dev_exe.vault()
return vault
Returns: The path to the vault binary, or `None` if not found, and
if `required` is `False`.

"""
if path := _find_vault_environ():
return path
if path := _find_vault_system():
return path
if required:
msg = "vault not found"
raise Exception(msg)
return None

def vault_exe_filename(platform):
if platform == "windows":
return "vault.exe"
else:
return "vault"

def ensure_installed(*, install: bool = False) -> None:
"""Ensure that vault is installed.

def vault_url(version, platform, arch="amd64"):
fmt = "https://releases.hashicorp.com/vault/{}/vault_{}_{}_{}.zip"
return fmt.format(version, version, platform, arch)
Args:
install: Install vault if not found? If not already installed
and if `install` is `False`, this function will throw.

Returns:
Nothing, called for side effects only.

"""
path = vault_path(required=install)
if path:
print(f"Found vault at '{path}'")
if not path:
print("Did not find system vault, installing one for tests")
path = _vault_install_session()
print(f"Using vault at '{path}'")

def vault_platform():
return platform.system().lower()

def vault_download(dest: str | Path, version: str, platform: str) -> Path:
"""Download vault.

def vault_download(dest, version, platform):
dest_bin = f"{dest}/{vault_exe_filename(platform)}"
if not os.path.exists(dest_bin):
Args:
dest: Destination directory
version: The version to download
platform: The platform to download (`linux`, `windows` or `darwin`)

Returns: The full path to the downloaded binary.
"""
filename = _vault_exe_filename(platform)
dest_bin = Path(dest) / filename
if not dest_bin.exists():
print(f"installing vault to '{dest}'")
url = vault_url(version, platform)
url = _vault_url(version, platform)
data = requests.get(url, timeout=600).content
with tempfile.TemporaryFile() as tmp:
tmp.write(data)
tmp.seek(0)
z = zipfile.ZipFile(tmp)
z.extract(vault_exe_filename(platform), dest)
z.extract(filename, dest)
os.chmod(dest_bin, 0o755) # noqa S103
return dest_bin


class VaultDevExe:
path = None
exe = None
def _find_vault_environ() -> Path | None:
path = os.environ.get("VAULT_DEV_BIN_PATH")
if not path:
return None
ret = Path(path)
if not ret.exists():
msg = "VAULT_DEV_BIN_PATH is set, but does not exist"
raise Exception(msg)
if ret.is_dir():
filename = _vault_exe_filename(_vault_platform())
ret = ret / filename
if not ret.exists():
msg = f"{filename} not found at {ret}, from VAULT_DEV_BIN_PATH"
raise Exception(msg)
return ret


def _find_vault_system() -> Path | None:
if path := shutil.which("vault"):
return Path(path)
else:
return None

def __init__(self):
if not self.path:
tmp = tempfile.TemporaryDirectory()
exe = vault_exe_filename(vault_platform())
self.path = tmp
self.exe = f"{tmp.name}/{exe}"

def install(self):
return vault_download(self.path.name, "1.0.0", vault_platform())
def _vault_exe_filename(platform: str) -> str:
"""Filename for the vault binary.

def exists(self):
return self.exe and os.path.exists(self.exe)
Args:
platform: The platform `windows`, `linux` or `darwin`

def vault(self):
if self.exists():
return self.exe
else:
msg = "No vault found"
raise Exception(msg)
Returns:
The name of the vault binary (either `vault.exe` or `vault`)
"""
return "vault.exe" if platform == "windows" else "vault"


def _vault_url(version: str, platform: str, arch: str = "amd64") -> str:
base = "https://releases.hashicorp.com/vault"
return f"{base}/{version}/vault_{version}_{platform}_{arch}.zip"


def _vault_platform() -> str:
return platform.system().lower()


# Package/module global used so that we only install vault once per
# session.
global_vault_dev_exe = VaultDevExe()
def _vault_install_session() -> Path:
tmp = tempfile.TemporaryDirectory(delete=False)
path = vault_download(tmp.name, "1.0.0", _vault_platform())
os.environ["VAULT_DEV_BIN_PATH"] = str(path)
return path
Empty file added src/vault_dev/py.typed
Empty file.
Loading
Loading