diff --git a/.gitignore b/.gitignore index c96fb6f..7aa0a20 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ mongocryptd.pid .python-version build/ uv.lock +secrets-export.sh diff --git a/django_mongodb_cli/repo.py b/django_mongodb_cli/repo.py index ccc49ca..afe5b4c 100644 --- a/django_mongodb_cli/repo.py +++ b/django_mongodb_cli/repo.py @@ -1,5 +1,6 @@ import typer import os +import shlex from .utils import Package, Repo, Test @@ -106,9 +107,7 @@ def cd( ctx: typer.Context, repo_name: str = typer.Argument(None), ): - """ - Change directory to the specified repository. - """ + """Change directory to the specified repository.""" repo = Repo() repo.ctx = ctx @@ -122,6 +121,52 @@ def cd( ) +@repo.command() +def run( + ctx: typer.Context, + repo_name: str = typer.Argument(..., help="Repository name"), + command: list[str] = typer.Argument( + ..., metavar="CMD...", help="Command (and args) to run in the repo directory" + ), +): + """Run an arbitrary command inside the repository directory. + + Examples: + dm repo run mongo-python-driver just setup tests encryption + dm repo run mongo-python-driver "just setup tests encryption" + + Environment variables can be configured per-repo under + ``[tool.django-mongodb-cli.run..env_vars]`` in ``pyproject.toml``. + """ + repo = Repo() + repo.ctx = ctx + + # Allow a single shell-style string (e.g. "just setup tests encryption"). + if len(command) == 1: + command = shlex.split(command[0]) + + path, _ = repo.ensure_repo(repo_name) + if not path: + return + + # Build environment from current process plus any configured env_vars. + env = os.environ.copy() + run_cfg = repo.run_cfg(repo_name) + env_vars_list = run_cfg.get("env_vars") + if env_vars_list: + repo.info("Setting environment variables from pyproject.toml:") + for item in env_vars_list: + name = item.get("name") + value = str(item.get("value")) + if name is None: + continue + env[name] = value + typer.echo(f" {name}={value}") + + repo.info(f"Running in {path}: {' '.join(command)}") + repo.run(command, cwd=path, env=env) + + @repo.command() def checkout( ctx: typer.Context, @@ -448,6 +493,18 @@ def reset_repo(name): ) +@repo.command() +def show( + ctx: typer.Context, + repo_name: str = typer.Argument(..., help="Repository name"), + commit_hash: str = typer.Argument(..., help="Commit hash to show"), +): + """Show the git diff for a specific commit hash in the given repository.""" + repo = Repo() + repo.ctx = ctx + repo.show_commit(repo_name, commit_hash) + + @repo.command() def set_default( repo_name: str = typer.Argument(None), diff --git a/django_mongodb_cli/utils.py b/django_mongodb_cli/utils.py index 95accde..606664e 100644 --- a/django_mongodb_cli/utils.py +++ b/django_mongodb_cli/utils.py @@ -55,8 +55,16 @@ def run( args, cwd: Path | str | None = None, check: bool = True, - env: str | None = None, + env: dict[str, str] | None = None, ) -> bool: + """Run a subprocess with optional working directory and environment. + + Args: + args: Command and arguments to run. + cwd: Optional working directory. + check: Whether to raise on non-zero exit code. + env: Optional environment mapping to pass to the subprocess. + """ try: subprocess.run(args, cwd=str(cwd) if cwd else None, check=check, env=env) return True @@ -82,6 +90,14 @@ def tool_cfg(self) -> dict: def test_cfg(self, repo_name: str) -> dict: return self.tool_cfg.get("test", {}).get(repo_name, {}) or {} + def run_cfg(self, repo_name: str) -> dict: + """Return configuration for arbitrary repo commands. + + The config is read from [tool.django-mongodb-cli.run.] in + pyproject.toml and can contain keys like ``env_vars``. + """ + return self.tool_cfg.get("run", {}).get(repo_name, {}) or {} + def evergreen_cfg(self, repo_name: str) -> dict: return self.tool_cfg.get("evergreen", {}).get(repo_name, {}) or {} @@ -466,6 +482,19 @@ def get_repo_diff(self, repo_name: str) -> None: except GitCommandError as e: self.err(f"❌ Failed to diff working tree: {e}") + def show_commit(self, repo_name: str, commit_hash: str) -> None: + """Show the diff for a specific commit hash in the given repository.""" + self.info(f"Showing diff for {repo_name}@{commit_hash}") + path, repo = self.ensure_repo(repo_name) + if not repo or not path: + return + + try: + output = repo.git.show(commit_hash) + typer.echo(output) + except GitCommandError as e: + self.err(f"❌ Failed to show commit {commit_hash}: {e}") + def _list_repos(self) -> tuple[set, set]: map_repos = set(self.map.keys()) diff --git a/pyproject.toml b/pyproject.toml index 535bd26..891948b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -122,7 +122,7 @@ value = "/Users/alex.clark/Developer/django-mongodb-cli/src/drivers-evergreen-to [[tool.django-mongodb-cli.test.mongo-python-driver.env_vars]] name = "AWS_PROFILE" -value = "drivers-test-secrets-role-857654397073" +value = "my-profile" [tool.django-mongodb-cli.test.django] test_command = "./runtests.py" @@ -142,6 +142,22 @@ test_dirs = [ "src/django/tests", "src/django-mongodb-backend/tests" ] name = "PYMONGOCRYPT_LIB" value = "/opt/homebrew/lib/libmongocrypt.dylib" +[[tool.django-mongodb-cli.test.django.env_vars]] +name = "MONGODB_URI" +value = "mongodb://localhost:64437/?directConnection=true" + +[[tool.django-mongodb-cli.run.mongo-python-driver.env_vars]] +name = "DRIVERS_TOOLS" +value = "/Users/alex.clark/Developer/django-mongodb-cli/src/drivers-evergreen-tools" + +[[tool.django-mongodb-cli.run.mongo-python-driver.env_vars]] +name = "MONGODB_URI" +value = "mongodb://localhost:64437/?directConnection=true" + +[[tool.django-mongodb-cli.run.mongo-python-driver.env_vars]] +name = "AWS_PROFILE" +value = "my-profile" + [tool.django-mongodb-cli.test.django.migrations_dir] source = "mongo_migrations" target = "src/django/tests/mongo_migrations" diff --git a/test/settings/qe.py b/test/settings/qe.py index 186d05d..2c7dfd7 100644 --- a/test/settings/qe.py +++ b/test/settings/qe.py @@ -6,6 +6,35 @@ MONGODB_URI = os.environ.get("MONGODB_URI", "mongodb://localhost:27017") +# Configure KMS providers. +# +# We prefer AWS when FLE_AWS_KEY/FLE_AWS_SECRET are set, mirroring +# src/mongo-python-driver/test/helpers_shared.py. Otherwise we fall back +# to a local KMS for convenience in local development. +AWS_CREDS = { + "accessKeyId": os.environ.get("FLE_AWS_KEY", ""), + "secretAccessKey": os.environ.get("FLE_AWS_SECRET", ""), +} +_USE_AWS_KMS = any(AWS_CREDS.values()) + +if _USE_AWS_KMS: + # Use the same demo key ARN and region as the PyMongo QE tests. + _AWS_REGION = os.environ.get("FLE_AWS_KMS_REGION", "us-east-1") + _AWS_KEY_ARN = os.environ.get( + "FLE_AWS_KMS_KEY_ARN", + "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0", + ) + KMS_PROVIDERS = {"aws": AWS_CREDS} + KMS_CREDENTIALS = {"aws": {"key": _AWS_KEY_ARN, "region": _AWS_REGION}} + DEFAULT_KMS_PROVIDER = "aws" +else: + pass + # Local-only fallback: matches the original configuration. + # KMS_PROVIDERS = {"local": {"key": os.urandom(96)}} + # KMS_CREDENTIALS = {"aws": {}} + # DEFAULT_KMS_PROVIDER = "local" + + DATABASES = { "default": { "ENGINE": "django_mongodb_backend", @@ -24,16 +53,10 @@ "OPTIONS": { "auto_encryption_opts": AutoEncryptionOpts( key_vault_namespace="djangotests_encrypted.__keyVault", - kms_providers={ - "local": { - "key": os.urandom(96), - }, - }, + kms_providers=KMS_PROVIDERS, ), }, - "KMS_CREDENTIALS": { - "aws": {}, - }, + "KMS_CREDENTIALS": KMS_CREDENTIALS, }, } @@ -57,7 +80,7 @@ def allow_migrate(self, db, app_label, model_name=None, **hints): return None def kms_provider(self, model): - return "local" + return DEFAULT_KMS_PROVIDER DATABASE_ROUTERS = ["django_mongodb_backend.routers.MongoRouter", EncryptedRouter()]