fix(macos-container): start builder with dns
This commit is contained in:
@@ -45,6 +45,7 @@ def build_image(ref: str, context: str, *, dockerfile: str = "") -> None:
|
|||||||
f"building image {ref} from {context} with Apple Container "
|
f"building image {ref} from {context} with Apple Container "
|
||||||
"(layer cache keeps repeat builds fast)"
|
"(layer cache keeps repeat builds fast)"
|
||||||
)
|
)
|
||||||
|
_ensure_builder_dns()
|
||||||
args = [_CONTAINER, "build", "-t", ref, "--dns", dns_server()]
|
args = [_CONTAINER, "build", "-t", ref, "--dns", dns_server()]
|
||||||
if dockerfile:
|
if dockerfile:
|
||||||
args.extend(["-f", dockerfile])
|
args.extend(["-f", dockerfile])
|
||||||
@@ -52,6 +53,54 @@ def build_image(ref: str, context: str, *, dockerfile: str = "") -> None:
|
|||||||
subprocess.run(args, check=True)
|
subprocess.run(args, check=True)
|
||||||
|
|
||||||
|
|
||||||
|
def _ensure_builder_dns() -> None:
|
||||||
|
dns = dns_server()
|
||||||
|
if _builder_has_dns(dns):
|
||||||
|
return
|
||||||
|
subprocess.run(
|
||||||
|
[_CONTAINER, "builder", "stop"],
|
||||||
|
stdout=subprocess.DEVNULL,
|
||||||
|
stderr=subprocess.DEVNULL,
|
||||||
|
check=False,
|
||||||
|
)
|
||||||
|
subprocess.run(
|
||||||
|
[_CONTAINER, "builder", "start", "--dns", dns],
|
||||||
|
check=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _builder_has_dns(dns: str) -> bool:
|
||||||
|
result = subprocess.run(
|
||||||
|
[_CONTAINER, "builder", "status", "--format", "json"],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
check=False,
|
||||||
|
)
|
||||||
|
if result.returncode != 0:
|
||||||
|
return False
|
||||||
|
try:
|
||||||
|
data = json.loads(result.stdout or "[]")
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
return False
|
||||||
|
entries = data if isinstance(data, list) else [data]
|
||||||
|
for entry in entries:
|
||||||
|
if not isinstance(entry, dict):
|
||||||
|
continue
|
||||||
|
status = entry.get("status")
|
||||||
|
if isinstance(status, dict) and status.get("state") != "running":
|
||||||
|
continue
|
||||||
|
config = entry.get("configuration")
|
||||||
|
config_dns = config.get("dns") if isinstance(config, dict) else None
|
||||||
|
nameservers = (
|
||||||
|
config_dns.get("nameservers")
|
||||||
|
if isinstance(config_dns, dict)
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
if isinstance(nameservers, list) and dns in nameservers:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def image_exists(ref: str) -> bool:
|
def image_exists(ref: str) -> bool:
|
||||||
return _silent_run([_CONTAINER, "image", "inspect", ref]) == 0
|
return _silent_run([_CONTAINER, "image", "inspect", ref]) == 0
|
||||||
|
|
||||||
|
|||||||
@@ -170,7 +170,10 @@ delivery design lands.
|
|||||||
- Integration tests run on macOS hosts with Apple Container installed
|
- Integration tests run on macOS hosts with Apple Container installed
|
||||||
and verify that egress cannot bypass the sidecar. They also preflight
|
and verify that egress cannot bypass the sidecar. They also preflight
|
||||||
Apple Container BuildKit DNS because image builds must resolve
|
Apple Container BuildKit DNS because image builds must resolve
|
||||||
package mirrors before a launch smoke can be meaningful.
|
package mirrors before a launch smoke can be meaningful. The backend
|
||||||
|
starts/restarts the Apple Container builder with the configured DNS
|
||||||
|
server before image builds so BuildKit `RUN` steps inherit a working
|
||||||
|
resolver.
|
||||||
|
|
||||||
## References
|
## References
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,16 @@ class TestMacosContainerAvailability(unittest.TestCase):
|
|||||||
|
|
||||||
class TestMacosContainerCommands(unittest.TestCase):
|
class TestMacosContainerCommands(unittest.TestCase):
|
||||||
def test_build_image(self):
|
def test_build_image(self):
|
||||||
with patch.object(util.subprocess, "run") as run, \
|
status = util.subprocess.CompletedProcess(
|
||||||
|
args=[],
|
||||||
|
returncode=0,
|
||||||
|
stdout=(
|
||||||
|
'[{"status":{"state":"running"},'
|
||||||
|
'"configuration":{"dns":{"nameservers":["9.9.9.9"]}}}]'
|
||||||
|
),
|
||||||
|
stderr="",
|
||||||
|
)
|
||||||
|
with patch.object(util.subprocess, "run", return_value=status) as run, \
|
||||||
patch.object(util.os, "environ", {
|
patch.object(util.os, "environ", {
|
||||||
"BOT_BOTTLE_MACOS_CONTAINER_DNS": "9.9.9.9",
|
"BOT_BOTTLE_MACOS_CONTAINER_DNS": "9.9.9.9",
|
||||||
}):
|
}):
|
||||||
@@ -39,9 +48,38 @@ class TestMacosContainerCommands(unittest.TestCase):
|
|||||||
"container", "build", "-t", "bot-bottle-agent:latest",
|
"container", "build", "-t", "bot-bottle-agent:latest",
|
||||||
"--dns", "9.9.9.9", "-f", "/repo/Dockerfile", "/repo",
|
"--dns", "9.9.9.9", "-f", "/repo/Dockerfile", "/repo",
|
||||||
],
|
],
|
||||||
run.call_args.args[0],
|
run.call_args_list[-1].args[0],
|
||||||
|
)
|
||||||
|
self.assertTrue(run.call_args_list[-1].kwargs["check"])
|
||||||
|
|
||||||
|
def test_build_image_restarts_builder_when_dns_mismatches(self):
|
||||||
|
status = util.subprocess.CompletedProcess(
|
||||||
|
args=[],
|
||||||
|
returncode=0,
|
||||||
|
stdout=(
|
||||||
|
'[{"status":{"state":"running"},'
|
||||||
|
'"configuration":{"dns":{"nameservers":[]}}}]'
|
||||||
|
),
|
||||||
|
stderr="",
|
||||||
|
)
|
||||||
|
with patch.object(util.subprocess, "run", return_value=status) as run, \
|
||||||
|
patch.object(util.os, "environ", {
|
||||||
|
"BOT_BOTTLE_MACOS_CONTAINER_DNS": "9.9.9.9",
|
||||||
|
}):
|
||||||
|
util.build_image("bot-bottle-agent:latest", "/repo")
|
||||||
|
calls = [c.args[0] for c in run.call_args_list]
|
||||||
|
self.assertIn(["container", "builder", "stop"], calls)
|
||||||
|
self.assertIn(
|
||||||
|
["container", "builder", "start", "--dns", "9.9.9.9"],
|
||||||
|
calls,
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
[
|
||||||
|
"container", "build", "-t", "bot-bottle-agent:latest",
|
||||||
|
"--dns", "9.9.9.9", "/repo",
|
||||||
|
],
|
||||||
|
calls[-1],
|
||||||
)
|
)
|
||||||
self.assertTrue(run.call_args.kwargs["check"])
|
|
||||||
|
|
||||||
def test_container_exists_parses_quiet_list(self):
|
def test_container_exists_parses_quiet_list(self):
|
||||||
completed = util.subprocess.CompletedProcess(
|
completed = util.subprocess.CompletedProcess(
|
||||||
|
|||||||
Reference in New Issue
Block a user