clusterflux-public/crates/clusterflux-cli/src/admin.rs
Clusterflux release eb1077f380 Public release release-09ca780bd67a
Source commit: 09ca780bd67a00467e78139cf38466b8201b66ca

Public tree identity: sha256:2f744c260b5fc2cf074ad9129f97f87f03714902e5b9fe174128afdc342ec2d0
2026-07-19 18:38:40 +02:00

236 lines
8.6 KiB
Rust

use std::path::PathBuf;
use anyhow::Result;
use clusterflux_core::admin_request_proof;
use serde_json::{json, Value};
use crate::client::JsonLineSession;
use crate::project::project_init_report;
use crate::tools::{command_nonce, unix_timestamp_seconds};
use crate::{
confirmation_required_report, AdminBootstrapArgs, AdminStatusArgs, AdminSuspendTenantArgs,
ProjectInitArgs,
};
pub(crate) fn admin_status_report(args: AdminStatusArgs) -> Result<Value> {
if let Some(coordinator) = &args.scope.coordinator {
let tenant = args.scope.tenant.clone();
let user = args.scope.user.clone();
let admin_token = admin_token_for_request(args.admin_token.as_deref())?;
let admin_nonce = command_nonce("admin-status");
let issued_at_epoch_seconds = unix_timestamp_seconds();
let admin_proof = admin_request_proof(
&admin_token,
"admin_status",
&tenant,
&user,
&tenant,
&admin_nonce,
issued_at_epoch_seconds,
);
let mut session = JsonLineSession::connect(coordinator)?;
let response = session.request(json!({
"type": "admin_status",
"tenant": tenant,
"actor_user": user,
"admin_proof": admin_proof,
"admin_nonce": admin_nonce,
"issued_at_epoch_seconds": issued_at_epoch_seconds,
}))?;
return Ok(json!({
"command": "admin status",
"coordinator": coordinator,
"tenant": tenant,
"user": user,
"suspended": response
.get("suspended")
.cloned()
.unwrap_or(json!(false)),
"response": response,
"safe_default": "read_only",
"private_website_required": false,
"coordinator_session_requests": session.requests(),
}));
}
Ok(json!({
"command": "admin status",
"mode": "self_hosted_local",
"safe_default": "read_only",
"private_website_required": false,
}))
}
pub(crate) fn admin_bootstrap_report(args: AdminBootstrapArgs, cwd: PathBuf) -> Result<Value> {
let scope = args.scope.clone();
let new_project = args.scope.project.clone();
let project_init = project_init_report(
ProjectInitArgs {
scope: args.scope,
new_project,
name: args.name,
yes: args.yes,
},
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(json!(false)),
"project_init": project_init,
"admin_surfaces": {
"coordinator": "clusterflux-coordinator",
"project": "clusterflux project init/status/list/select",
"node": "clusterflux node enroll/list/status/revoke",
"process": "clusterflux run/process status/process restart/process cancel",
"logs": "clusterflux logs",
"artifacts": "clusterflux artifact list/download/export",
"quota": "clusterflux quota status",
"policy": "clusterflux admin status/suspend-tenant",
},
"bootstrap_sequence": [
{
"step": "start_self_hosted_coordinator",
"command": "clusterflux-coordinator --listen 127.0.0.1:0",
"private_website_required": false,
},
{
"step": "create_or_link_project",
"command": "clusterflux project init --yes",
"completed": true,
"private_website_required": false,
},
{
"step": "create_node_enrollment_grant",
"command": format!(
"clusterflux node enroll --coordinator {coordinator} --tenant {} --project-id {}",
tenant, project
),
"private_website_required": false,
},
{
"step": "attach_worker_node",
"command": format!(
"clusterflux node attach --coordinator {coordinator} --tenant {} --project-id {} --worker",
tenant, project
),
"private_website_required": false,
},
{
"step": "run_process",
"command": format!(
"clusterflux run --coordinator {coordinator} --tenant {} --project-id {}",
tenant, project
),
"private_website_required": false,
},
{
"step": "inspect_status_logs_artifacts",
"commands": [
"clusterflux process status",
"clusterflux task list",
"clusterflux logs",
"clusterflux artifact list",
"clusterflux quota status",
],
"private_website_required": false,
},
{
"step": "revoke_access",
"command": "clusterflux admin revoke-node --node <node-id> --yes",
"private_website_required": false,
}
],
}))
}
pub(crate) fn admin_suspend_tenant_report(args: AdminSuspendTenantArgs) -> Result<Value> {
let tenant = args
.target_tenant
.unwrap_or_else(|| args.scope.tenant.clone());
if !args.yes {
return Ok(confirmation_required_report(
"admin suspend-tenant",
"suspend_tenant",
json!({
"coordinator": args.scope.coordinator,
"actor_tenant": args.scope.tenant,
"actor_user": args.scope.user,
"target_tenant": tenant,
}),
"clusterflux admin suspend-tenant --yes".to_owned(),
));
}
if let Some(coordinator) = &args.scope.coordinator {
let actor_tenant = args.scope.tenant.clone();
let actor_user = args.scope.user.clone();
let admin_token = admin_token_for_request(args.admin_token.as_deref())?;
let admin_nonce = command_nonce("admin-suspend-tenant");
let issued_at_epoch_seconds = unix_timestamp_seconds();
let admin_proof = admin_request_proof(
&admin_token,
"suspend_tenant",
&actor_tenant,
&actor_user,
&tenant,
&admin_nonce,
issued_at_epoch_seconds,
);
let mut session = JsonLineSession::connect(coordinator)?;
let response = session.request(json!({
"type": "suspend_tenant",
"tenant": actor_tenant,
"actor_user": actor_user,
"target_tenant": tenant,
"admin_proof": admin_proof,
"admin_nonce": admin_nonce,
"issued_at_epoch_seconds": issued_at_epoch_seconds,
}))?;
return Ok(json!({
"command": "admin suspend-tenant",
"coordinator": coordinator,
"requires_confirmation": !args.yes,
"tenant": tenant,
"actor_tenant": actor_tenant,
"actor_user": actor_user,
"suspended": response.get("type").and_then(Value::as_str) == Some("tenant_suspended"),
"private_website_required": false,
"response": response,
"coordinator_session_requests": session.requests(),
}));
}
Ok(json!({
"command": "admin suspend-tenant",
"status": "requires_coordinator",
"requires_confirmation": !args.yes,
"tenant": tenant,
"private_website_required": false,
}))
}
fn admin_token_for_request(explicit: Option<&str>) -> Result<String> {
explicit
.map(str::to_owned)
.or_else(|| std::env::var("CLUSTERFLUX_ADMIN_TOKEN").ok())
.filter(|token| !token.trim().is_empty())
.ok_or_else(|| {
anyhow::anyhow!(
"admin command requires --admin-token or CLUSTERFLUX_ADMIN_TOKEN for coordinator requests"
)
})
}