Add validate_compose_external_state() for env_file/external-network checks

Separate from load_compose_file() (which stays pure YAML validation with no
host-state dependency): checks every env_file exists as a readable regular
file, and every network marked external: true already exists in the real
persistent.yaml. Fail-fast, same convention as load_compose_file()'s own
cross-validation. Bind-mount host directories are deliberately not checked
here, since resolve_volume_mount() already auto-creates a missing one.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gv3s5jckJKzh6JkMoi2Akz
This commit is contained in:
2026-09-07 09:39:12 +00:00
parent 410faff79c
commit 4ffc68a00e
3 changed files with 108 additions and 0 deletions
+50
View File
@@ -528,3 +528,53 @@ TEST_CASE("compose file: service volumes -- bind mounts (absolute-resolved, ro),
" - ./scripts:relative/path\n");
CHECK_FALSE(load_compose_file(relative_target).has_value());
}
TEST_CASE("compose file: validate_compose_external_state -- env_file existence, external network existence",
"[unit]") {
ScratchXdgDirs scratch;
auto path = write_compose(scratch.path(),
"services:\n"
" web:\n"
" image: busybox:latest\n"
" env_file: ./web.env\n"
"networks:\n"
" net-managed:\n"
" internal: false\n"
" net-preexisting:\n"
" external: true\n");
auto loaded = load_compose_file(path);
REQUIRE(loaded.has_value());
// env_file doesn't exist yet, and the external network isn't declared
// anywhere in persistent_config -- both must fail, one at a time
// (fail-fast, same convention load_compose_file()'s own cross-validation
// already uses).
AppConfig empty_persistent;
CHECK_FALSE(validate_compose_external_state(*loaded, empty_persistent));
// Create the env_file -- still fails, since the external network still
// isn't declared.
{
std::ofstream env_file(scratch.path() / "web.env");
env_file << "FOO=bar\n";
}
CHECK_FALSE(validate_compose_external_state(*loaded, empty_persistent));
// Declare the external network -- now both checks pass. A managed
// (non-external) network, net-managed, is never checked against
// persistent_config at all, so its absence there doesn't matter.
AppConfig with_network;
with_network.networks.push_back({"net-preexisting", NetworkKind::intern, "10.169.10.0/24", false, "", true});
CHECK(validate_compose_external_state(*loaded, with_network));
// A directory in place of the env_file still fails -- not a regular file.
auto dir_env_file = write_compose(scratch.path(), "services:\n"
" web:\n"
" image: busybox:latest\n"
" env_file: ./a-directory\n");
auto loaded_dir = load_compose_file(dir_env_file);
REQUIRE(loaded_dir.has_value());
std::filesystem::create_directory(scratch.path() / "a-directory");
CHECK_FALSE(validate_compose_external_state(*loaded_dir, AppConfig{}));
}