Public dry run dryrun-33643196cbdf
This commit is contained in:
parent
666360b98d
commit
17e52b5a2f
4 changed files with 172 additions and 4 deletions
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"kind": "disasmer-filtered-public-tree",
|
||||
"source_commit": "f6bbcf3a2f6fd20b724a51a04861ea480d4c5607",
|
||||
"release_name": "dryrun-f6bbcf3a2f6f",
|
||||
"source_commit": "33643196cbdfe5eeacd69bc39b8cdf86cb924d2d",
|
||||
"release_name": "dryrun-33643196cbdf",
|
||||
"filtered_out": [
|
||||
"private/**",
|
||||
"experiments/**",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use std::io::{BufRead, BufReader, Write};
|
||||
use std::net::{TcpListener, TcpStream};
|
||||
use std::net::{TcpListener, TcpStream, ToSocketAddrs};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::{Command, Stdio};
|
||||
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
|
||||
|
|
@ -19,6 +19,7 @@ const DEFAULT_OIDC_ISSUER_URL: &str = "https://auth.michelpaulissen.com";
|
|||
const BROWSER_CALLBACK_ADDR: &str = "127.0.0.1:45173";
|
||||
const BROWSER_CALLBACK_PATH: &str = "/callback";
|
||||
const DEFAULT_BROWSER_LOGIN_CALLBACK_TIMEOUT_SECONDS: u64 = 300;
|
||||
const DOCTOR_COORDINATOR_TIMEOUT: Duration = Duration::from_millis(500);
|
||||
|
||||
#[derive(Clone, Debug, Parser)]
|
||||
#[command(name = "disasmer", version, arg_required_else_help = true)]
|
||||
|
|
@ -639,10 +640,17 @@ struct ProjectConfig {
|
|||
|
||||
fn doctor_report(args: DoctorArgs, cwd: PathBuf) -> Result<Value> {
|
||||
let config = read_project_config(&cwd)?;
|
||||
let coordinator = args.scope.coordinator.or_else(|| {
|
||||
config
|
||||
.as_ref()
|
||||
.and_then(|config| config.coordinator.clone())
|
||||
});
|
||||
let coordinator_reachability = coordinator_reachability(coordinator.as_deref());
|
||||
Ok(json!({
|
||||
"command": "doctor",
|
||||
"cwd": cwd,
|
||||
"coordinator": args.scope.coordinator.or_else(|| config.as_ref().and_then(|config| config.coordinator.clone())),
|
||||
"coordinator": coordinator,
|
||||
"coordinator_reachability": coordinator_reachability,
|
||||
"auth": auth_state_value(),
|
||||
"project": config,
|
||||
"dependencies": {
|
||||
|
|
@ -663,6 +671,73 @@ fn doctor_report(args: DoctorArgs, cwd: PathBuf) -> Result<Value> {
|
|||
}))
|
||||
}
|
||||
|
||||
fn coordinator_reachability(coordinator: Option<&str>) -> Value {
|
||||
let Some(coordinator) = coordinator else {
|
||||
return json!({
|
||||
"checked": false,
|
||||
"status": "not_configured",
|
||||
"next_action": "run disasmer login --browser or pass --coordinator"
|
||||
});
|
||||
};
|
||||
|
||||
match ping_coordinator(coordinator, DOCTOR_COORDINATOR_TIMEOUT) {
|
||||
Ok(response) => json!({
|
||||
"checked": true,
|
||||
"status": "reachable",
|
||||
"coordinator": coordinator,
|
||||
"response": response
|
||||
}),
|
||||
Err(err) => json!({
|
||||
"checked": true,
|
||||
"status": "unreachable",
|
||||
"coordinator": coordinator,
|
||||
"error": err.to_string(),
|
||||
"next_action": "check the coordinator URL, network, and service status"
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn ping_coordinator(coordinator: &str, timeout: Duration) -> Result<Value> {
|
||||
let transport_addr = json_line_transport_addr(coordinator);
|
||||
let socket_addrs = transport_addr
|
||||
.to_socket_addrs()
|
||||
.with_context(|| format!("failed to resolve {coordinator} via {transport_addr}"))?;
|
||||
let mut saw_addr = false;
|
||||
let mut last_error = None;
|
||||
|
||||
for socket_addr in socket_addrs {
|
||||
saw_addr = true;
|
||||
match TcpStream::connect_timeout(&socket_addr, timeout) {
|
||||
Ok(stream) => {
|
||||
stream
|
||||
.set_read_timeout(Some(timeout))
|
||||
.with_context(|| format!("failed to set read timeout for {socket_addr}"))?;
|
||||
stream
|
||||
.set_write_timeout(Some(timeout))
|
||||
.with_context(|| format!("failed to set write timeout for {socket_addr}"))?;
|
||||
let mut session = JsonLineSession::from_stream(stream)?;
|
||||
return session.request(json!({ "type": "ping" })).with_context(|| {
|
||||
format!("coordinator ping failed for {coordinator} via {socket_addr}")
|
||||
});
|
||||
}
|
||||
Err(err) => last_error = Some(err),
|
||||
}
|
||||
}
|
||||
|
||||
if !saw_addr {
|
||||
anyhow::bail!(
|
||||
"failed to resolve any socket address for {coordinator} via {transport_addr}"
|
||||
);
|
||||
}
|
||||
|
||||
anyhow::bail!(
|
||||
"failed to connect to {coordinator} via {transport_addr}: {}",
|
||||
last_error
|
||||
.map(|err| err.to_string())
|
||||
.unwrap_or_else(|| "no socket addresses attempted".to_owned())
|
||||
);
|
||||
}
|
||||
|
||||
fn auth_status_report(args: AuthStatusArgs, cwd: PathBuf) -> Result<Value> {
|
||||
let config = read_project_config(&cwd)?;
|
||||
Ok(json!({
|
||||
|
|
@ -1363,6 +1438,20 @@ fn human_report(value: &Value) -> String {
|
|||
));
|
||||
}
|
||||
}
|
||||
if let Some(reachability) = value.get("coordinator_reachability") {
|
||||
if let Some(status) = reachability.get("status").and_then(Value::as_str) {
|
||||
lines.push(format!("coordinator reachability: {status}"));
|
||||
}
|
||||
if let Some(error) = reachability.get("error").and_then(Value::as_str) {
|
||||
lines.push(format!("coordinator error: {error}"));
|
||||
}
|
||||
if let Some(response_type) = reachability
|
||||
.pointer("/response/type")
|
||||
.and_then(Value::as_str)
|
||||
{
|
||||
lines.push(format!("coordinator ping: {response_type}"));
|
||||
}
|
||||
}
|
||||
if let Some(response) = value
|
||||
.get("response")
|
||||
.or_else(|| value.get("coordinator_response"))
|
||||
|
|
@ -2724,6 +2813,10 @@ impl JsonLineSession {
|
|||
let transport_addr = json_line_transport_addr(addr);
|
||||
let writer = TcpStream::connect(&transport_addr)
|
||||
.with_context(|| format!("failed to connect to {addr} via {transport_addr}"))?;
|
||||
Self::from_stream(writer)
|
||||
}
|
||||
|
||||
fn from_stream(writer: TcpStream) -> Result<Self> {
|
||||
let reader = BufReader::new(writer.try_clone()?);
|
||||
Ok(Self {
|
||||
writer,
|
||||
|
|
@ -3198,6 +3291,73 @@ mod tests {
|
|||
assert_eq!(json_line_transport_addr("127.0.0.1:7999"), "127.0.0.1:7999");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn doctor_reports_unchecked_coordinator_reachability_without_config() {
|
||||
let temp = tempfile::tempdir().unwrap();
|
||||
let report = doctor_report(
|
||||
DoctorArgs {
|
||||
scope: CliScopeArgs {
|
||||
coordinator: None,
|
||||
tenant: "tenant".to_owned(),
|
||||
project: "project".to_owned(),
|
||||
user: "user".to_owned(),
|
||||
json: false,
|
||||
},
|
||||
},
|
||||
temp.path().to_path_buf(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(report["command"], "doctor");
|
||||
assert!(report["coordinator"].is_null());
|
||||
assert_eq!(report["coordinator_reachability"]["checked"], false);
|
||||
assert_eq!(
|
||||
report["coordinator_reachability"]["status"],
|
||||
"not_configured"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn doctor_pings_configured_coordinator() {
|
||||
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
|
||||
let addr = listener.local_addr().unwrap().to_string();
|
||||
let server = std::thread::spawn(move || {
|
||||
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("\"type\":\"ping\""));
|
||||
stream
|
||||
.write_all(b"{\"type\":\"pong\",\"epoch\":42}\n")
|
||||
.unwrap();
|
||||
});
|
||||
|
||||
let temp = tempfile::tempdir().unwrap();
|
||||
let report = doctor_report(
|
||||
DoctorArgs {
|
||||
scope: CliScopeArgs {
|
||||
coordinator: Some(addr.clone()),
|
||||
tenant: "tenant".to_owned(),
|
||||
project: "project".to_owned(),
|
||||
user: "user".to_owned(),
|
||||
json: false,
|
||||
},
|
||||
},
|
||||
temp.path().to_path_buf(),
|
||||
)
|
||||
.unwrap();
|
||||
server.join().unwrap();
|
||||
|
||||
assert_eq!(report["coordinator"], addr);
|
||||
assert_eq!(report["coordinator_reachability"]["checked"], true);
|
||||
assert_eq!(report["coordinator_reachability"]["status"], "reachable");
|
||||
assert_eq!(
|
||||
report["coordinator_reachability"]["response"]["type"],
|
||||
"pong"
|
||||
);
|
||||
assert_eq!(report["coordinator_reachability"]["response"]["epoch"], 42);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cli_first_mvp_command_surface_parses() {
|
||||
for args in [
|
||||
|
|
|
|||
|
|
@ -104,6 +104,8 @@ for (const [name, pattern] of [
|
|||
["CLI version coverage", /fn top_level_version_is_available\(\)/],
|
||||
["CLI JSON parse coverage", /fn cli_first_json_mode_parses_for_primary_commands\(\)/],
|
||||
["CLI human output coverage", /fn human_report_is_text_not_json\(\)/],
|
||||
["doctor unchecked reachability coverage", /fn doctor_reports_unchecked_coordinator_reachability_without_config\(\)/],
|
||||
["doctor ping reachability coverage", /fn doctor_pings_configured_coordinator\(\)/],
|
||||
["project local config coverage", /fn project_init_select_and_status_use_local_project_config\(\)/],
|
||||
["build no full repo upload coverage", /fn build_command_reuses_bundle_inspection_without_full_repo_upload\(\)/],
|
||||
["safe coordinator-required plans", /fn node_enroll_and_process_commands_have_safe_plan_without_coordinator\(\)/],
|
||||
|
|
@ -127,6 +129,7 @@ for (const [name, pattern] of [
|
|||
["human default assertion", /default output should be human-readable text, not JSON/],
|
||||
["login JSON mode", /\["login", "--coordinator", "https:\/\/coord\.example\.test", "--json"\]/],
|
||||
["doctor human mode", /\["doctor"\]/],
|
||||
["doctor reachability JSON mode", /doctorJson\.coordinator_reachability\.status/],
|
||||
["bundle inspect JSON mode", /\["bundle", "inspect", "--project", project, "--json"\]/],
|
||||
["auth expiry posture", /token_expiry_posture[\s\S]*expires_at/],
|
||||
]) {
|
||||
|
|
|
|||
|
|
@ -50,11 +50,16 @@ assert(loginJson.human_flow.Device);
|
|||
const doctorHuman = disasmer(["doctor"]);
|
||||
assertHuman("doctor", doctorHuman, [
|
||||
/Disasmer doctor/,
|
||||
/coordinator reachability: not_configured/,
|
||||
/dependencies:/,
|
||||
/auth:/,
|
||||
/node capabilities:/,
|
||||
]);
|
||||
|
||||
const doctorJson = json(["doctor", "--json"]);
|
||||
assert.strictEqual(doctorJson.coordinator_reachability.checked, false);
|
||||
assert.strictEqual(doctorJson.coordinator_reachability.status, "not_configured");
|
||||
|
||||
const authJson = json(["auth", "status", "--json"], {
|
||||
DISASMER_TOKEN: "token",
|
||||
DISASMER_TOKEN_EXPIRES_AT: "2026-07-04T00:00:00Z",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue