mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-11 13:17:07 +00:00
68 conflicts, resolved per hunk; resolutions agreed with the Updater session were applied as agreed: - svc-db-postgres (superuser password script and core) and sys-svc-rdbms realign_dedicated_db: updater's argv/stdin redesign with expand_argument_vars: false, nested into store-i18n's block structure. - svc-db-redis 01_init: store-i18n's ACL password on stdin. - sys-svc-proxy forwarded.conf.j2: store-i18n's handover-aware file, without the forwarded_base include (Host and X-Forwarded-Proto would be sent twice); forwarded_base serves the inline consumers, e.g. web-app-seaweedfs nginx.conf.j2. - basic.conf.j2 and web-app-openproject proxy.conf.j2: issuer redirect, then handover, else updater's location/sso_acl.conf.j2; the server-level webserver_extra_configuration hook stays. - web-app-keycloak: updater's admin-REST settings step and renumbered update steps; update/01_client.yml removed; store-i18n's design step and LDAP block kept;a73435cc9darrived through rename detection. - web-app-nextcloud OpenProject provisioning: updater's rewrite, all credentials on the Ruby runner's stdin. - web-app-semaphore: store-i18n's canonical web root (a1d7835863). - Role READMEs: no generated Cosmos/Architecture/Quick Setup/Credits sections; desk-* paths resolved to the dsk-* renames. Integration fixes for what merged cleanly but did not fit together: - web-app-matrix ansible vars: drop updater's second matrix_client_element_configuration_extension_json, which silently overrode store-i18n's design-aware value. - url.origin instead of a stripped url.base in 19 places store-i18n added since44aaa68924, same lookup targets (the double-slash lint). - Package batch (18ea75049e,cb4d340de9): installs the deploy-start batch covers are removed (caffeine, cursor, blender, npm in dsk-gnt-*, with shared references), the rest carry batch: false and a nocheck with the verified reason (PyPI fallback, before the batch, vendor repository, configured flavor only, addon gate); dev-npm references the root npm id. - sys-token-store write_many.yml: store-i18n's change-gated block and IS_SWARM_MODE. - web-app-openproject: IS_COMPOSE_MODE in 02_migrate/run.yml; the seeded-admin lock (ccbf8f168d) ported into 04_admin/provision.yml and covered by the admin-state digest. - Bucket policy (3b4ac40df5and follow-ups) moved to web-svc-seaweedfs, where store-i18n's object store lives; the S3 admin secret travels on stdin instead of a task-level environment. - act workflow.sh: no Exception-labelled comment. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
116 lines
3.6 KiB
Python
116 lines
3.6 KiB
Python
"""Install plans for the package registry.
|
|
|
|
Turns a resolved :class:`~utils.packages.registry.PackageSpec` into the
|
|
ordered module calls that acquire it. Keeping this pure means every
|
|
acquisition path - distribution repository, AUR, COPR, PPA, source build,
|
|
pinned vendor download, Flatpak remote - is unit-testable without an Ansible connection; the action
|
|
plugin only executes what it is handed.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import replace
|
|
|
|
from utils.packages.calls import (
|
|
GENERIC_PACKAGE,
|
|
STATE_ABSENT,
|
|
STATE_PRESENT,
|
|
ModuleCall,
|
|
package_call,
|
|
)
|
|
from utils.packages.schema import (
|
|
SOURCE_AUR,
|
|
SOURCE_BUILD,
|
|
SOURCE_COPR,
|
|
SOURCE_DOWNLOAD,
|
|
SOURCE_FLATPAK,
|
|
SOURCE_PPA,
|
|
PackageSpec,
|
|
)
|
|
from utils.packages.sources import (
|
|
aur_calls,
|
|
build_calls,
|
|
download_calls,
|
|
flatpak_calls,
|
|
repository_calls,
|
|
)
|
|
|
|
|
|
def build_plan(spec: PackageSpec, state: str) -> list[ModuleCall]:
|
|
"""Return the ordered module calls that bring ``spec`` into ``state``."""
|
|
names = list(spec.names)
|
|
if not names:
|
|
return []
|
|
|
|
if state == STATE_ABSENT:
|
|
return [package_call(names, STATE_ABSENT)]
|
|
|
|
if spec.source == SOURCE_AUR:
|
|
return aur_calls(names)
|
|
|
|
if spec.source == SOURCE_BUILD:
|
|
if not spec.build:
|
|
raise ValueError("source 'build' requires a 'build' block")
|
|
return build_calls(spec.build)
|
|
|
|
if spec.source == SOURCE_DOWNLOAD:
|
|
if not spec.download:
|
|
raise ValueError("source 'download' requires a 'download' block")
|
|
return download_calls(names, spec.download)
|
|
|
|
if spec.source == SOURCE_FLATPAK:
|
|
return flatpak_calls(names, spec.flatpak)
|
|
|
|
if spec.source in (SOURCE_COPR, SOURCE_PPA) and not spec.repo:
|
|
raise ValueError(f"source '{spec.source}' requires a 'repo' block")
|
|
|
|
if spec.repo:
|
|
return repository_calls(spec.repo, names)
|
|
return [package_call(names, STATE_PRESENT)]
|
|
|
|
|
|
def _plain(plan: list[ModuleCall]) -> bool:
|
|
if not plan or any(
|
|
call.module != GENERIC_PACKAGE
|
|
or call.become_user is not None
|
|
or set(call.args) != {"name", "state"}
|
|
for call in plan
|
|
):
|
|
return False
|
|
return len({call.args["state"] for call in plan}) == 1
|
|
|
|
|
|
def merge_plans(plans: list[list[ModuleCall]]) -> list[list[ModuleCall]]:
|
|
"""Fold the plans that only call the host's package manager into one plan.
|
|
|
|
The plain plans are aligned on their last call, so the n-th call from
|
|
the end of every plan lands in the same merged call and each plan's own
|
|
order survives: a bootstrap package still installs before the names it
|
|
unlocks. Every other plan follows unchanged, in request order.
|
|
|
|
Args:
|
|
plans: one plan per package id, in request order.
|
|
|
|
Returns:
|
|
one merged plan per state the plain plans bring their names into,
|
|
then every other plan.
|
|
"""
|
|
groups: dict[str, list[list[ModuleCall]]] = {}
|
|
for plan in plans:
|
|
if _plain(plan):
|
|
groups.setdefault(plan[-1].args["state"], []).append(plan)
|
|
return [_merge(group) for group in groups.values()] + [
|
|
plan for plan in plans if not _plain(plan)
|
|
]
|
|
|
|
|
|
def _merge(plain: list[list[ModuleCall]]) -> list[ModuleCall]:
|
|
merged: list[ModuleCall] = []
|
|
for offset in range(max(len(plan) for plan in plain), 0, -1):
|
|
calls = [plan[-offset] for plan in plain if len(plan) >= offset]
|
|
names = list(
|
|
dict.fromkeys(name for call in calls for name in call.args["name"])
|
|
)
|
|
merged.append(replace(calls[0], args={**calls[0].args, "name": names}))
|
|
return merged
|