milestone 20: declarative configuration for iac

This commit is contained in:
2026-08-11 23:31:40 +02:00
parent 2f29121e27
commit d76afc147a
74 changed files with 6722 additions and 1949 deletions
+20 -135
View File
@@ -22,7 +22,6 @@ const build_options = @import("build_options");
const Writer = std.Io.Writer;
const cli = @import("../cli.zig");
const bootstrap = @import("../config/bootstrap.zig");
const config_export = @import("../config/export.zig");
const import = @import("../config/import.zig");
const model = @import("../config/model.zig");
@@ -127,7 +126,7 @@ fn openMigrated(f: *const Fixture, name: []const u8) !Data {
return .{ .dir = dir, .database = database };
}
fn importInto(f: *const Fixture, data: *Data, file: []const u8, force: bool) !void {
fn importInto(f: *const Fixture, data: *Data, file: []const u8, allow_delete: bool) !void {
var diags: validate.Diagnostics = .init(testing.allocator);
defer diags.deinit();
return import.importFile(
@@ -136,7 +135,7 @@ fn importInto(f: *const Fixture, data: *Data, file: []const u8, force: bool) !vo
&data.database,
f.tmp.dir,
file,
.{ .force = force },
.{ .allow_delete = allow_delete },
&diags,
);
}
@@ -655,7 +654,7 @@ test "S7 case 10: a config.db stamped one version ahead is refused and left alon
}
// ---------------------------------------------------------------------------
// case 11-19: export, import and bootstrap on real files
// case 11-19: export and import on real files
// ---------------------------------------------------------------------------
test "S7 case 11: an exported file is mode 0600 and starts with the header comment" {
@@ -704,7 +703,7 @@ test "S7 case 12: export, import and export again are byte-identical files" {
try testing.expectEqualStrings(a, b);
}
test "S7 case 13: import refuses a configured database unless --force is given" {
test "S7 case 13: import refuses a diff that deletes rows unless --allow-delete is given" {
if (!build_options.integration) return error.SkipZigTest;
var f: Fixture = .init();
@@ -716,7 +715,10 @@ test "S7 case 13: import refuses a configured database unless --force is given"
defer data.deinit();
try importInto(&f, &data, "first.zon", false);
try testing.expectError(error.DatabaseNotEmpty, importInto(&f, &data, "second.zon", false));
// The two files name different upstream urls, and a url is the upstream's
// identity: applying the second deletes the first's row, which is what the
// gate exists to stop.
try testing.expectError(error.DestructiveImport, importInto(&f, &data, "second.zon", false));
try testing.expectEqual(
@as(i64, 1),
try data.database.queryInt(
@@ -753,14 +755,15 @@ test "S7 case 14: an invalid import reports every problem and writes nothing" {
&data.database,
f.tmp.dir,
"config.zon",
.{ .force = false },
.{ .allow_delete = false },
&diags,
)) |_| {
return error.TestUnexpectedResult;
} else |_| {}
try testing.expectEqual(@as(usize, 2), diags.problems.items.len);
try testing.expect(try import.isEmpty(&data.database));
try testing.expectEqual(@as(i64, 0), try data.database.queryInt("SELECT count(*) FROM upstreams"));
try testing.expectEqual(@as(i64, 0), try data.database.queryInt("SELECT count(*) FROM settings"));
// Nothing beyond the database and its sidecars was created.
var dir = try f.tmp.dir.openDir(io, "data", .{ .iterate = true });
@@ -771,129 +774,6 @@ test "S7 case 14: an invalid import reports every problem and writes nothing" {
}
}
test "S7 case 15: bootstrap with no configuration file leaves the database empty" {
if (!build_options.integration) return error.SkipZigTest;
var f: Fixture = .init();
defer f.deinit();
var data = try openMigrated(&f, "data");
defer data.deinit();
var diags: validate.Diagnostics = .init(testing.allocator);
defer diags.deinit();
const outcome = try bootstrap.bootstrap(
io,
testing.allocator,
&data.database,
f.tmp.dir,
"config.zon",
&diags,
);
try testing.expectEqual(bootstrap.Outcome.no_config_file, outcome);
try testing.expect(try import.isEmpty(&data.database));
}
test "S7 case 16: bootstrap seeds an empty database from the configuration file" {
if (!build_options.integration) return error.SkipZigTest;
var f: Fixture = .init();
defer f.deinit();
try f.write("config.zon", rich_config);
var data = try openMigrated(&f, "data");
defer data.deinit();
var diags: validate.Diagnostics = .init(testing.allocator);
defer diags.deinit();
const outcome = try bootstrap.bootstrap(
io,
testing.allocator,
&data.database,
f.tmp.dir,
"config.zon",
&diags,
);
try testing.expectEqual(bootstrap.Outcome.seeded, outcome);
try testing.expectEqual(@as(i64, 2), try data.database.queryInt("SELECT count(*) FROM groups"));
try testing.expectEqual(@as(i64, 2), try data.database.queryInt("SELECT count(*) FROM upstreams"));
try testing.expectEqual(
@as(i64, 1),
try data.database.queryInt("SELECT count(*) FROM clients WHERE ip = 'fd00::1'"),
);
try testing.expectEqual(
@as(i64, 5353),
try data.database.queryInt("SELECT CAST(value AS INTEGER) FROM settings WHERE key = 'dns.port'"),
);
}
test "S7 case 17: bootstrap on a configured database never reads the file" {
if (!build_options.integration) return error.SkipZigTest;
var f: Fixture = .init();
defer f.deinit();
try f.write("seed.zon", minimal_config);
var data = try openMigrated(&f, "data");
defer data.deinit();
try importInto(&f, &data, "seed.zon", false);
// Unparseable on purpose: the call can only succeed if the file is never
// opened.
try f.write("config.zon", broken_zon);
var diags: validate.Diagnostics = .init(testing.allocator);
defer diags.deinit();
const outcome = try bootstrap.bootstrap(
io,
testing.allocator,
&data.database,
f.tmp.dir,
"config.zon",
&diags,
);
try testing.expectEqual(bootstrap.Outcome.db_already_configured, outcome);
try testing.expectEqual(@as(usize, 0), diags.problems.items.len);
try testing.expectEqual(
@as(i64, 1),
try data.database.queryInt(
"SELECT count(*) FROM upstreams WHERE url = 'https://dns.example/dns-query'",
),
);
}
test "S7 case 18: bootstrap with an invalid configuration file fails and writes nothing" {
if (!build_options.integration) return error.SkipZigTest;
var f: Fixture = .init();
defer f.deinit();
try f.write("config.zon", two_problem_config);
var data = try openMigrated(&f, "data");
defer data.deinit();
var diags: validate.Diagnostics = .init(testing.allocator);
defer diags.deinit();
if (bootstrap.bootstrap(
io,
testing.allocator,
&data.database,
f.tmp.dir,
"config.zon",
&diags,
)) |outcome| {
std.debug.print("bootstrap unexpectedly returned .{s}\n", .{@tagName(outcome)});
return error.TestUnexpectedResult;
} else |_| {}
try testing.expectEqual(@as(usize, 2), diags.problems.items.len);
try testing.expect(try import.isEmpty(&data.database));
}
test "S7 case 19: writeToFile replaces an existing file and restores mode 0600" {
if (!build_options.integration) return error.SkipZigTest;
@@ -988,11 +868,13 @@ test "S7 case 21: runCheck passes a seeded database and reports two stored probl
try importInto(&f, &data, "config.zon", false);
}
{
// `applyToDb` rather than an import: the validator would refuse this
// `apply` rather than an import: the validator would refuse this
// configuration, and the case needs the problems to reach the database.
var data = try openMigrated(&f, "bad");
defer data.deinit();
try import.applyToDb(io, testing.allocator, &data.database, two_problem_model, 42, .{});
var diags: validate.Diagnostics = .init(testing.allocator);
defer diags.deinit();
try import.apply(io, testing.allocator, &data.database, two_problem_model, 42, .{}, &diags);
}
{
@@ -1045,13 +927,16 @@ test "S7 case 22: runCheck probes a real upstream and prints an OK line" {
const code = cli.runCheck(
captured.runner(),
.{ .paths = .{ .config = config_path }, .config_explicit = true },
.{ .config = config_path },
true,
);
try testing.expectEqual(cli.exit_ok, code);
// Milestone 13 changed the probe line to the redacted `OK upstreams[i]`
// form; this expectation went stale unnoticed because nothing ran -Dlive
// between then and milestone 20.
try testing.expect(std.mem.count(
u8,
captured.out.written(),
"OK https://cloudflare-dns.com/dns-query\n",
"OK upstreams[0] https://cloudflare-dns.com\n",
) == 1);
}