mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-10 12:47:04 +00:00
perf(packages): install every role's packages per host in one run at deploy start
A constructor task after the update role and the swarm auto-placement installs, in one package-manager call, every package id of every role the host's groups may run: meta dependencies, service edges and every role invocation readable from tasks and handlers, conditions ignored. Ids that must stay with their role (a conditional install, a repository added first, a need before update) carry batch: false in meta/packages.yml and keep their task; roles reference shared root ids with shared: true. 42 per-role install tasks go, and a lint keeps the split: no batched id in a role task, none in a role run ahead of the batch. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
3be77f1aa9
commit
18ea75049e
82 files changed
+1415
-189
No files matched your search
@@ -2,10 +2,12 @@ import unittest
|
||||
import unittest.mock as mock
|
||||
|
||||
from ansible.errors import AnsibleActionFail
|
||||
from ansible.plugins.action import ActionBase
|
||||
from ansible.plugins.loader import become_loader, shell_loader
|
||||
|
||||
from plugins.action.package_install import ActionModule
|
||||
from utils.packages.calls import GENERIC_PACKAGE, ModuleCall, RetryPolicy
|
||||
from utils.packages.schema import PackageSpec
|
||||
|
||||
|
||||
def _action(args) -> ActionModule:
|
||||
@@ -238,6 +240,47 @@ class TestTargetFacts(unittest.TestCase):
|
||||
self.assertIn("localhost", str(caught.exception))
|
||||
|
||||
|
||||
class TestRunMergesPlainPlans(unittest.TestCase):
|
||||
FACTS = {"distribution": "Debian", "os_family": "Debian", "pkg_mgr": "apt"}
|
||||
|
||||
def _run(self, package_ids, specs):
|
||||
action = _action({"id": package_ids})
|
||||
action._task._role = None
|
||||
action._task.delegate_to = None
|
||||
action._connection = mock.Mock(become=None, transport="local")
|
||||
action._execute_module = mock.Mock(return_value={"changed": False})
|
||||
registry = {
|
||||
package_id: mock.Mock(package_id=package_id, role=None, shared=True)
|
||||
for package_id in package_ids
|
||||
}
|
||||
with (
|
||||
mock.patch.object(ActionBase, "run", return_value={}),
|
||||
mock.patch(
|
||||
"plugins.action.package_install.build_registry", return_value=registry
|
||||
),
|
||||
mock.patch(
|
||||
"plugins.action.package_install.resolve",
|
||||
side_effect=lambda declaration, *_: specs[declaration.package_id],
|
||||
),
|
||||
):
|
||||
result = action.run(task_vars={"ansible_facts": self.FACTS})
|
||||
return action._execute_module, result
|
||||
|
||||
def test_two_repository_ids_cost_one_package_manager_call(self):
|
||||
execute, _result = self._run(
|
||||
["git", "curl"],
|
||||
{"git": PackageSpec(("git",)), "curl": PackageSpec(("curl",))},
|
||||
)
|
||||
execute.assert_called_once()
|
||||
self.assertEqual(execute.call_args.kwargs["module_args"]["name"], ["git", "curl"])
|
||||
|
||||
def test_an_id_without_names_is_reported_skipped(self):
|
||||
_execute, result = self._run(
|
||||
["git", "mhwd"], {"git": PackageSpec(("git",)), "mhwd": PackageSpec(())}
|
||||
)
|
||||
self.assertEqual(result["skipped_ids"], ["mhwd"])
|
||||
|
||||
|
||||
class TestAttempt(unittest.TestCase):
|
||||
def _action_with(self, results):
|
||||
action = _action({})
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
import unittest
|
||||
from unittest import mock
|
||||
|
||||
from ansible.errors import AnsibleError
|
||||
|
||||
from plugins.lookup.package_batch import LookupModule
|
||||
from utils.packages.schema import PackagesShapeError
|
||||
|
||||
MODULE = "plugins.lookup.package_batch"
|
||||
|
||||
|
||||
class TestPackageBatchLookup(unittest.TestCase):
|
||||
def _run(self, terms, group_names, closure=None, side_effect=None):
|
||||
applications = mock.Mock()
|
||||
applications.run.return_value = [{"web-app-x": {}}]
|
||||
with (
|
||||
mock.patch(f"{MODULE}.lookup_loader.get", return_value=applications),
|
||||
mock.patch(f"{MODULE}.types_from_group_names", return_value=["server"]),
|
||||
mock.patch(
|
||||
f"{MODULE}.deploy_closure",
|
||||
return_value=closure or {"web-app-x"},
|
||||
side_effect=side_effect,
|
||||
) as closure_mock,
|
||||
mock.patch(f"{MODULE}.batch_ids", return_value=["git"]) as ids_mock,
|
||||
):
|
||||
lookup = LookupModule()
|
||||
result = lookup.run(terms, variables={"group_names": group_names})
|
||||
return result, closure_mock, ids_mock
|
||||
|
||||
def test_returns_the_batch_ids_of_the_closure(self):
|
||||
result, _closure, ids = self._run([["web-app-x"]], ["web-app-x"])
|
||||
self.assertEqual(result, [["git"]])
|
||||
self.assertEqual(ids.call_args.args[0], {"web-app-x"})
|
||||
|
||||
def test_nested_and_scalar_terms_become_one_seed_list(self):
|
||||
_result, closure, _ids = self._run([["web-app-x"], "role-b"], ["web-app-x"])
|
||||
self.assertEqual(closure.call_args.args[1], ["web-app-x", "role-b"])
|
||||
|
||||
def test_the_host_groups_select_types_and_gates(self):
|
||||
_result, closure, _ids = self._run([["web-app-x"]], ["web-app-x", "servers"])
|
||||
self.assertEqual(closure.call_args.kwargs["host_types"], ["server"])
|
||||
self.assertEqual(
|
||||
closure.call_args.kwargs["group_names"], ["web-app-x", "servers"]
|
||||
)
|
||||
|
||||
def test_a_registry_error_becomes_an_ansible_error(self):
|
||||
with self.assertRaises(AnsibleError):
|
||||
self._run([["web-app-x"]], [], side_effect=PackagesShapeError("broken"))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user