Public dry run dryrun-00a47f2c6f21
Source commit: 00a47f2c6f213d43fcef11fdf7926320241da30d Public tree identity: sha256:c41c788b9938cfa66d0c047c16729942bfe6ba4f884acec7d4cd482da762f81a
This commit is contained in:
parent
a40778221a
commit
005b47e4c0
6 changed files with 282 additions and 5 deletions
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"kind": "disasmer-filtered-public-tree",
|
"kind": "disasmer-filtered-public-tree",
|
||||||
"source_commit": "1bb23a07ddd082b491fd20b9be7bd7a6f9c46916",
|
"source_commit": "00a47f2c6f213d43fcef11fdf7926320241da30d",
|
||||||
"release_name": "dryrun-1bb23a07ddd0",
|
"release_name": "dryrun-00a47f2c6f21",
|
||||||
"filtered_out": [
|
"filtered_out": [
|
||||||
"private/**",
|
"private/**",
|
||||||
"experiments/**",
|
"experiments/**",
|
||||||
|
|
|
||||||
|
|
@ -1481,13 +1481,27 @@ fn quota_status_report(args: QuotaStatusArgs, cwd: PathBuf) -> Result<Value> {
|
||||||
|
|
||||||
fn admin_status_report(args: AdminStatusArgs) -> Result<Value> {
|
fn admin_status_report(args: AdminStatusArgs) -> Result<Value> {
|
||||||
if let Some(coordinator) = &args.scope.coordinator {
|
if let Some(coordinator) = &args.scope.coordinator {
|
||||||
|
let tenant = args.scope.tenant.clone();
|
||||||
|
let user = args.scope.user.clone();
|
||||||
let mut session = JsonLineSession::connect(coordinator)?;
|
let mut session = JsonLineSession::connect(coordinator)?;
|
||||||
let response = session.request(json!({ "type": "ping" }))?;
|
let response = session.request(json!({
|
||||||
|
"type": "admin_status",
|
||||||
|
"tenant": tenant,
|
||||||
|
"actor_user": user,
|
||||||
|
}))?;
|
||||||
return Ok(json!({
|
return Ok(json!({
|
||||||
"command": "admin status",
|
"command": "admin status",
|
||||||
"coordinator": coordinator,
|
"coordinator": coordinator,
|
||||||
|
"tenant": tenant,
|
||||||
|
"user": user,
|
||||||
|
"suspended": response
|
||||||
|
.get("suspended")
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or_else(|| json!(false)),
|
||||||
"response": response,
|
"response": response,
|
||||||
"safe_default": "read_only",
|
"safe_default": "read_only",
|
||||||
|
"private_website_required": false,
|
||||||
|
"coordinator_session_requests": session.requests(),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
Ok(json!({
|
Ok(json!({
|
||||||
|
|
@ -1516,24 +1530,34 @@ fn admin_suspend_tenant_report(args: AdminSuspendTenantArgs) -> Result<Value> {
|
||||||
.target_tenant
|
.target_tenant
|
||||||
.unwrap_or_else(|| args.scope.tenant.clone());
|
.unwrap_or_else(|| args.scope.tenant.clone());
|
||||||
if let Some(coordinator) = &args.scope.coordinator {
|
if let Some(coordinator) = &args.scope.coordinator {
|
||||||
|
let actor_tenant = args.scope.tenant.clone();
|
||||||
|
let actor_user = args.scope.user.clone();
|
||||||
let mut session = JsonLineSession::connect(coordinator)?;
|
let mut session = JsonLineSession::connect(coordinator)?;
|
||||||
let response = session.request(json!({
|
let response = session.request(json!({
|
||||||
"type": "suspend_tenant",
|
"type": "suspend_tenant",
|
||||||
"tenant": tenant,
|
"tenant": actor_tenant,
|
||||||
|
"actor_user": actor_user,
|
||||||
|
"target_tenant": tenant,
|
||||||
}))?;
|
}))?;
|
||||||
return Ok(json!({
|
return Ok(json!({
|
||||||
"command": "admin suspend-tenant",
|
"command": "admin suspend-tenant",
|
||||||
"coordinator": coordinator,
|
"coordinator": coordinator,
|
||||||
"requires_confirmation": !args.yes,
|
"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,
|
"response": response,
|
||||||
"coordinator_session_requests": session.requests(),
|
"coordinator_session_requests": session.requests(),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
Ok(json!({
|
Ok(json!({
|
||||||
"command": "admin suspend-tenant",
|
"command": "admin suspend-tenant",
|
||||||
"status": "requires_private_hosted_coordinator",
|
"status": "requires_coordinator",
|
||||||
"requires_confirmation": !args.yes,
|
"requires_confirmation": !args.yes,
|
||||||
"tenant": tenant,
|
"tenant": tenant,
|
||||||
|
"private_website_required": false,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -4501,6 +4525,69 @@ mod tests {
|
||||||
assert_eq!(revoked["node_credentials_separate_from_user_session"], true);
|
assert_eq!(revoked["node_credentials_separate_from_user_session"], true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn admin_status_and_suspend_use_public_coordinator_api() {
|
||||||
|
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
|
||||||
|
let addr = listener.local_addr().unwrap().to_string();
|
||||||
|
let server = std::thread::spawn(move || {
|
||||||
|
for (expected, response) in [
|
||||||
|
(
|
||||||
|
"admin_status",
|
||||||
|
r#"{"type":"admin_status","tenant":"tenant","actor":"admin","suspended":false,"safe_default":"read_only"}"#,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"suspend_tenant",
|
||||||
|
r#"{"type":"tenant_suspended","tenant":"tenant","actor":"admin","policy":{"tenant":"tenant","name":"tenant:suspended","digest":"sha256:suspension"}}"#,
|
||||||
|
),
|
||||||
|
] {
|
||||||
|
let (mut stream, _) = listener.accept().unwrap();
|
||||||
|
let mut reader = BufReader::new(stream.try_clone().unwrap());
|
||||||
|
let mut line = String::new();
|
||||||
|
reader.read_line(&mut line).unwrap();
|
||||||
|
assert!(line.contains(&format!(r#""type":"{expected}""#)));
|
||||||
|
assert!(
|
||||||
|
line.contains(r#""tenant":"admin-tenant""#)
|
||||||
|
|| line.contains(r#""tenant":"tenant""#)
|
||||||
|
);
|
||||||
|
assert!(line.contains(r#""actor_user":"admin""#));
|
||||||
|
if expected == "suspend_tenant" {
|
||||||
|
assert!(line.contains(r#""target_tenant":"tenant""#));
|
||||||
|
}
|
||||||
|
stream.write_all(response.as_bytes()).unwrap();
|
||||||
|
stream.write_all(b"\n").unwrap();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let scope = CliScopeArgs {
|
||||||
|
coordinator: Some(addr),
|
||||||
|
tenant: "admin-tenant".to_owned(),
|
||||||
|
project: "project".to_owned(),
|
||||||
|
user: "admin".to_owned(),
|
||||||
|
json: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
let status = admin_status_report(AdminStatusArgs {
|
||||||
|
scope: scope.clone(),
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
let suspended = admin_suspend_tenant_report(AdminSuspendTenantArgs {
|
||||||
|
scope,
|
||||||
|
target_tenant: Some("tenant".to_owned()),
|
||||||
|
yes: true,
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
server.join().unwrap();
|
||||||
|
|
||||||
|
assert_eq!(status["command"], "admin status");
|
||||||
|
assert_eq!(status["safe_default"], "read_only");
|
||||||
|
assert_eq!(status["private_website_required"], false);
|
||||||
|
assert_eq!(status["suspended"], false);
|
||||||
|
assert_eq!(suspended["command"], "admin suspend-tenant");
|
||||||
|
assert_eq!(suspended["tenant"], "tenant");
|
||||||
|
assert_eq!(suspended["actor_tenant"], "admin-tenant");
|
||||||
|
assert_eq!(suspended["suspended"], true);
|
||||||
|
assert_eq!(suspended["private_website_required"], false);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn human_report_is_text_not_json() {
|
fn human_report_is_text_not_json() {
|
||||||
let report = json!({
|
let report = json!({
|
||||||
|
|
|
||||||
|
|
@ -334,6 +334,34 @@ impl Coordinator {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn suspend_tenant(&mut self, tenant: TenantId, actor: UserId) -> ServicePolicyRecord {
|
||||||
|
self.upsert_tenant(tenant.clone());
|
||||||
|
let name = "tenant:suspended".to_owned();
|
||||||
|
let digest = Digest::from_parts([
|
||||||
|
b"tenant-suspension:v1".as_slice(),
|
||||||
|
tenant.as_str().as_bytes(),
|
||||||
|
actor.as_str().as_bytes(),
|
||||||
|
]);
|
||||||
|
self.upsert_service_policy_record(tenant.clone(), name.clone(), digest);
|
||||||
|
self.service_policy_record(&tenant, &name)
|
||||||
|
.expect("tenant suspension record was just inserted")
|
||||||
|
.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tenant_suspended(&self, tenant: &TenantId) -> bool {
|
||||||
|
self.service_policy_record(tenant, "tenant:suspended")
|
||||||
|
.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ensure_tenant_active(&self, tenant: &TenantId) -> Result<(), CoordinatorError> {
|
||||||
|
if self.tenant_suspended(tenant) {
|
||||||
|
return Err(CoordinatorError::Unauthorized(
|
||||||
|
"tenant is suspended by admin controls".to_owned(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn grant_project_debug(&mut self, tenant: TenantId, project: ProjectId, user: UserId) {
|
pub fn grant_project_debug(&mut self, tenant: TenantId, project: ProjectId, user: UserId) {
|
||||||
self.durable.project_permissions.insert(
|
self.durable.project_permissions.insert(
|
||||||
(tenant.clone(), project.clone(), user.clone()),
|
(tenant.clone(), project.clone(), user.clone()),
|
||||||
|
|
@ -870,6 +898,27 @@ mod tests {
|
||||||
assert_eq!(projects[0].id, ProjectId::from("project-a"));
|
assert_eq!(projects[0].id, ProjectId::from("project-a"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn tenant_suspension_is_durable_admin_policy_state() {
|
||||||
|
let mut store = InMemoryDurableStore::default();
|
||||||
|
let mut coordinator = Coordinator::boot(&store, 1);
|
||||||
|
let record =
|
||||||
|
coordinator.suspend_tenant(TenantId::from("tenant"), UserId::from("admin-user"));
|
||||||
|
|
||||||
|
assert_eq!(record.tenant, TenantId::from("tenant"));
|
||||||
|
assert_eq!(record.name, "tenant:suspended");
|
||||||
|
assert!(coordinator.tenant_suspended(&TenantId::from("tenant")));
|
||||||
|
assert!(coordinator
|
||||||
|
.ensure_tenant_active(&TenantId::from("tenant"))
|
||||||
|
.unwrap_err()
|
||||||
|
.to_string()
|
||||||
|
.contains("suspended"));
|
||||||
|
|
||||||
|
coordinator.persist(&mut store);
|
||||||
|
let restarted = Coordinator::boot(&store, 2);
|
||||||
|
assert!(restarted.tenant_suspended(&TenantId::from("tenant")));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn agent_public_keys_are_project_user_scoped_and_restart_durable() {
|
fn agent_public_keys_are_project_user_scoped_and_restart_durable() {
|
||||||
let mut store = InMemoryDurableStore::default();
|
let mut store = InMemoryDurableStore::default();
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,15 @@ const MAX_TASK_LOG_TAIL_BYTES: usize = 256 * 1024;
|
||||||
#[serde(tag = "type", rename_all = "snake_case")]
|
#[serde(tag = "type", rename_all = "snake_case")]
|
||||||
pub enum CoordinatorRequest {
|
pub enum CoordinatorRequest {
|
||||||
Ping,
|
Ping,
|
||||||
|
AdminStatus {
|
||||||
|
tenant: String,
|
||||||
|
actor_user: String,
|
||||||
|
},
|
||||||
|
SuspendTenant {
|
||||||
|
tenant: String,
|
||||||
|
actor_user: String,
|
||||||
|
target_tenant: String,
|
||||||
|
},
|
||||||
CreateProject {
|
CreateProject {
|
||||||
tenant: String,
|
tenant: String,
|
||||||
actor_user: String,
|
actor_user: String,
|
||||||
|
|
@ -427,6 +436,17 @@ pub enum CoordinatorResponse {
|
||||||
Pong {
|
Pong {
|
||||||
epoch: u64,
|
epoch: u64,
|
||||||
},
|
},
|
||||||
|
AdminStatus {
|
||||||
|
tenant: TenantId,
|
||||||
|
actor: UserId,
|
||||||
|
suspended: bool,
|
||||||
|
safe_default: String,
|
||||||
|
},
|
||||||
|
TenantSuspended {
|
||||||
|
tenant: TenantId,
|
||||||
|
actor: UserId,
|
||||||
|
policy: crate::ServicePolicyRecord,
|
||||||
|
},
|
||||||
ProjectCreated {
|
ProjectCreated {
|
||||||
project: ProjectRecord,
|
project: ProjectRecord,
|
||||||
actor: UserId,
|
actor: UserId,
|
||||||
|
|
@ -670,6 +690,40 @@ impl CoordinatorService {
|
||||||
CoordinatorRequest::Ping => Ok(CoordinatorResponse::Pong {
|
CoordinatorRequest::Ping => Ok(CoordinatorResponse::Pong {
|
||||||
epoch: self.coordinator.coordinator_epoch(),
|
epoch: self.coordinator.coordinator_epoch(),
|
||||||
}),
|
}),
|
||||||
|
CoordinatorRequest::AdminStatus { tenant, actor_user } => {
|
||||||
|
let tenant = TenantId::new(tenant);
|
||||||
|
let actor = UserId::new(actor_user);
|
||||||
|
Ok(CoordinatorResponse::AdminStatus {
|
||||||
|
suspended: self.coordinator.tenant_suspended(&tenant),
|
||||||
|
tenant,
|
||||||
|
actor,
|
||||||
|
safe_default: "read_only".to_owned(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
CoordinatorRequest::SuspendTenant {
|
||||||
|
tenant,
|
||||||
|
actor_user,
|
||||||
|
target_tenant,
|
||||||
|
} => {
|
||||||
|
let actor_tenant = TenantId::new(tenant);
|
||||||
|
let actor = UserId::new(actor_user);
|
||||||
|
let target_tenant = TenantId::new(target_tenant);
|
||||||
|
self.coordinator.upsert_tenant(actor_tenant.clone());
|
||||||
|
self.coordinator.upsert_user(
|
||||||
|
actor_tenant,
|
||||||
|
actor.clone(),
|
||||||
|
CredentialKind::CliDeviceSession,
|
||||||
|
);
|
||||||
|
let policy = self
|
||||||
|
.coordinator
|
||||||
|
.suspend_tenant(target_tenant.clone(), actor.clone());
|
||||||
|
self.coordinator.persist(&mut self.store);
|
||||||
|
Ok(CoordinatorResponse::TenantSuspended {
|
||||||
|
tenant: target_tenant,
|
||||||
|
actor,
|
||||||
|
policy,
|
||||||
|
})
|
||||||
|
}
|
||||||
CoordinatorRequest::CreateProject {
|
CoordinatorRequest::CreateProject {
|
||||||
tenant,
|
tenant,
|
||||||
actor_user,
|
actor_user,
|
||||||
|
|
@ -679,6 +733,7 @@ impl CoordinatorService {
|
||||||
let tenant = TenantId::new(tenant);
|
let tenant = TenantId::new(tenant);
|
||||||
let actor = UserId::new(actor_user);
|
let actor = UserId::new(actor_user);
|
||||||
let project = ProjectId::new(project);
|
let project = ProjectId::new(project);
|
||||||
|
self.coordinator.ensure_tenant_active(&tenant)?;
|
||||||
if let Some(existing) = self.coordinator.project(&project) {
|
if let Some(existing) = self.coordinator.project(&project) {
|
||||||
if existing.tenant != tenant {
|
if existing.tenant != tenant {
|
||||||
return Err(CoordinatorError::Unauthorized(
|
return Err(CoordinatorError::Unauthorized(
|
||||||
|
|
@ -770,6 +825,7 @@ impl CoordinatorService {
|
||||||
let project = ProjectId::new(project);
|
let project = ProjectId::new(project);
|
||||||
let actor = UserId::new(user);
|
let actor = UserId::new(user);
|
||||||
let agent = AgentId::new(agent);
|
let agent = AgentId::new(agent);
|
||||||
|
self.coordinator.ensure_tenant_active(&tenant)?;
|
||||||
if let Some(existing) = self.coordinator.project(&project) {
|
if let Some(existing) = self.coordinator.project(&project) {
|
||||||
if existing.tenant != tenant {
|
if existing.tenant != tenant {
|
||||||
return Err(CoordinatorError::Unauthorized(
|
return Err(CoordinatorError::Unauthorized(
|
||||||
|
|
@ -842,6 +898,7 @@ impl CoordinatorService {
|
||||||
let tenant = TenantId::new(tenant);
|
let tenant = TenantId::new(tenant);
|
||||||
let project = ProjectId::new(project);
|
let project = ProjectId::new(project);
|
||||||
let node = NodeId::new(node);
|
let node = NodeId::new(node);
|
||||||
|
self.coordinator.ensure_tenant_active(&tenant)?;
|
||||||
self.coordinator.upsert_tenant(tenant.clone());
|
self.coordinator.upsert_tenant(tenant.clone());
|
||||||
self.coordinator.upsert_user(
|
self.coordinator.upsert_user(
|
||||||
tenant.clone(),
|
tenant.clone(),
|
||||||
|
|
@ -881,6 +938,7 @@ impl CoordinatorService {
|
||||||
let tenant = TenantId::new(tenant);
|
let tenant = TenantId::new(tenant);
|
||||||
let project = ProjectId::new(project);
|
let project = ProjectId::new(project);
|
||||||
let actor = UserId::new(actor_user);
|
let actor = UserId::new(actor_user);
|
||||||
|
self.coordinator.ensure_tenant_active(&tenant)?;
|
||||||
self.coordinator.upsert_tenant(tenant.clone());
|
self.coordinator.upsert_tenant(tenant.clone());
|
||||||
self.coordinator.upsert_user(
|
self.coordinator.upsert_user(
|
||||||
tenant.clone(),
|
tenant.clone(),
|
||||||
|
|
@ -926,6 +984,7 @@ impl CoordinatorService {
|
||||||
let tenant = TenantId::new(tenant);
|
let tenant = TenantId::new(tenant);
|
||||||
let project = ProjectId::new(project);
|
let project = ProjectId::new(project);
|
||||||
let node = NodeId::new(node);
|
let node = NodeId::new(node);
|
||||||
|
self.coordinator.ensure_tenant_active(&tenant)?;
|
||||||
let grant = self
|
let grant = self
|
||||||
.enrollment_grants
|
.enrollment_grants
|
||||||
.get_mut(&enrollment_grant_key(&tenant, &project, &enrollment_grant))
|
.get_mut(&enrollment_grant_key(&tenant, &project, &enrollment_grant))
|
||||||
|
|
@ -1336,6 +1395,7 @@ impl CoordinatorService {
|
||||||
let tenant = TenantId::new(tenant);
|
let tenant = TenantId::new(tenant);
|
||||||
let project = ProjectId::new(project);
|
let project = ProjectId::new(project);
|
||||||
let process = ProcessId::new(process);
|
let process = ProcessId::new(process);
|
||||||
|
self.coordinator.ensure_tenant_active(&tenant)?;
|
||||||
if let Some(active) = self
|
if let Some(active) = self
|
||||||
.coordinator
|
.coordinator
|
||||||
.active_process_for_project(&tenant, &project)
|
.active_process_for_project(&tenant, &project)
|
||||||
|
|
@ -2464,6 +2524,79 @@ mod tests {
|
||||||
assert!(cross_tenant.to_string().contains("tenant scope"));
|
assert!(cross_tenant.to_string().contains("tenant scope"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn service_reports_and_enforces_public_admin_tenant_suspension() {
|
||||||
|
let mut service = CoordinatorService::new(7);
|
||||||
|
|
||||||
|
let CoordinatorResponse::AdminStatus {
|
||||||
|
tenant,
|
||||||
|
actor,
|
||||||
|
suspended,
|
||||||
|
safe_default,
|
||||||
|
} = service
|
||||||
|
.handle_request(CoordinatorRequest::AdminStatus {
|
||||||
|
tenant: "tenant".to_owned(),
|
||||||
|
actor_user: "admin".to_owned(),
|
||||||
|
})
|
||||||
|
.unwrap()
|
||||||
|
else {
|
||||||
|
panic!("expected admin status");
|
||||||
|
};
|
||||||
|
assert_eq!(tenant, TenantId::from("tenant"));
|
||||||
|
assert_eq!(actor, UserId::from("admin"));
|
||||||
|
assert!(!suspended);
|
||||||
|
assert_eq!(safe_default, "read_only");
|
||||||
|
|
||||||
|
let CoordinatorResponse::TenantSuspended {
|
||||||
|
tenant,
|
||||||
|
actor,
|
||||||
|
policy,
|
||||||
|
} = service
|
||||||
|
.handle_request(CoordinatorRequest::SuspendTenant {
|
||||||
|
tenant: "admin-tenant".to_owned(),
|
||||||
|
actor_user: "admin".to_owned(),
|
||||||
|
target_tenant: "tenant".to_owned(),
|
||||||
|
})
|
||||||
|
.unwrap()
|
||||||
|
else {
|
||||||
|
panic!("expected tenant suspension");
|
||||||
|
};
|
||||||
|
assert_eq!(tenant, TenantId::from("tenant"));
|
||||||
|
assert_eq!(actor, UserId::from("admin"));
|
||||||
|
assert_eq!(policy.name, "tenant:suspended");
|
||||||
|
|
||||||
|
let CoordinatorResponse::AdminStatus { suspended, .. } = service
|
||||||
|
.handle_request(CoordinatorRequest::AdminStatus {
|
||||||
|
tenant: "tenant".to_owned(),
|
||||||
|
actor_user: "admin".to_owned(),
|
||||||
|
})
|
||||||
|
.unwrap()
|
||||||
|
else {
|
||||||
|
panic!("expected suspended admin status");
|
||||||
|
};
|
||||||
|
assert!(suspended);
|
||||||
|
|
||||||
|
let create = service
|
||||||
|
.handle_request(CoordinatorRequest::CreateProject {
|
||||||
|
tenant: "tenant".to_owned(),
|
||||||
|
actor_user: "user".to_owned(),
|
||||||
|
project: "project".to_owned(),
|
||||||
|
name: "Demo".to_owned(),
|
||||||
|
})
|
||||||
|
.unwrap_err();
|
||||||
|
assert!(create.to_string().contains("tenant is suspended"));
|
||||||
|
|
||||||
|
let start = service
|
||||||
|
.handle_request(CoordinatorRequest::StartProcess {
|
||||||
|
tenant: "tenant".to_owned(),
|
||||||
|
project: "project".to_owned(),
|
||||||
|
process: "process".to_owned(),
|
||||||
|
restart: false,
|
||||||
|
})
|
||||||
|
.unwrap_err();
|
||||||
|
assert!(start.to_string().contains("tenant is suspended"));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn service_manages_project_scoped_agent_public_keys() {
|
fn service_manages_project_scoped_agent_public_keys() {
|
||||||
let mut service = CoordinatorService::new(7);
|
let mut service = CoordinatorService::new(7);
|
||||||
|
|
|
||||||
|
|
@ -107,6 +107,7 @@ for (const [name, pattern] of [
|
||||||
["CLI human output coverage", /fn human_report_is_text_not_json\(\)/],
|
["CLI human output coverage", /fn human_report_is_text_not_json\(\)/],
|
||||||
["CLI key lifecycle coverage", /fn key_lifecycle_reports_project_scoped_agent_credentials\(\)/],
|
["CLI key lifecycle coverage", /fn key_lifecycle_reports_project_scoped_agent_credentials\(\)/],
|
||||||
["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\(\)/],
|
||||||
["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\(\)/],
|
||||||
["project local config coverage", /fn project_init_select_and_status_use_local_project_config\(\)/],
|
["project local config coverage", /fn project_init_select_and_status_use_local_project_config\(\)/],
|
||||||
|
|
@ -143,6 +144,11 @@ expect(
|
||||||
"coordinator node revoke coverage",
|
"coordinator node revoke coverage",
|
||||||
/fn service_revokes_node_credentials_and_live_descriptors\(\)/
|
/fn service_revokes_node_credentials_and_live_descriptors\(\)/
|
||||||
);
|
);
|
||||||
|
expect(
|
||||||
|
coordinator,
|
||||||
|
"coordinator public admin suspension coverage",
|
||||||
|
/fn service_reports_and_enforces_public_admin_tenant_suspension\(\)/
|
||||||
|
);
|
||||||
|
|
||||||
for (const [name, pattern] of [
|
for (const [name, pattern] of [
|
||||||
["agent --json flag", /struct AgentEnrollArgs[\s\S]*#\[arg\(long\)\]\s*json: bool/],
|
["agent --json flag", /struct AgentEnrollArgs[\s\S]*#\[arg\(long\)\]\s*json: bool/],
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,8 @@ function assertNoUserSessionCredential(surface, text) {
|
||||||
|
|
||||||
const coordinatorService = read("crates/disasmer-coordinator/src/service.rs");
|
const coordinatorService = read("crates/disasmer-coordinator/src/service.rs");
|
||||||
for (const variant of [
|
for (const variant of [
|
||||||
|
"AdminStatus",
|
||||||
|
"SuspendTenant",
|
||||||
"AttachNode",
|
"AttachNode",
|
||||||
"RegisterAgentPublicKey",
|
"RegisterAgentPublicKey",
|
||||||
"ListAgentPublicKeys",
|
"ListAgentPublicKeys",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue