Merge pull request 'clan-cli: add autocommits for facts/secrets' (#809) from lassulus-autocommits into main
This commit is contained in:
commit
bfd525b042
@ -7,29 +7,49 @@ from clan_cli.nix import nix_shell
|
|||||||
from .cmd import Log, run
|
from .cmd import Log, run
|
||||||
|
|
||||||
|
|
||||||
# generic vcs agnostic commit function
|
|
||||||
def commit_file(
|
def commit_file(
|
||||||
file_path: Path,
|
file_path: Path,
|
||||||
repo_dir: Path,
|
repo_dir: Path,
|
||||||
commit_message: str | None = None,
|
commit_message: str | None = None,
|
||||||
|
) -> None:
|
||||||
|
"""Commit a file to a git repository.
|
||||||
|
|
||||||
|
:param file_path: The path to the file to commit.
|
||||||
|
:param repo_dir: The path to the git repository.
|
||||||
|
:param commit_message: The commit message.
|
||||||
|
:raises ClanError: If the file is not in the git repository.
|
||||||
|
"""
|
||||||
|
commit_files([file_path], repo_dir, commit_message)
|
||||||
|
|
||||||
|
|
||||||
|
# generic vcs agnostic commit function
|
||||||
|
def commit_files(
|
||||||
|
file_paths: list[Path],
|
||||||
|
repo_dir: Path,
|
||||||
|
commit_message: str | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
# check that the file is in the git repository and exists
|
# check that the file is in the git repository and exists
|
||||||
if not Path(file_path).resolve().is_relative_to(repo_dir.resolve()):
|
for file_path in file_paths:
|
||||||
raise ClanError(f"File {file_path} is not in the git repository {repo_dir}")
|
if not Path(file_path).resolve().is_relative_to(repo_dir.resolve()):
|
||||||
if not file_path.exists():
|
raise ClanError(f"File {file_path} is not in the git repository {repo_dir}")
|
||||||
raise ClanError(f"File {file_path} does not exist")
|
if not file_path.exists():
|
||||||
|
raise ClanError(f"File {file_path} does not exist")
|
||||||
# generate commit message if not provided
|
# generate commit message if not provided
|
||||||
if commit_message is None:
|
if commit_message is None:
|
||||||
# ensure that mentioned file path is relative to repo
|
commit_message = ""
|
||||||
commit_message = f"Add {file_path.relative_to(repo_dir)}"
|
for file_path in file_paths:
|
||||||
|
# ensure that mentioned file path is relative to repo
|
||||||
|
commit_message += f"Add {file_path.relative_to(repo_dir)}"
|
||||||
# check if the repo is a git repo and commit
|
# check if the repo is a git repo and commit
|
||||||
if (repo_dir / ".git").exists():
|
if (repo_dir / ".git").exists():
|
||||||
_commit_file_to_git(repo_dir, file_path, commit_message)
|
_commit_file_to_git(repo_dir, file_paths, commit_message)
|
||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
def _commit_file_to_git(repo_dir: Path, file_path: Path, commit_message: str) -> None:
|
def _commit_file_to_git(
|
||||||
|
repo_dir: Path, file_paths: list[Path], commit_message: str
|
||||||
|
) -> None:
|
||||||
"""Commit a file to a git repository.
|
"""Commit a file to a git repository.
|
||||||
|
|
||||||
:param repo_dir: The path to the git repository.
|
:param repo_dir: The path to the git repository.
|
||||||
@ -37,18 +57,20 @@ def _commit_file_to_git(repo_dir: Path, file_path: Path, commit_message: str) ->
|
|||||||
:param commit_message: The commit message.
|
:param commit_message: The commit message.
|
||||||
:raises ClanError: If the file is not in the git repository.
|
:raises ClanError: If the file is not in the git repository.
|
||||||
"""
|
"""
|
||||||
cmd = nix_shell(
|
for file_path in file_paths:
|
||||||
["nixpkgs#git"],
|
cmd = nix_shell(
|
||||||
["git", "-C", str(repo_dir), "add", str(file_path)],
|
["nixpkgs#git"],
|
||||||
)
|
["git", "-C", str(repo_dir), "add", str(file_path)],
|
||||||
# add the file to the git index
|
)
|
||||||
|
# add the file to the git index
|
||||||
|
|
||||||
run(cmd, log=Log.BOTH, error_msg=f"Failed to add {file_path} file to git index")
|
run(cmd, log=Log.BOTH, error_msg=f"Failed to add {file_path} file to git index")
|
||||||
|
|
||||||
# check if there is a diff
|
# check if there is a diff
|
||||||
cmd = nix_shell(
|
cmd = nix_shell(
|
||||||
["nixpkgs#git"],
|
["nixpkgs#git"],
|
||||||
["git", "-C", str(repo_dir), "diff", "--cached", "--exit-code", str(file_path)],
|
["git", "-C", str(repo_dir), "diff", "--cached", "--exit-code"]
|
||||||
|
+ [str(file_path) for file_path in file_paths],
|
||||||
)
|
)
|
||||||
result = run(cmd, check=False, cwd=repo_dir)
|
result = run(cmd, check=False, cwd=repo_dir)
|
||||||
# if there is no diff, return
|
# if there is no diff, return
|
||||||
@ -65,8 +87,8 @@ def _commit_file_to_git(repo_dir: Path, file_path: Path, commit_message: str) ->
|
|||||||
"commit",
|
"commit",
|
||||||
"-m",
|
"-m",
|
||||||
commit_message,
|
commit_message,
|
||||||
str(file_path.relative_to(repo_dir)),
|
]
|
||||||
],
|
+ [str(file_path) for file_path in file_paths],
|
||||||
)
|
)
|
||||||
|
|
||||||
run(cmd, error_msg=f"Failed to commit {file_path} to git repository {repo_dir}")
|
run(cmd, error_msg=f"Failed to commit {file_paths} to git repository {repo_dir}")
|
||||||
|
@ -9,6 +9,7 @@ from tempfile import TemporaryDirectory
|
|||||||
from clan_cli.cmd import run
|
from clan_cli.cmd import run
|
||||||
|
|
||||||
from ..errors import ClanError
|
from ..errors import ClanError
|
||||||
|
from ..git import commit_files
|
||||||
from ..machines.machines import Machine
|
from ..machines.machines import Machine
|
||||||
from ..nix import nix_shell
|
from ..nix import nix_shell
|
||||||
from .check import check_secrets
|
from .check import check_secrets
|
||||||
@ -22,14 +23,14 @@ def generate_secrets(machine: Machine) -> None:
|
|||||||
|
|
||||||
with TemporaryDirectory() as d:
|
with TemporaryDirectory() as d:
|
||||||
for service in machine.secrets_data:
|
for service in machine.secrets_data:
|
||||||
print(service)
|
|
||||||
tmpdir = Path(d) / service
|
tmpdir = Path(d) / service
|
||||||
# check if all secrets exist and generate them if at least one is missing
|
# check if all secrets exist and generate them if at least one is missing
|
||||||
needs_regeneration = not check_secrets(machine)
|
needs_regeneration = not check_secrets(machine)
|
||||||
for fact in machine.secrets_data[service]["facts"].values():
|
|
||||||
if not (machine.flake / fact).exists():
|
|
||||||
print(f"fact {fact} is missing")
|
|
||||||
if needs_regeneration:
|
if needs_regeneration:
|
||||||
|
if not isinstance(machine.flake, Path):
|
||||||
|
msg = f"flake is not a Path: {machine.flake}"
|
||||||
|
msg += "fact/secret generation is only supported for local flakes"
|
||||||
|
|
||||||
env = os.environ.copy()
|
env = os.environ.copy()
|
||||||
facts_dir = tmpdir / "facts"
|
facts_dir = tmpdir / "facts"
|
||||||
facts_dir.mkdir(parents=True)
|
facts_dir.mkdir(parents=True)
|
||||||
@ -62,6 +63,7 @@ def generate_secrets(machine: Machine) -> None:
|
|||||||
cmd,
|
cmd,
|
||||||
env=env,
|
env=env,
|
||||||
)
|
)
|
||||||
|
files_to_commit = []
|
||||||
# store secrets
|
# store secrets
|
||||||
for secret in machine.secrets_data[service]["secrets"]:
|
for secret in machine.secrets_data[service]["secrets"]:
|
||||||
secret_file = secrets_dir / secret
|
secret_file = secrets_dir / secret
|
||||||
@ -69,7 +71,12 @@ def generate_secrets(machine: Machine) -> None:
|
|||||||
msg = f"did not generate a file for '{secret}' when running the following command:\n"
|
msg = f"did not generate a file for '{secret}' when running the following command:\n"
|
||||||
msg += machine.secrets_data[service]["generator"]
|
msg += machine.secrets_data[service]["generator"]
|
||||||
raise ClanError(msg)
|
raise ClanError(msg)
|
||||||
secret_store.set(service, secret, secret_file.read_bytes())
|
secret_path = secret_store.set(
|
||||||
|
service, secret, secret_file.read_bytes()
|
||||||
|
)
|
||||||
|
if secret_path:
|
||||||
|
files_to_commit.append(secret_path)
|
||||||
|
|
||||||
# store facts
|
# store facts
|
||||||
for name, fact_path in machine.secrets_data[service]["facts"].items():
|
for name, fact_path in machine.secrets_data[service]["facts"].items():
|
||||||
fact_file = facts_dir / name
|
fact_file = facts_dir / name
|
||||||
@ -80,6 +87,12 @@ def generate_secrets(machine: Machine) -> None:
|
|||||||
fact_path = machine.flake / fact_path
|
fact_path = machine.flake / fact_path
|
||||||
fact_path.parent.mkdir(parents=True, exist_ok=True)
|
fact_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
shutil.copyfile(fact_file, fact_path)
|
shutil.copyfile(fact_file, fact_path)
|
||||||
|
files_to_commit.append(fact_path)
|
||||||
|
commit_files(
|
||||||
|
files_to_commit,
|
||||||
|
machine.flake_dir,
|
||||||
|
f"Update facts/secrets for service {service} in machine {machine.name}",
|
||||||
|
)
|
||||||
|
|
||||||
print("successfully generated secrets")
|
print("successfully generated secrets")
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ class SecretStore:
|
|||||||
def __init__(self, machine: Machine) -> None:
|
def __init__(self, machine: Machine) -> None:
|
||||||
self.machine = machine
|
self.machine = machine
|
||||||
|
|
||||||
def set(self, service: str, name: str, value: bytes) -> None:
|
def set(self, _service: str, name: str, value: bytes) -> Path | None:
|
||||||
subprocess.run(
|
subprocess.run(
|
||||||
nix_shell(
|
nix_shell(
|
||||||
["nixpkgs#pass"],
|
["nixpkgs#pass"],
|
||||||
@ -19,8 +19,9 @@ class SecretStore:
|
|||||||
input=value,
|
input=value,
|
||||||
check=True,
|
check=True,
|
||||||
)
|
)
|
||||||
|
return None # we manage the files outside of the git repo
|
||||||
|
|
||||||
def get(self, service: str, name: str) -> bytes:
|
def get(self, _service: str, name: str) -> bytes:
|
||||||
return subprocess.run(
|
return subprocess.run(
|
||||||
nix_shell(
|
nix_shell(
|
||||||
["nixpkgs#pass"],
|
["nixpkgs#pass"],
|
||||||
@ -30,7 +31,7 @@ class SecretStore:
|
|||||||
stdout=subprocess.PIPE,
|
stdout=subprocess.PIPE,
|
||||||
).stdout
|
).stdout
|
||||||
|
|
||||||
def exists(self, service: str, name: str) -> bool:
|
def exists(self, _service: str, name: str) -> bool:
|
||||||
password_store = os.environ.get(
|
password_store = os.environ.get(
|
||||||
"PASSWORD_STORE_DIR", f"{os.environ['HOME']}/.password-store"
|
"PASSWORD_STORE_DIR", f"{os.environ['HOME']}/.password-store"
|
||||||
)
|
)
|
||||||
|
@ -28,13 +28,17 @@ class SecretStore:
|
|||||||
)
|
)
|
||||||
add_machine(self.machine.flake_dir, self.machine.name, pub_key, False)
|
add_machine(self.machine.flake_dir, self.machine.name, pub_key, False)
|
||||||
|
|
||||||
def set(self, _service: str, name: str, value: bytes) -> None:
|
def set(self, _service: str, name: str, value: bytes) -> Path | None:
|
||||||
|
path = (
|
||||||
|
sops_secrets_folder(self.machine.flake_dir) / f"{self.machine.name}-{name}"
|
||||||
|
)
|
||||||
encrypt_secret(
|
encrypt_secret(
|
||||||
self.machine.flake_dir,
|
self.machine.flake_dir,
|
||||||
sops_secrets_folder(self.machine.flake_dir) / f"{self.machine.name}-{name}",
|
path,
|
||||||
value.decode(),
|
value.decode(),
|
||||||
add_machines=[self.machine.name],
|
add_machines=[self.machine.name],
|
||||||
)
|
)
|
||||||
|
return path
|
||||||
|
|
||||||
def get(self, _service: str, _name: str) -> bytes:
|
def get(self, _service: str, _name: str) -> bytes:
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
Loading…
Reference in New Issue
Block a user