Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ tempfile = "3.23.0"
hostname = "0.4.1"
scopeguard = "1.2.0"
users = "0.11"
humantime = "2.4.0"

[build-dependencies]
protoc-bin-vendored = "3.2.0"
Expand Down
56 changes: 56 additions & 0 deletions context/interfaces/src/cmd_report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# `src/cmd_report.rs`

## Responsible for
- The `debug report` artifact: an in-memory `Report` model filled by the
CLI's impure collectors, rendered to text as a pure function.

## Public interface
```rust
/// Everything the report file shows, collected before rendering.
pub struct Report {
pub id: String,
pub captured_at_unix: u64,
pub tool_build: String,
pub dump: StateDump,
pub intercept: Option<bool>,
pub environment: Vec<Probe>,
pub log_tail: Option<Result<LogTail, String>>,
}

/// One environment probe: a label plus its output or an error note.
pub struct Probe {
pub label: String,
pub result: Result<String, String>,
}

/// A bounded tail of a log file, plus the facts needed to say honestly
/// whether older lines were dropped to fit the bound.
pub struct LogTail {
pub text: String,
pub window_lines: usize,
pub byte_clipped: bool,
}

impl Report {
/// Render the report artifact. Pure: the same `Report` always
/// produces the same text.
pub fn render(&self) -> String;
}

/// This node's mesh name (lexicographically first if several), or
/// `unknown` if the derivation assigns it none.
pub fn node_name(dump: &StateDump) -> String;

/// Hash of the shared derived view: the "do these nodes agree?"
/// comparison key.
pub fn state_fingerprint(dump: &StateDump) -> String;

/// Hash of the input endorsement bases; per-node change marker, not a
/// cross-node comparison key.
pub fn inputs_digest(input: &BTreeSet<endor::Base>) -> String;

/// Collect and write a debug report file, printing its path. Errors if
/// the daemon's admin socket does not answer; all other probes degrade
/// into notes in the report.
pub async fn run(socket_path: Option<PathBuf>) -> anyhow::Result<()>;
```
5 changes: 5 additions & 0 deletions context/interfaces/src/proxy.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,9 @@ impl Handle {
/// Run the proxy/intercept service until cancelled.
pub(crate) async fn run(self, cancel: CancellationToken) -> anyhow::Result<()>;
}

/// The listener addresses the proxy binds when traffic interception is
/// enabled. Expectations for the debug report, not confirmed-bound
/// addresses.
pub(crate) fn expected_listeners() -> Vec<(&'static str, SocketAddr)>;
```
2 changes: 2 additions & 0 deletions context/interfaces/src/state.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ pub(crate) struct State {
pub(crate) log_file: Option<PathBuf>,
pub(crate) listen_addr: SocketAddr,
pub(crate) local_ip: IpAddr,
/// Unix time when this state was created (daemon start). Not persisted.
pub(crate) started_at_unix: u64,
pub(crate) endorse_local_ip: bool,
pub(crate) intercept: bool,
}
Expand Down
7 changes: 7 additions & 0 deletions context/interfaces/src/state_dump.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ pub struct StateDump {
pub adhoc_membership: Vec<AdhocMembershipDump>,
pub admin_socket: Option<String>,
pub log_file: Option<String>,
/// Daemon self-description: build hash and start time reported by the
/// running daemon (which may predate the binary on disk). Empty/zero
/// when the daemon did not report them (older build).
pub daemon_build: String,
pub daemon_started_at_unix: u64,
}

/// Structured snapshot of ad-hoc membership state.
Expand All @@ -29,6 +34,8 @@ impl StateDump {
membership: Option<adhoc::Membership>,
admin_socket: Option<String>,
log_file: Option<String>,
daemon_build: String,
daemon_started_at_unix: u64,
) -> Self;

/// Encode the dump as TOML for debugging.
Expand Down
5 changes: 5 additions & 0 deletions proto/intermesh.proto
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,11 @@ message StateDumpResponse {
optional string log_file = 8;

repeated intermesh.adhoc.Membership adhoc_membership = 9;

// Daemon self-description. The daemon reports its own build and start
// time because the running daemon may predate the binary on disk.
string daemon_build = 14; // git commit hash of the running daemon
uint64 daemon_started_at_unix = 15; // (Timestamp)
}

message ShowRequest {
Expand Down
2 changes: 2 additions & 0 deletions src/admin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ impl AdminService for GrpcService {
.log_file
.as_ref()
.map(|p| p.display().to_string()),
env!("GIT_COMMIT").to_string(),
self.state.started_at_unix,
);

Ok(Response::new(dump.to_proto()))
Expand Down
13 changes: 13 additions & 0 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use dialoguer::console;
use tokio::process::Command;

use crate::admin::AdminClient;
use crate::cmd_report;
use crate::cmd_status;
use crate::daemon;
use crate::dsl::parse_endorsements;
Expand Down Expand Up @@ -106,6 +107,17 @@ enum DebugCommands {
},
/// Sign and insert endorsements from DSL input (use "-" for stdin).
Endorse { input: String },

/// Write a shareable debug report file and print its path
///
/// The header carries two fingerprints for comparing captures. "state"
/// is a fingerprint of the shared mesh view (names, identities, IPs, and
/// mesh root); it excludes node-local fields, so match it across nodes
/// to confirm they agree on the mesh. "inputs" is a fingerprint of this
/// node's raw endorsement set; it is per-node by design and changes when
/// that node's inputs do, so compare it across two captures of the same
/// node to see whether anything changed.
Report,
}

fn print_banner() {
Expand Down Expand Up @@ -223,6 +235,7 @@ pub async fn run() -> Result<()> {
DebugCommands::Endorse { input } => {
cmd_debug_endorse(input, cli.admin_socket).await?;
}
DebugCommands::Report => cmd_report::run(cli.admin_socket).await?,
},
Some(Commands::Version) => println!("intermesh {GIT_COMMIT}"),
None => Cli::command().print_help()?,
Expand Down
Loading
Loading