clan-cli: tests/age_keys.py add notes, move function to check sops recipients
This supports the new integration test for `clan machines delete`.
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
from clan_cli.secrets.folders import sops_secrets_folder
|
||||
from helpers import cli
|
||||
|
||||
|
||||
@@ -12,13 +14,26 @@ class KeyPair:
|
||||
|
||||
|
||||
class SopsSetup:
|
||||
"""Hold a list of three key pairs and create an "admin" user in the clan.
|
||||
|
||||
The first key in the list is used as the admin key and
|
||||
the private part of the key is exposed in the
|
||||
`SOPS_AGE_KEY` environment variable, the two others can
|
||||
be used to add machines or other users.
|
||||
"""
|
||||
|
||||
def __init__(self, keys: list[KeyPair]) -> None:
|
||||
self.keys = keys
|
||||
self.user = os.environ.get("USER", "admin")
|
||||
|
||||
# louis@(2025-03-10): It is odd to have to call an init function on a
|
||||
# fixture: the fixture should already be initialized when it is received in
|
||||
# the test function. Maybe we can arrange for the `flake` fixtures, to take
|
||||
# the `sops_setup` fixture as input and call its `init` function on the
|
||||
# correct path.
|
||||
def init(self, flake_path: Path | None = None) -> None:
|
||||
if flake_path is None:
|
||||
flake_path = Path.cwd()
|
||||
self.user = os.environ.get("USER", "user")
|
||||
cli.run(
|
||||
[
|
||||
"vars",
|
||||
@@ -49,9 +64,6 @@ KEYS = [
|
||||
|
||||
@pytest.fixture
|
||||
def age_keys() -> list[KeyPair]:
|
||||
"""
|
||||
Root directory of the tests
|
||||
"""
|
||||
return KEYS
|
||||
|
||||
|
||||
@@ -59,8 +71,33 @@ def age_keys() -> list[KeyPair]:
|
||||
def sops_setup(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> SopsSetup:
|
||||
"""
|
||||
Root directory of the tests
|
||||
"""
|
||||
monkeypatch.setenv("SOPS_AGE_KEY", KEYS[0].privkey)
|
||||
return SopsSetup(KEYS)
|
||||
|
||||
|
||||
# louis@(2025-03-10): right now this is specific to the `sops/secrets` folder,
|
||||
# but we could make it generic to any sops file if the need arises.
|
||||
def assert_secrets_file_recipients(
|
||||
flake_path: Path,
|
||||
secret_name: str,
|
||||
expected_age_recipients_keypairs: list["KeyPair"],
|
||||
err_msg: str | None = None,
|
||||
) -> None:
|
||||
"""Checks that the recipients of a secrets file matches expectations.
|
||||
|
||||
This looks up the `secret` file for `secret_name` in the `sops` directory
|
||||
under `flake_path`.
|
||||
|
||||
:param err_msg: in case of failure, if you gave an error message then it
|
||||
will be displayed, otherwise pytest will display the two different sets
|
||||
of recipients.
|
||||
"""
|
||||
sops_file = sops_secrets_folder(flake_path) / secret_name / "secret"
|
||||
with sops_file.open("rb") as fp:
|
||||
sops_data = json.load(fp)
|
||||
age_recipients = {each["recipient"] for each in sops_data["sops"]["age"]}
|
||||
expected_age_recipients = {pair.pubkey for pair in expected_age_recipients_keypairs}
|
||||
if not err_msg:
|
||||
assert age_recipients == expected_age_recipients
|
||||
return
|
||||
assert age_recipients == expected_age_recipients, err_msg
|
||||
|
||||
@@ -10,8 +10,8 @@ from pathlib import Path
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import pytest
|
||||
from age_keys import assert_secrets_file_recipients
|
||||
from clan_cli.errors import ClanError
|
||||
from clan_cli.secrets.folders import sops_secrets_folder
|
||||
from fixtures_flakes import FlakeForTest
|
||||
from helpers import cli
|
||||
from stdout import CaptureOutput
|
||||
@@ -91,7 +91,7 @@ def _test_identities(
|
||||
test_secret_name,
|
||||
]
|
||||
)
|
||||
assert_sops_file_recipients(
|
||||
assert_secrets_file_recipients(
|
||||
test_flake.path,
|
||||
test_secret_name,
|
||||
expected_age_recipients_keypairs=[age_keys[0], admin_age_key],
|
||||
@@ -111,7 +111,7 @@ def _test_identities(
|
||||
age_keys[1].privkey,
|
||||
]
|
||||
)
|
||||
assert_sops_file_recipients(
|
||||
assert_secrets_file_recipients(
|
||||
test_flake.path,
|
||||
test_secret_name,
|
||||
expected_age_recipients_keypairs=[age_keys[1], admin_age_key],
|
||||
@@ -302,7 +302,7 @@ def test_groups(
|
||||
]
|
||||
)
|
||||
|
||||
assert_sops_file_recipients(
|
||||
assert_secrets_file_recipients(
|
||||
test_flake.path,
|
||||
secret_name,
|
||||
expected_age_recipients_keypairs=[
|
||||
@@ -327,7 +327,7 @@ def test_groups(
|
||||
"user1",
|
||||
]
|
||||
)
|
||||
assert_sops_file_recipients(
|
||||
assert_secrets_file_recipients(
|
||||
test_flake.path,
|
||||
secret_name,
|
||||
expected_age_recipients_keypairs=[machine1_age_key, admin_age_key],
|
||||
@@ -349,7 +349,7 @@ def test_groups(
|
||||
"user1",
|
||||
]
|
||||
)
|
||||
assert_sops_file_recipients(
|
||||
assert_secrets_file_recipients(
|
||||
test_flake.path,
|
||||
secret_name,
|
||||
expected_age_recipients_keypairs=[
|
||||
@@ -370,7 +370,7 @@ def test_groups(
|
||||
"user1",
|
||||
]
|
||||
)
|
||||
assert_sops_file_recipients(
|
||||
assert_secrets_file_recipients(
|
||||
test_flake.path,
|
||||
secret_name,
|
||||
expected_age_recipients_keypairs=[machine1_age_key, admin_age_key],
|
||||
@@ -391,7 +391,7 @@ def test_groups(
|
||||
"machine1",
|
||||
]
|
||||
)
|
||||
assert_sops_file_recipients(
|
||||
assert_secrets_file_recipients(
|
||||
test_flake.path,
|
||||
secret_name,
|
||||
expected_age_recipients_keypairs=[admin_age_key],
|
||||
@@ -413,29 +413,6 @@ def test_groups(
|
||||
assert not group_symlink.exists(follow_symlinks=False), err_msg
|
||||
|
||||
|
||||
def assert_sops_file_recipients(
|
||||
flake_path: Path,
|
||||
secret_name: str,
|
||||
expected_age_recipients_keypairs: list["KeyPair"],
|
||||
err_msg: str | None = None,
|
||||
) -> None:
|
||||
"""Checks that the recipients of a SOPS file matches expectations.
|
||||
|
||||
:param err_msg: in case of failure, if you gave an error message then it
|
||||
will be displayed, otherwise pytest will display the two different sets
|
||||
of recipients.
|
||||
"""
|
||||
sops_file = sops_secrets_folder(flake_path) / secret_name / "secret"
|
||||
with sops_file.open("rb") as fp:
|
||||
sops_data = json.load(fp)
|
||||
age_recipients = {each["recipient"] for each in sops_data["sops"]["age"]}
|
||||
expected_age_recipients = {pair.pubkey for pair in expected_age_recipients_keypairs}
|
||||
if not err_msg:
|
||||
assert age_recipients == expected_age_recipients
|
||||
return
|
||||
assert age_recipients == expected_age_recipients, err_msg
|
||||
|
||||
|
||||
@contextmanager
|
||||
def use_age_key(key: str, monkeypatch: pytest.MonkeyPatch) -> Iterator[None]:
|
||||
old_key = os.environ["SOPS_AGE_KEY_FILE"]
|
||||
|
||||
Reference in New Issue
Block a user