Report self-hosted admin bootstrap path

This commit is contained in:
Michel Paulissen 2026-07-04 12:10:15 +02:00
parent ab84fd382d
commit c836abc405
3 changed files with 168 additions and 4 deletions

View file

@ -1,7 +1,7 @@
{ {
"kind": "disasmer-filtered-public-tree", "kind": "disasmer-filtered-public-tree",
"source_commit": "714ecfe0fc03dec4fa3ca48b8f8760a7aeead338", "source_commit": "41a0af41b5289a1cac004531798bb5269c9bd749",
"release_name": "dryrun-714ecfe0fc03", "release_name": "dryrun-41a0af41b528",
"filtered_out": [ "filtered_out": [
"private/**", "private/**",
"experiments/**", "experiments/**",

View file

@ -2258,8 +2258,9 @@ fn admin_status_report(args: AdminStatusArgs) -> Result<Value> {
} }
fn admin_bootstrap_report(args: AdminBootstrapArgs, cwd: PathBuf) -> Result<Value> { fn admin_bootstrap_report(args: AdminBootstrapArgs, cwd: PathBuf) -> Result<Value> {
let scope = args.scope.clone();
let new_project = args.scope.project.clone(); let new_project = args.scope.project.clone();
project_init_report( let project_init = project_init_report(
ProjectInitArgs { ProjectInitArgs {
scope: args.scope, scope: args.scope,
new_project, new_project,
@ -2267,7 +2268,92 @@ fn admin_bootstrap_report(args: AdminBootstrapArgs, cwd: PathBuf) -> Result<Valu
yes: args.yes, yes: args.yes,
}, },
cwd, cwd,
) )?;
let coordinator = scope
.coordinator
.clone()
.unwrap_or_else(|| "<local-coordinator>".to_owned());
let tenant = scope.tenant.clone();
let project = scope.project.clone();
let user = scope.user.clone();
Ok(json!({
"command": "admin bootstrap",
"mode": if scope.coordinator.is_some() { "public_coordinator_api" } else { "self_hosted_local" },
"tenant": tenant.clone(),
"project": project.clone(),
"user": user,
"coordinator": scope.coordinator,
"private_website_required": false,
"self_hosted_cli_only": true,
"project_config_written": project_init
.get("project_config_written")
.cloned()
.unwrap_or_else(|| json!(false)),
"project_init": project_init,
"admin_surfaces": {
"coordinator": "disasmer-coordinator",
"project": "disasmer project init/status/list/select",
"node": "disasmer node enroll/list/status/revoke",
"process": "disasmer run/process status/process restart/process cancel",
"logs": "disasmer logs",
"artifacts": "disasmer artifact list/download/export",
"quota": "disasmer quota status",
"policy": "disasmer admin status/suspend-tenant",
},
"bootstrap_sequence": [
{
"step": "start_self_hosted_coordinator",
"command": "disasmer-coordinator --listen 127.0.0.1:0",
"private_website_required": false,
},
{
"step": "create_or_link_project",
"command": "disasmer project init --yes",
"completed": true,
"private_website_required": false,
},
{
"step": "create_node_enrollment_grant",
"command": format!(
"disasmer node enroll --coordinator {coordinator} --tenant {} --project-id {}",
tenant, project
),
"private_website_required": false,
},
{
"step": "attach_worker_node",
"command": format!(
"disasmer node attach --coordinator {coordinator} --tenant {} --project-id {} --worker",
tenant, project
),
"private_website_required": false,
},
{
"step": "run_process",
"command": format!(
"disasmer run --coordinator {coordinator} --tenant {} --project-id {}",
tenant, project
),
"private_website_required": false,
},
{
"step": "inspect_status_logs_artifacts",
"commands": [
"disasmer process status",
"disasmer task list",
"disasmer logs",
"disasmer artifact list",
"disasmer quota status",
],
"private_website_required": false,
},
{
"step": "revoke_access",
"command": "disasmer admin revoke-node --node <node-id> --yes",
"private_website_required": false,
}
],
}))
} }
fn admin_suspend_tenant_report(args: AdminSuspendTenantArgs) -> Result<Value> { fn admin_suspend_tenant_report(args: AdminSuspendTenantArgs) -> Result<Value> {
@ -7158,6 +7244,73 @@ mod tests {
} }
} }
#[test]
fn admin_bootstrap_reports_self_hosted_cli_only_path() {
let temp = tempfile::tempdir().unwrap();
let report = admin_bootstrap_report(
AdminBootstrapArgs {
scope: CliScopeArgs {
coordinator: None,
tenant: "team".to_owned(),
project: "self-hosted".to_owned(),
user: "admin".to_owned(),
json: false,
},
name: "Self Hosted".to_owned(),
yes: true,
},
temp.path().to_path_buf(),
)
.unwrap();
assert_eq!(report["command"], "admin bootstrap");
assert_eq!(report["mode"], "self_hosted_local");
assert_eq!(report["private_website_required"], false);
assert_eq!(report["self_hosted_cli_only"], true);
assert_eq!(report["project_config_written"], true);
assert_eq!(report["project_init"]["command"], "project init");
assert_eq!(report["project_init"]["private_website_required"], false);
assert_eq!(
report["project_init"]["project_config"]["project"],
"self-hosted"
);
assert_eq!(
report["admin_surfaces"]["node"],
"disasmer node enroll/list/status/revoke"
);
let steps = report["bootstrap_sequence"].as_array().unwrap();
for expected in [
"start_self_hosted_coordinator",
"create_or_link_project",
"create_node_enrollment_grant",
"attach_worker_node",
"run_process",
"inspect_status_logs_artifacts",
"revoke_access",
] {
assert!(
steps.iter().any(|step| step["step"] == expected),
"missing bootstrap step {expected}"
);
}
assert!(steps.iter().all(|step| {
step.get("private_website_required")
.and_then(Value::as_bool)
.unwrap_or(false)
== false
}));
assert!(steps.iter().any(|step| step
.get("command")
.and_then(Value::as_str)
.unwrap_or("")
.contains("disasmer node enroll")));
assert!(steps.iter().any(|step| step
.get("command")
.and_then(Value::as_str)
.unwrap_or("")
.contains("disasmer admin revoke-node")));
}
#[test] #[test]
fn top_level_help_exposes_primary_workflow_without_auth() { fn top_level_help_exposes_primary_workflow_without_auth() {
let mut command = Cli::command(); let mut command = Cli::command();

View file

@ -107,6 +107,11 @@ expect(
"doctor node readiness criteria", "doctor node readiness criteria",
/`disasmer doctor` reports missing local dependencies[\s\S]*explicit node readiness summary[\s\S]*missing local dependencies[\s\S]*node next actions/ /`disasmer doctor` reports missing local dependencies[\s\S]*explicit node readiness summary[\s\S]*missing local dependencies[\s\S]*node next actions/
); );
expect(
criteria,
"admin bootstrap self-hosted sequence criteria",
/admin bootstrap now reports a CLI-only self-hosted sequence[\s\S]*node enrollment\/attach[\s\S]*quota status[\s\S]*node revoke/
);
expect( expect(
criteria, criteria,
"quota resource-category criteria", "quota resource-category criteria",
@ -213,6 +218,7 @@ for (const [name, pattern] of [
["CLI agent workflow actor coverage", /fn run_with_agent_public_key_sends_attributable_workflow_actor\(\)/], ["CLI agent workflow actor coverage", /fn run_with_agent_public_key_sends_attributable_workflow_actor\(\)/],
["CLI node revoke coverage", /fn node_revoke_reports_scoped_credential_revocation\(\)/], ["CLI node revoke coverage", /fn node_revoke_reports_scoped_credential_revocation\(\)/],
["CLI admin public API coverage", /fn admin_status_and_suspend_use_public_coordinator_api\(\)/], ["CLI admin public API coverage", /fn admin_status_and_suspend_use_public_coordinator_api\(\)/],
["CLI admin bootstrap coverage", /fn admin_bootstrap_reports_self_hosted_cli_only_path\(\)/],
["CLI debug attach coverage", /fn debug_attach_reports_public_authorization\(\)/], ["CLI debug attach coverage", /fn debug_attach_reports_public_authorization\(\)/],
["doctor unchecked reachability coverage", /fn doctor_reports_unchecked_coordinator_reachability_without_config\(\)/], ["doctor unchecked reachability coverage", /fn doctor_reports_unchecked_coordinator_reachability_without_config\(\)/],
["doctor ping reachability coverage", /fn doctor_pings_configured_coordinator\(\)/], ["doctor ping reachability coverage", /fn doctor_pings_configured_coordinator\(\)/],
@ -460,6 +466,11 @@ expect(
"CLI artifact export keeps content out of reports", "CLI artifact export keeps content out of reports",
/fn artifact_stream_summary[\s\S]*content_material_returned_in_report[\s\S]*false/ /fn artifact_stream_summary[\s\S]*content_material_returned_in_report[\s\S]*false/
); );
expect(
cli,
"CLI exposes admin bootstrap self-hosted path",
/fn admin_bootstrap_report[\s\S]*self_hosted_cli_only[\s\S]*bootstrap_sequence[\s\S]*create_node_enrollment_grant[\s\S]*attach_worker_node[\s\S]*inspect_status_logs_artifacts[\s\S]*revoke_access/
);
expect( expect(
coordinator, coordinator,
"coordinator exposes conservative artifact stream content", "coordinator exposes conservative artifact stream content",