Skip to content

Commit 59776ab

Browse files
committed
apply: resolve the release up front
Collection expansion can now filter on the release, but nothing supplies one. Resolve it in take_action and pass it down, with --openstack-version overriding the versions file. The resolution happens before the dispatch loop, not inside it. That loop iterates the //-separated entries and each handle_collection call ends in apply_async(), so resolving per entry would let "osism apply collection-monitoring//nutshell" schedule monitoring and only then fail on nutshell, having already touched the cluster. Done up front, a failed resolution schedules nothing at all, and the release is resolved exactly once, so every entry and every level of the recursion sees the same value even if the versions file changes mid-run. The lookup is skipped entirely for collections that contain no bounded roles, which is eight of the eleven, so such a command cannot fail on any of this. When it is needed and fails, the three causes are reported differently -- an unparseable value is already a sentence and is printed as is, while a missing file or key is appended to one naming the affected collections -- followed by the roles that needed the release and how to supply it. The advice names no file to edit: the versions file belongs to the kolla-ansible container, so the two ways an operator can supply a release are the environment variable and the flag. --openstack-version deliberately does not default from the environment the way sync.py does: openstack_release already consults it, and reading it here would make the override always truthy. Nor does it default to a release, which would be the silent guess this all exists to avoid. Note that argparse.REMAINDER on the trailing arguments swallows any flag placed after the collection name, so both the help text and the error advice lead with the environment variable, which is order-independent. Bounds stay out of the single-role path: "osism apply <role>" never consults them, because a bound is collection membership and not a claim that the play exists. Assisted-by: Claude:claude-opus-5 Signed-off-by: Roger Luethi <luethi@osism.tech>
1 parent b045b68 commit 59776ab

2 files changed

Lines changed: 361 additions & 1 deletion

File tree

osism/commands/apply.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,19 @@ def get_parser(self, prog_name):
107107
help="Dry run, do not initiate tasks (for collections only)",
108108
action="store_true",
109109
)
110+
parser.add_argument(
111+
"--openstack-version",
112+
default=None,
113+
type=str,
114+
help=(
115+
"OpenStack release the collection is applied to, e.g. 2026.1. "
116+
"Collections deploy a few roles only on certain releases (redis "
117+
"up to 2025.1, valkey from 2025.2). Read from openstack_version "
118+
"in /interface/versions/kolla-ansible.yml when not given. Must "
119+
"be given before the collection name, or it is swallowed as an "
120+
"Ansible argument. (env: OPENSTACK_VERSION)"
121+
),
122+
)
110123
parser.add_argument(
111124
"--show-tree",
112125
dest="show_tree",
@@ -329,6 +342,49 @@ def handle_collection(
329342

330343
return 0
331344

345+
def _resolve_release(self, override, bounded):
346+
"""Return the deployed release, or exit reporting why it is unknown.
347+
348+
``bounded`` maps collection name to the release-bounded roles it
349+
contains; it is used only to name what needed the release.
350+
"""
351+
from osism.data.releases import (
352+
ReleaseUndetermined,
353+
ReleaseUnparseable,
354+
openstack_release,
355+
)
356+
357+
try:
358+
return openstack_release(override)
359+
except ReleaseUnparseable as exc:
360+
# Already a complete sentence.
361+
logger.error(str(exc))
362+
except ReleaseUndetermined as exc:
363+
names = ", ".join(sorted(bounded))
364+
noun = "Collection" if len(bounded) == 1 else "Collections"
365+
verb = "contains" if len(bounded) == 1 else "contain"
366+
logger.error(
367+
f"{noun} {names} {verb} roles that depend on the OpenStack "
368+
f"release, but the release could not be determined: {exc}"
369+
)
370+
371+
# One role can appear in several collections; report it once.
372+
descriptions = {}
373+
for roles in bounded.values():
374+
for role in roles:
375+
descriptions[role.name] = role.bound_description()
376+
377+
affected = ", ".join(
378+
f"{name} ({descriptions[name]})" for name in sorted(descriptions)
379+
)
380+
logger.error(f"Affected roles: {affected}")
381+
logger.error(
382+
"Supply the release with OPENSTACK_VERSION=<release> osism apply "
383+
"<collection>, or with --openstack-version <release> before the "
384+
"collection name."
385+
)
386+
exit(1)
387+
332388
def _prepare_task(
333389
self,
334390
arguments,
@@ -502,6 +558,21 @@ def take_action(self, parsed_args):
502558

503559
rc = 0
504560

561+
# Resolve the release before the dispatch loop below, not inside it: each
562+
# iteration ends in apply_async(), so resolving per entry would let an
563+
# earlier collection reach the cluster before a later one failed.
564+
release = None
565+
if role:
566+
bounded = {}
567+
for entry in role.split("//"):
568+
if entry in enums.MAP_ROLE2ROLE:
569+
found = list(enums.bounded_roles(enums.MAP_ROLE2ROLE[entry]))
570+
if found:
571+
bounded[entry] = found
572+
573+
if bounded:
574+
release = self._resolve_release(parsed_args.openstack_version, bounded)
575+
505576
if not role:
506577
table = []
507578
for role in MAP_ROLE2ENVIRONMENT:
@@ -529,6 +600,7 @@ def take_action(self, parsed_args):
529600
retry,
530601
dry_run,
531602
show_tree,
603+
release=release,
532604
)
533605
if rc != 0:
534606
outer_break = True

0 commit comments

Comments
 (0)