Skip to content

Commit 26714c9

Browse files
committed
Fix: all frontends must use the custom-frontend interface
1 parent 52d70fd commit 26714c9

2 files changed

Lines changed: 93 additions & 38 deletions

File tree

cert-tools/toolbox/src/toolbox/checkbox/installers/snaps.py

Lines changed: 27 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Checkbox installer for snap-based installations."""
22

3+
import contextlib
34
import logging
45
import os
56

@@ -105,33 +106,36 @@ def install_frontend_snap(self, snap: SnapSpecifier):
105106
strict = False
106107
return strict
107108

108-
def has_custom_frontend_connected(self, frontend: SnapSpecifier) -> bool:
109-
"""Check if custom-frontend interface is connected between runtime and frontend.
110-
111-
The runtime has the plug, the frontend has the slot.
109+
def custom_frontend_interface(self) -> bool:
110+
"""
111+
Check if ALL frontends have the custom-frontend interface connected.
112112
"""
113113
snap_connection_data = self.device.interfaces[SnapdAPIClient].get(
114114
"connections", params={"select": "all"}
115115
)
116-
try:
117-
plug = next(
118-
plug
119-
for plug in snap_connection_data["plugs"]
120-
if plug["snap"] == self.runtime.name
116+
117+
frontends = {f.name for f in self.frontends}
118+
for plug in snap_connection_data.get("plugs", []):
119+
if (
120+
plug["snap"] == self.runtime.name
121121
and plug["interface"] == "content"
122122
and plug.get("attrs", {}).get("content") == "custom-frontend"
123-
)
124-
next(conn for conn in plug["connections"] if conn["snap"] == frontend.name)
125-
logger.info(
126-
"custom-frontend interface connected: %s -> %s",
127-
self.runtime.name,
128-
frontend.name,
129-
)
130-
return True
131-
except (StopIteration, KeyError):
132-
logger.info("custom-frontend interface not connected")
123+
):
124+
custom_frontend_plug = plug
125+
break
126+
else:
127+
logger.warning("Installed runtime doesn't provide a custom-frontend plug")
128+
return False
129+
130+
for conn in custom_frontend_plug.get("connections", []):
131+
with contextlib.suppress(KeyError):
132+
frontends.remove(conn["snap"])
133+
134+
if frontends:
133135
return False
134136

137+
return True
138+
135139
def configure_agent(self, agent: SnapSpecifier):
136140
"""Configure checkbox snap agent."""
137141
logger.info("Configuring legacy agent: %s", agent.name)
@@ -192,13 +196,12 @@ def restart(self):
192196
policy=Linear(times=30, delay=10),
193197
)
194198

195-
frontend = self.frontends[0]
196-
if self.has_custom_frontend_connected(frontend):
197-
logger.info("Using new providers interface for %s", frontend.name)
199+
if self.custom_frontend_interface():
200+
logger.info("Using new providers interface with runtime agent")
198201
agent = self.runtime
199202
else:
200-
logger.info("Using legacy interface for %s", frontend.name)
201-
agent = frontend
203+
logger.info("Using legacy interface with frontend agent")
204+
agent = self.frontends[0]
202205

203206
self.configure_agent(agent)
204207

cert-tools/toolbox/tests/checkbox/installers/test_snaps.py

Lines changed: 66 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -474,8 +474,8 @@ def test_perform_connections(self, mocker):
474474
]
475475
assert len(connect_calls) == 2
476476

477-
def test_has_custom_frontend_connected_true(self, mocker):
478-
"""Test detection when custom-frontend interface is connected."""
477+
def test_custom_frontend_interface_true(self, mocker):
478+
"""Test returns True when all frontends have custom-frontend connected."""
479479
device = TrivialDevice(
480480
interfaces=[
481481
SnapdAPIClient(),
@@ -497,7 +497,10 @@ def test_has_custom_frontend_connected_true(self, mocker):
497497
"snap": "checkbox22",
498498
"interface": "content",
499499
"attrs": {"content": "custom-frontend"},
500-
"connections": [{"snap": "checkbox"}],
500+
"connections": [
501+
{"snap": "checkbox"},
502+
{"snap": "checkbox-iiotg"},
503+
],
501504
}
502505
]
503506
},
@@ -510,18 +513,71 @@ def test_has_custom_frontend_connected_true(self, mocker):
510513
)
511514

512515
frontends = [
513-
SnapSpecifier(name="checkbox", channel=Channel.from_string("22/stable"))
516+
SnapSpecifier(name="checkbox", channel=Channel.from_string("22/stable")),
517+
SnapSpecifier(
518+
name="checkbox-iiotg", channel=Channel.from_string("22/stable")
519+
),
514520
]
515521
installer = CheckboxSnapsInstaller(
516522
device, TrivialDevice(), frontends, mocker.Mock()
517523
)
518524

519-
result = installer.has_custom_frontend_connected(frontends[0])
525+
result = installer.custom_frontend_interface()
520526

521527
assert result is True
522528

523-
def test_has_custom_frontend_connected_false(self, mocker):
524-
"""Test detection when custom-frontend interface is not connected."""
529+
def test_custom_frontend_interface_false_missing(self, mocker):
530+
"""Test returns False when some frontends lack custom-frontend interface."""
531+
device = TrivialDevice(
532+
interfaces=[
533+
SnapdAPIClient(),
534+
RebootInterface(),
535+
SystemStatusInterface(),
536+
SnapInterface(),
537+
]
538+
)
539+
540+
mocker.patch.object(
541+
device.interfaces[SnapdAPIClient],
542+
"get",
543+
side_effect=[
544+
{"architecture": "amd64", "store": None},
545+
[{"store": "branded"}],
546+
{
547+
"plugs": [
548+
{
549+
"snap": "checkbox22",
550+
"interface": "content",
551+
"attrs": {"content": "custom-frontend"},
552+
"connections": [{"snap": "checkbox"}],
553+
}
554+
]
555+
},
556+
],
557+
)
558+
mocker.patch(
559+
"toolbox.checkbox.installers.snaps.CheckboxRuntimeHelper"
560+
).return_value.determine_checkbox_runtime.return_value = SnapSpecifier(
561+
name="checkbox22", channel=Channel.from_string("latest/stable")
562+
)
563+
564+
# Two frontends but only one is connected
565+
frontends = [
566+
SnapSpecifier(name="checkbox", channel=Channel.from_string("22/stable")),
567+
SnapSpecifier(
568+
name="checkbox-iiotg", channel=Channel.from_string("22/stable")
569+
),
570+
]
571+
installer = CheckboxSnapsInstaller(
572+
device, TrivialDevice(), frontends, mocker.Mock()
573+
)
574+
575+
result = installer.custom_frontend_interface()
576+
577+
assert result is False
578+
579+
def test_custom_frontend_interface_false_no_plugs(self, mocker):
580+
"""Test returns False when no custom-frontend plugs exist."""
525581
device = TrivialDevice(
526582
interfaces=[
527583
SnapdAPIClient(),
@@ -553,7 +609,7 @@ def test_has_custom_frontend_connected_false(self, mocker):
553609
device, TrivialDevice(), frontends, mocker.Mock()
554610
)
555611

556-
result = installer.has_custom_frontend_connected(frontends[0])
612+
result = installer.custom_frontend_interface()
557613

558614
assert result is False
559615

@@ -634,9 +690,7 @@ def test_restart_new_interface(self, mocker):
634690
device, TrivialDevice(), frontends, mocker.Mock()
635691
)
636692

637-
mocker.patch.object(
638-
installer, "has_custom_frontend_connected", return_value=True
639-
)
693+
mocker.patch.object(installer, "custom_frontend_interface", return_value=True)
640694
mock_configure_agent = mocker.patch.object(installer, "configure_agent")
641695

642696
installer.restart()
@@ -688,9 +742,7 @@ def test_restart_legacy_interface(self, mocker):
688742
device, TrivialDevice(), frontends, mocker.Mock()
689743
)
690744

691-
mocker.patch.object(
692-
installer, "has_custom_frontend_connected", return_value=False
693-
)
745+
mocker.patch.object(installer, "custom_frontend_interface", return_value=False)
694746
mock_configure_agent = mocker.patch.object(installer, "configure_agent")
695747

696748
installer.restart()

0 commit comments

Comments
 (0)