mirror of
https://github.com/ModernRelay/omnigraph.git
synced 2026-06-21 02:28:07 +02:00
feat(cluster): add read-only validate and plan
This commit is contained in:
parent
ab5f3b878a
commit
043b02e617
12 changed files with 1764 additions and 33 deletions
|
|
@ -10,6 +10,9 @@ use color_eyre::eyre::{Result, bail};
|
|||
use omnigraph::db::{Omnigraph, ReadTarget, SnapshotId};
|
||||
use omnigraph::loader::LoadMode;
|
||||
use omnigraph::storage::normalize_root_uri;
|
||||
use omnigraph_cluster::{
|
||||
DiagnosticSeverity, PlanOutput, ValidateOutput, plan_config_dir, validate_config_dir,
|
||||
};
|
||||
use omnigraph_compiler::query::parser::parse_query;
|
||||
use omnigraph_compiler::schema::parser::parse_schema;
|
||||
use omnigraph_compiler::{
|
||||
|
|
@ -305,6 +308,11 @@ enum Command {
|
|||
#[arg(long)]
|
||||
json: bool,
|
||||
},
|
||||
/// Validate and plan read-only cluster configuration.
|
||||
Cluster {
|
||||
#[command(subcommand)]
|
||||
command: ClusterCommand,
|
||||
},
|
||||
/// Manage graphs on a multi-graph server (MR-668)
|
||||
Graphs {
|
||||
#[command(subcommand)]
|
||||
|
|
@ -312,6 +320,28 @@ enum Command {
|
|||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
enum ClusterCommand {
|
||||
/// Validate cluster.yaml and referenced schemas, queries, and policy files.
|
||||
Validate {
|
||||
/// Cluster config directory containing cluster.yaml.
|
||||
#[arg(long, default_value = ".")]
|
||||
config: PathBuf,
|
||||
/// Emit JSON instead of human text.
|
||||
#[arg(long)]
|
||||
json: bool,
|
||||
},
|
||||
/// Produce a read-only plan by diffing cluster.yaml against __cluster/state.json.
|
||||
Plan {
|
||||
/// Cluster config directory containing cluster.yaml.
|
||||
#[arg(long, default_value = ".")]
|
||||
config: PathBuf,
|
||||
/// Emit JSON instead of human text.
|
||||
#[arg(long)]
|
||||
json: bool,
|
||||
},
|
||||
}
|
||||
|
||||
/// Operations on the graph registry of a multi-graph server (MR-668).
|
||||
///
|
||||
/// All operations target a remote multi-graph server URL (http:// or
|
||||
|
|
@ -683,6 +713,77 @@ fn print_json<T: Serialize>(value: &T) -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn print_cluster_validate_human(output: &ValidateOutput) {
|
||||
if output.ok {
|
||||
println!(
|
||||
"cluster config valid: {} resource(s), {} dependency edge(s)",
|
||||
output.resources.len(),
|
||||
output.dependencies.len()
|
||||
);
|
||||
} else {
|
||||
println!("cluster config invalid");
|
||||
}
|
||||
print_cluster_diagnostics(&output.diagnostics);
|
||||
}
|
||||
|
||||
fn print_cluster_plan_human(output: &PlanOutput) {
|
||||
if output.ok {
|
||||
println!(
|
||||
"cluster plan: {} change(s), {} approval gate(s)",
|
||||
output.changes.len(),
|
||||
output.approvals_required.len()
|
||||
);
|
||||
for change in &output.changes {
|
||||
println!(" {:?} {}", change.operation, change.resource);
|
||||
}
|
||||
if output.changes.is_empty() {
|
||||
println!(" no changes");
|
||||
}
|
||||
} else {
|
||||
println!("cluster plan failed");
|
||||
}
|
||||
print_cluster_diagnostics(&output.diagnostics);
|
||||
}
|
||||
|
||||
fn print_cluster_diagnostics(diagnostics: &[omnigraph_cluster::Diagnostic]) {
|
||||
for diagnostic in diagnostics {
|
||||
let label = match diagnostic.severity {
|
||||
DiagnosticSeverity::Error => "ERROR",
|
||||
DiagnosticSeverity::Warning => "WARN ",
|
||||
};
|
||||
println!(
|
||||
"{label} {} {}: {}",
|
||||
diagnostic.code, diagnostic.path, diagnostic.message
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn finish_cluster_validate(output: &ValidateOutput, json: bool) -> Result<()> {
|
||||
if json {
|
||||
print_json(output)?;
|
||||
} else {
|
||||
print_cluster_validate_human(output);
|
||||
}
|
||||
if !output.ok {
|
||||
io::stdout().flush()?;
|
||||
std::process::exit(1);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn finish_cluster_plan(output: &PlanOutput, json: bool) -> Result<()> {
|
||||
if json {
|
||||
print_json(output)?;
|
||||
} else {
|
||||
print_cluster_plan_human(output);
|
||||
}
|
||||
if !output.ok {
|
||||
io::stdout().flush()?;
|
||||
std::process::exit(1);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_remote_uri(uri: &str) -> bool {
|
||||
uri.starts_with("http://") || uri.starts_with("https://")
|
||||
}
|
||||
|
|
@ -801,13 +902,11 @@ struct ResolvedPolicyContext {
|
|||
|
||||
fn resolve_policy_context(config: &OmnigraphConfig) -> Result<ResolvedPolicyContext> {
|
||||
let selected = config.resolve_policy_tooling_graph_selection()?;
|
||||
let policy_file = config
|
||||
.resolve_policy_file_for(selected)
|
||||
.ok_or_else(|| {
|
||||
color_eyre::eyre::eyre!(
|
||||
"policy.file or graphs.<name>.policy.file must be set in omnigraph.yaml"
|
||||
)
|
||||
})?;
|
||||
let policy_file = config.resolve_policy_file_for(selected).ok_or_else(|| {
|
||||
color_eyre::eyre::eyre!(
|
||||
"policy.file or graphs.<name>.policy.file must be set in omnigraph.yaml"
|
||||
)
|
||||
})?;
|
||||
let graph_id = match selected {
|
||||
Some(name) => graph_resource_id_for_selection(Some(name), ""),
|
||||
None => graph_resource_id_for_selection(None, "default"),
|
||||
|
|
@ -2166,16 +2265,14 @@ fn rewrite_deprecated_argv(args: Vec<OsString>) -> Vec<OsString> {
|
|||
}
|
||||
if let Some(sub) = args.get(1).and_then(|s| s.to_str()) {
|
||||
match sub {
|
||||
"read" => eprintln!(
|
||||
"warning: `omnigraph read` is deprecated; use `omnigraph query` instead"
|
||||
),
|
||||
"read" => {
|
||||
eprintln!("warning: `omnigraph read` is deprecated; use `omnigraph query` instead")
|
||||
}
|
||||
"change" => eprintln!(
|
||||
"warning: `omnigraph change` is deprecated; use `omnigraph mutate` instead"
|
||||
),
|
||||
"check" => {
|
||||
eprintln!(
|
||||
"warning: `omnigraph check` is deprecated; use `omnigraph lint` instead"
|
||||
);
|
||||
eprintln!("warning: `omnigraph check` is deprecated; use `omnigraph lint` instead");
|
||||
// Rewrite the top-level subcommand to `lint`; pass through the rest.
|
||||
let mut out = Vec::with_capacity(args.len());
|
||||
out.push(args[0].clone());
|
||||
|
|
@ -3111,6 +3208,16 @@ async fn main() -> Result<()> {
|
|||
}
|
||||
}
|
||||
}
|
||||
Command::Cluster { command } => match command {
|
||||
ClusterCommand::Validate { config, json } => {
|
||||
let output = validate_config_dir(config);
|
||||
finish_cluster_validate(&output, json)?;
|
||||
}
|
||||
ClusterCommand::Plan { config, json } => {
|
||||
let output = plan_config_dir(config);
|
||||
finish_cluster_plan(&output, json)?;
|
||||
}
|
||||
},
|
||||
Command::Graphs { command } => match command {
|
||||
GraphsCommand::List {
|
||||
uri,
|
||||
|
|
@ -3157,8 +3264,8 @@ mod tests {
|
|||
use super::{
|
||||
DEFAULT_BEARER_TOKEN_ENV, apply_bearer_token, bearer_token_from_env_file,
|
||||
legacy_change_request_body, load_cli_config, load_env_file_into_process,
|
||||
normalize_bearer_token, parse_env_assignment, resolve_policy_context,
|
||||
resolve_cli_graph, resolve_remote_bearer_token,
|
||||
normalize_bearer_token, parse_env_assignment, resolve_cli_graph, resolve_policy_context,
|
||||
resolve_remote_bearer_token,
|
||||
};
|
||||
use omnigraph_server::load_config;
|
||||
use reqwest::header::AUTHORIZATION;
|
||||
|
|
@ -3420,7 +3527,8 @@ graphs:
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn graph_identity_resolve_policy_context_named_cli_graph_uses_graph_key_not_project_name_or_uri() {
|
||||
fn graph_identity_resolve_policy_context_named_cli_graph_uses_graph_key_not_project_name_or_uri()
|
||||
{
|
||||
let temp = tempdir().unwrap();
|
||||
let config_path = temp.path().join("omnigraph.yaml");
|
||||
fs::write(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue