Publish Clusterflux 53f9eb8 filtered source
This commit is contained in:
commit
f28e40d889
226 changed files with 86596 additions and 0 deletions
381
crates/clusterflux-cli/src/project.rs
Normal file
381
crates/clusterflux-cli/src/project.rs
Normal file
|
|
@ -0,0 +1,381 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::Result;
|
||||
use serde_json::{json, Value};
|
||||
|
||||
use crate::client::{
|
||||
authenticated_or_local_trusted_request, list_attached_nodes_if_available_with_session,
|
||||
list_task_events_if_available_with_session, stored_session_for_coordinator, JsonLineSession,
|
||||
};
|
||||
use crate::config::{
|
||||
effective_project_scope, effective_scope_value, project_config_file, read_cli_session,
|
||||
read_project_config, write_project_config, ProjectConfig, StoredCliSession,
|
||||
};
|
||||
use crate::process::process_list_report_with_session;
|
||||
use crate::process_events::project_quota_posture;
|
||||
use crate::{
|
||||
bundle_inspection, discovered_environment_names, BundleInspectArgs, ProcessListArgs,
|
||||
ProjectInitArgs, ProjectListArgs, ProjectSelectArgs, ProjectStatusArgs,
|
||||
};
|
||||
|
||||
pub(crate) fn project_init_report(args: ProjectInitArgs, cwd: PathBuf) -> Result<Value> {
|
||||
let stored_session = read_cli_session(&cwd)?;
|
||||
let tenant = session_or_effective_scope_value(
|
||||
stored_session.as_ref(),
|
||||
&args.scope.tenant,
|
||||
|session| session.tenant.as_str(),
|
||||
"tenant",
|
||||
);
|
||||
let project = args.new_project.clone();
|
||||
let user = session_or_effective_scope_value(
|
||||
stored_session.as_ref(),
|
||||
&args.scope.user,
|
||||
|session| session.user.as_str(),
|
||||
"user",
|
||||
);
|
||||
let coordinator = args.scope.coordinator.clone().or_else(|| {
|
||||
stored_session
|
||||
.as_ref()
|
||||
.map(|session| session.coordinator.clone())
|
||||
});
|
||||
let name = args.name.clone();
|
||||
let config = ProjectConfig {
|
||||
tenant: tenant.clone(),
|
||||
project: project.clone(),
|
||||
user: user.clone(),
|
||||
coordinator: coordinator.clone(),
|
||||
};
|
||||
let config_file = project_config_file(&cwd);
|
||||
if config_file.exists() && !args.yes {
|
||||
anyhow::bail!(
|
||||
"{} already exists; rerun with --yes to update the project link",
|
||||
config_file.display()
|
||||
);
|
||||
}
|
||||
let mut coordinator_session_requests = 0;
|
||||
let coordinator_response = if let Some(coordinator) = &coordinator {
|
||||
let mut session = JsonLineSession::connect(coordinator)?;
|
||||
let request = authenticated_or_local_trusted_request(
|
||||
coordinator,
|
||||
stored_session.as_ref(),
|
||||
json!({
|
||||
"type": "create_project",
|
||||
"project": project,
|
||||
"name": name,
|
||||
}),
|
||||
json!({
|
||||
"type": "create_project",
|
||||
"tenant": tenant,
|
||||
"actor_user": user,
|
||||
"project": project,
|
||||
"name": name,
|
||||
}),
|
||||
)?;
|
||||
let response = session.request(request)?;
|
||||
coordinator_session_requests = session.requests();
|
||||
Some(response)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
write_project_config(&cwd, &config)?;
|
||||
let created_or_linked_project = coordinator_response
|
||||
.as_ref()
|
||||
.and_then(|response| response.get("project"))
|
||||
.cloned()
|
||||
.unwrap_or_else(|| {
|
||||
json!({
|
||||
"id": config.project.clone(),
|
||||
"tenant": config.tenant.clone(),
|
||||
"name": args.name.clone(),
|
||||
})
|
||||
});
|
||||
Ok(json!({
|
||||
"command": "project init",
|
||||
"source": if coordinator.is_some() { "public_coordinator_api" } else { "local_project_config" },
|
||||
"private_website_required": false,
|
||||
"project_config_written": true,
|
||||
"project_config_write_after_coordinator_acceptance": coordinator.is_some(),
|
||||
"coordinator_create_before_local_write": coordinator.is_some(),
|
||||
"coordinator_session_requests": coordinator_session_requests,
|
||||
"created_or_linked_project": created_or_linked_project,
|
||||
"current_directory_link": {
|
||||
"cwd": cwd,
|
||||
"config_file": config_file,
|
||||
"config_format": "clusterflux_project_config_v1",
|
||||
"links_current_directory": true,
|
||||
"writes_current_directory_only": true,
|
||||
"private_website_required": false,
|
||||
},
|
||||
"safe_defaults": {
|
||||
"tenant": config.tenant.clone(),
|
||||
"project": config.project.clone(),
|
||||
"user": config.user.clone(),
|
||||
"coordinator": config.coordinator.clone(),
|
||||
"project_name": args.name.clone(),
|
||||
"default_project_id_used": args.new_project == "project",
|
||||
"default_project_name_used": args.name == "Clusterflux Project",
|
||||
"browser_interaction_required": false,
|
||||
"private_website_required": false,
|
||||
},
|
||||
"project_config": config,
|
||||
"config_file": project_config_file(&cwd),
|
||||
"coordinator_response": coordinator_response,
|
||||
}))
|
||||
}
|
||||
|
||||
pub(crate) fn project_status_report(args: ProjectStatusArgs, cwd: PathBuf) -> Result<Value> {
|
||||
let config = read_project_config(&cwd)?;
|
||||
let stored_session = read_cli_session(&cwd)?;
|
||||
let mut effective_scope = effective_project_scope(&args.scope, config.as_ref());
|
||||
if effective_scope.coordinator.is_none() {
|
||||
effective_scope.coordinator = stored_session
|
||||
.as_ref()
|
||||
.filter(|session| session.session_secret.is_some())
|
||||
.map(|session| session.coordinator.clone());
|
||||
}
|
||||
if let (Some(config), Some(session)) = (config.as_ref(), stored_session.as_ref()) {
|
||||
let same_coordinator = effective_scope
|
||||
.coordinator
|
||||
.as_deref()
|
||||
.is_some_and(|coordinator| {
|
||||
crate::client::control_endpoint_identity(coordinator).ok()
|
||||
== crate::client::control_endpoint_identity(&session.coordinator).ok()
|
||||
});
|
||||
if same_coordinator
|
||||
&& (session.tenant != config.tenant
|
||||
|| session.project != config.project
|
||||
|| session.user != config.user)
|
||||
{
|
||||
anyhow::bail!(
|
||||
"stored CLI session is for {}/{}/{} but this workspace is configured for {}/{}/{}; run `clusterflux login --browser` from this workspace",
|
||||
session.tenant,
|
||||
session.project,
|
||||
session.user,
|
||||
config.tenant,
|
||||
config.project,
|
||||
config.user,
|
||||
);
|
||||
}
|
||||
}
|
||||
if let Some(bound_session) = effective_scope
|
||||
.coordinator
|
||||
.as_deref()
|
||||
.and_then(|coordinator| {
|
||||
stored_session_for_coordinator(coordinator, stored_session.as_ref())
|
||||
})
|
||||
{
|
||||
effective_scope.tenant = bound_session.tenant.clone();
|
||||
effective_scope.project = bound_session.project.clone();
|
||||
effective_scope.user = bound_session.user.clone();
|
||||
}
|
||||
let inspection = bundle_inspection(
|
||||
BundleInspectArgs {
|
||||
project: Some(cwd.clone()),
|
||||
source_provider: None,
|
||||
disabled_source_providers: Vec::new(),
|
||||
json: true,
|
||||
},
|
||||
cwd.clone(),
|
||||
)
|
||||
.ok();
|
||||
let coordinator = effective_scope.coordinator.clone();
|
||||
let attached_nodes = list_attached_nodes_if_available_with_session(
|
||||
coordinator.as_deref(),
|
||||
&effective_scope,
|
||||
stored_session.as_ref(),
|
||||
)?;
|
||||
let coordinator_response = list_task_events_if_available_with_session(
|
||||
coordinator.as_deref(),
|
||||
&effective_scope,
|
||||
None,
|
||||
stored_session.as_ref(),
|
||||
)?;
|
||||
let process_report = process_list_report_with_session(
|
||||
ProcessListArgs {
|
||||
scope: effective_scope.clone(),
|
||||
},
|
||||
stored_session.as_ref(),
|
||||
)?;
|
||||
let discovered_environments = discovered_environment_names(inspection.as_ref());
|
||||
let active_process = process_report
|
||||
.get("processes")
|
||||
.and_then(Value::as_array)
|
||||
.and_then(|processes| processes.first())
|
||||
.and_then(|process| process.get("process"))
|
||||
.and_then(Value::as_str)
|
||||
.map(str::to_owned)
|
||||
.unwrap_or_else(|| {
|
||||
if process_report.get("status").and_then(Value::as_str) == Some("ok") {
|
||||
"none".to_owned()
|
||||
} else {
|
||||
"unknown_without_coordinator".to_owned()
|
||||
}
|
||||
});
|
||||
let quota_posture = project_quota_posture(&attached_nodes, coordinator_response.as_ref());
|
||||
Ok(json!({
|
||||
"command": "project status",
|
||||
"cwd": cwd,
|
||||
"tenant": effective_scope.tenant,
|
||||
"project": effective_scope.project,
|
||||
"user": effective_scope.user,
|
||||
"coordinator": coordinator,
|
||||
"project_identity": {
|
||||
"tenant": effective_scope.tenant,
|
||||
"project": effective_scope.project,
|
||||
"user": effective_scope.user,
|
||||
"source": if config.is_some() { "project_config_with_cli_overrides" } else { "cli_scope" }
|
||||
},
|
||||
"project_config": config,
|
||||
"bundle": inspection,
|
||||
"discovered_environments": discovered_environments,
|
||||
"active_process": active_process,
|
||||
"processes": process_report.get("processes").cloned().unwrap_or_else(|| json!([])),
|
||||
"attached_nodes": attached_nodes,
|
||||
"quota_posture": quota_posture,
|
||||
"coordinator_response": coordinator_response,
|
||||
}))
|
||||
}
|
||||
|
||||
pub(crate) fn project_list_report(args: ProjectListArgs, cwd: PathBuf) -> Result<Value> {
|
||||
let stored_session = read_cli_session(&cwd)?;
|
||||
let coordinator = args.scope.coordinator.clone().or_else(|| {
|
||||
stored_session
|
||||
.as_ref()
|
||||
.map(|session| session.coordinator.clone())
|
||||
});
|
||||
let tenant = session_or_effective_scope_value(
|
||||
stored_session.as_ref(),
|
||||
&args.scope.tenant,
|
||||
|session| session.tenant.as_str(),
|
||||
"tenant",
|
||||
);
|
||||
let user = session_or_effective_scope_value(
|
||||
stored_session.as_ref(),
|
||||
&args.scope.user,
|
||||
|session| session.user.as_str(),
|
||||
"user",
|
||||
);
|
||||
if let Some(coordinator) = &coordinator {
|
||||
let mut session = JsonLineSession::connect(coordinator)?;
|
||||
let request = authenticated_or_local_trusted_request(
|
||||
coordinator,
|
||||
stored_session.as_ref(),
|
||||
json!({
|
||||
"type": "list_projects",
|
||||
}),
|
||||
json!({
|
||||
"type": "list_projects",
|
||||
"tenant": tenant,
|
||||
"actor_user": user,
|
||||
}),
|
||||
)?;
|
||||
let response = session.request(request)?;
|
||||
let projects = response
|
||||
.get("projects")
|
||||
.cloned()
|
||||
.unwrap_or_else(|| json!([]));
|
||||
let project_count = projects.as_array().map(Vec::len).unwrap_or(0);
|
||||
return Ok(json!({
|
||||
"command": "project list",
|
||||
"source": "public_coordinator_api",
|
||||
"coordinator": coordinator,
|
||||
"tenant": tenant,
|
||||
"user": user,
|
||||
"projects": projects,
|
||||
"project_count": project_count,
|
||||
"private_website_required": false,
|
||||
"response": response,
|
||||
"coordinator_session_requests": session.requests(),
|
||||
}));
|
||||
}
|
||||
let projects = read_project_config(&cwd)?.into_iter().collect::<Vec<_>>();
|
||||
let project_count = projects.len();
|
||||
Ok(json!({
|
||||
"command": "project list",
|
||||
"source": "local_project_config",
|
||||
"projects": projects,
|
||||
"project_count": project_count,
|
||||
"private_website_required": false,
|
||||
}))
|
||||
}
|
||||
|
||||
pub(crate) fn project_select_report(args: ProjectSelectArgs, cwd: PathBuf) -> Result<Value> {
|
||||
let stored_session = read_cli_session(&cwd)?;
|
||||
let tenant = session_or_effective_scope_value(
|
||||
stored_session.as_ref(),
|
||||
&args.scope.tenant,
|
||||
|session| session.tenant.as_str(),
|
||||
"tenant",
|
||||
);
|
||||
let user = session_or_effective_scope_value(
|
||||
stored_session.as_ref(),
|
||||
&args.scope.user,
|
||||
|session| session.user.as_str(),
|
||||
"user",
|
||||
);
|
||||
let coordinator = args.scope.coordinator.clone().or_else(|| {
|
||||
stored_session
|
||||
.as_ref()
|
||||
.map(|session| session.coordinator.clone())
|
||||
});
|
||||
let config = ProjectConfig {
|
||||
tenant: tenant.clone(),
|
||||
project: args.selected_project.clone(),
|
||||
user: user.clone(),
|
||||
coordinator: coordinator.clone(),
|
||||
};
|
||||
let coordinator_response = if let Some(coordinator) = &coordinator {
|
||||
let mut session = JsonLineSession::connect(coordinator)?;
|
||||
let request = authenticated_or_local_trusted_request(
|
||||
coordinator,
|
||||
stored_session.as_ref(),
|
||||
json!({
|
||||
"type": "select_project",
|
||||
"project": args.selected_project,
|
||||
}),
|
||||
json!({
|
||||
"type": "select_project",
|
||||
"tenant": tenant,
|
||||
"actor_user": user,
|
||||
"project": args.selected_project,
|
||||
}),
|
||||
)?;
|
||||
Some(session.request(request)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
write_project_config(&cwd, &config)?;
|
||||
let selected_project = coordinator_response
|
||||
.as_ref()
|
||||
.and_then(|response| response.get("project"))
|
||||
.cloned()
|
||||
.unwrap_or_else(|| {
|
||||
json!({
|
||||
"id": config.project.clone(),
|
||||
"tenant": config.tenant.clone(),
|
||||
"name": config.project.clone(),
|
||||
})
|
||||
});
|
||||
Ok(json!({
|
||||
"command": "project select",
|
||||
"source": if coordinator.is_some() { "public_coordinator_api" } else { "local_project_config" },
|
||||
"selected_project": selected_project,
|
||||
"project_config_written": true,
|
||||
"private_website_required": false,
|
||||
"project_config": config,
|
||||
"coordinator_response": coordinator_response,
|
||||
}))
|
||||
}
|
||||
|
||||
fn session_or_effective_scope_value(
|
||||
stored_session: Option<&StoredCliSession>,
|
||||
cli_value: &str,
|
||||
session_value: impl FnOnce(&StoredCliSession) -> &str,
|
||||
default_value: &str,
|
||||
) -> String {
|
||||
if let Some(session) = stored_session.filter(|session| session.session_secret.is_some()) {
|
||||
session_value(session).to_owned()
|
||||
} else {
|
||||
effective_scope_value(cli_value, stored_session.map(session_value), default_value)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue