If /usr/bin/isort is found, run it during "make format" to get a defined way the imports are sorted. tool.isort in pyproject.toml is updated to match the other fixers. Commit the fallout of this change. Running the other fixers alone doesn't change the formatting, so this should be safe. Signed-off-by: Jan Lindemann <jan@janware.com>
164 lines
5.7 KiB
Python
164 lines
5.7 KiB
Python
from __future__ import annotations
|
|
|
|
import re
|
|
import sys
|
|
|
|
from pathlib import Path
|
|
from typing import TYPE_CHECKING
|
|
|
|
from ....lib.log import DEBUG, NOTICE, WARNING, log
|
|
from ....lib.ProcFilterGpg import ProcFilterGpg
|
|
from .FilesContext import FilesContext
|
|
|
|
if TYPE_CHECKING:
|
|
from collections.abc import Collection
|
|
|
|
from ....lib.Distro import Distro
|
|
from .base import Attrs
|
|
|
|
class DistroContext(FilesContext):
|
|
|
|
def __init__(self, distro: Distro) -> None:
|
|
super().__init__(distro.ctx)
|
|
self.__distro = distro
|
|
|
|
async def match_files(self, pkg_names: Collection[str], pattern: str) -> list[str]:
|
|
ret: list[str] = []
|
|
for pkg_name in pkg_names:
|
|
for path in await self.__distro.pkg_files(pkg_name):
|
|
if re.match(pattern, path):
|
|
ret.append(path)
|
|
return ret
|
|
|
|
async def list_template_files(self, pkg_names: Collection[str]) -> list[str]:
|
|
if not pkg_names:
|
|
pkg_names = [p.name for p in await self.__distro.select()]
|
|
return await self.match_files(pkg_names, pattern = r'.*\.jw-tmpl$')
|
|
|
|
async def list_secret_paths(
|
|
self, pkg_names: Collection[str], ignore_missing: bool = False
|
|
) -> list[str]:
|
|
ret = []
|
|
for tmpl in await self.list_template_files(pkg_names):
|
|
path = str(Path(tmpl).with_suffix('.jw-secret'))
|
|
if ignore_missing and not await self.ctx.file_exists(path):
|
|
continue
|
|
ret.append(path)
|
|
return ret
|
|
|
|
async def list_compilation_targets(
|
|
self, pkg_names: Collection[str], ignore_missing: bool = False
|
|
) -> list[str]:
|
|
ret = []
|
|
for tmpl in await self.list_template_files(pkg_names):
|
|
path = tmpl.removesuffix('.jw-tmpl')
|
|
if ignore_missing and not await self.ctx.file_exists(path):
|
|
continue
|
|
ret.append(path)
|
|
return ret
|
|
|
|
async def remove_compilation_targets(self, pkg_names: Collection[str]) -> list[str]:
|
|
ret: list[str] = []
|
|
for path in await self.list_compilation_targets(pkg_names):
|
|
try:
|
|
await self.ctx.stat(path)
|
|
log(NOTICE, f'Removing {path}')
|
|
await self.ctx.unlink(path)
|
|
ret.append(path)
|
|
except FileNotFoundError:
|
|
log(DEBUG, f"Compilation target {path} doesn't exist (ignored)")
|
|
continue
|
|
return ret
|
|
|
|
async def compile_template_files(
|
|
self, pkg_names: Collection[str], default_attrs: Attrs
|
|
) -> list[str]:
|
|
ret: list[str] = []
|
|
missing = 0
|
|
for target in await self.list_compilation_targets(pkg_names):
|
|
if await self.compile_template_file(target, default_attrs):
|
|
ret.append(target)
|
|
else:
|
|
missing += 1
|
|
if missing > 0:
|
|
from ....lib.util import pretty_cmd
|
|
|
|
cmdline = pretty_cmd(sys.argv)
|
|
log(
|
|
WARNING,
|
|
(
|
|
f'{missing} missing secrets found. You might want to add them and '
|
|
f'run sudo {cmdline} again'
|
|
),
|
|
)
|
|
return ret
|
|
|
|
async def install(
|
|
self,
|
|
src_uri: str,
|
|
pkg_names: Collection[str],
|
|
only_missing: bool = False,
|
|
verbose: bool = False,
|
|
) -> None:
|
|
|
|
async def _read_secret_tar_blob(src_uri: str) -> bytes:
|
|
ec = self.ctx
|
|
from ....lib.ec.Local import Local
|
|
from ....lib.util import get
|
|
|
|
if not isinstance(ec, Local):
|
|
# Security: Use a local exec context for decrypting and
|
|
# filtering secrets
|
|
ec = Local()
|
|
return (await get(src_uri, content_filter = ProcFilterGpg(ec = ec))).stdout
|
|
|
|
def _is_needed_secret(path: str) -> bool:
|
|
return path in secret_paths
|
|
|
|
from .tar import extract as tar_extract
|
|
from .tar import filter as tar_filter
|
|
from .tar import merge as tar_merge
|
|
from .tar import rewrite as tar_rewrite
|
|
|
|
if only_missing:
|
|
raise NotImplementedError('--only-missing is not yet implemented')
|
|
|
|
secret_paths = await self.list_secret_paths(pkg_names)
|
|
hostname = self.ctx.uri.hostname
|
|
if not hostname:
|
|
raise Exception('Have no hostname to find secrets in tar file')
|
|
host_root_in_tar = '/'.join(reversed(hostname.split('.')))
|
|
host_rx = re.compile(r'^' + host_root_in_tar)
|
|
default_rx = re.compile(r'^default')
|
|
|
|
filtered_paths: list[str] = []
|
|
extracted_paths: list[str] = []
|
|
|
|
blob_all = await _read_secret_tar_blob(src_uri)
|
|
if blob_all is None:
|
|
raise Exception(f'Tar blob {src_uri} is empty')
|
|
blob_host_filtered = tar_filter(
|
|
blob_all, lambda p: re.match(host_rx, p) is not None, filtered_paths
|
|
)
|
|
blob_host_transformed = tar_rewrite(
|
|
blob_host_filtered, lambda p: re.sub(host_rx, '', p)
|
|
)
|
|
blob_default_filtered = tar_filter(
|
|
blob_all, lambda p: re.match(default_rx, p) is not None, filtered_paths
|
|
)
|
|
blob_default_transformed = tar_rewrite(
|
|
blob_default_filtered, lambda p: re.sub(default_rx, '', p)
|
|
)
|
|
blob_secret_material = tar_merge(
|
|
[blob_host_transformed, blob_default_transformed], overwrite = False
|
|
)
|
|
blob_needed = tar_filter(
|
|
blob_secret_material, _is_needed_secret, extracted_paths
|
|
)
|
|
|
|
await tar_extract(self.ctx, blob_needed, root = '/', verbose = verbose)
|
|
for path in secret_paths:
|
|
if path not in extracted_paths:
|
|
log(NOTICE, f'not extracted: {path}')
|
|
else:
|
|
log(NOTICE, f'extracted: {path}')
|