use datestamp instead of creating a version
This commit is contained in:
parent
bd08bb3aad
commit
65fbf3bb31
7 changed files with 47 additions and 87 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
|
@ -3359,6 +3359,7 @@ dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"cc",
|
"cc",
|
||||||
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
"crossterm 0.28.1",
|
"crossterm 0.28.1",
|
||||||
"futures",
|
"futures",
|
||||||
|
|
@ -3371,6 +3372,7 @@ dependencies = [
|
||||||
"prost-types",
|
"prost-types",
|
||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
"ratatui",
|
"ratatui",
|
||||||
|
"regex",
|
||||||
"russh",
|
"russh",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,9 @@ rand = "0.8"
|
||||||
ratatui = { version = "0.30", optional = true }
|
ratatui = { version = "0.30", optional = true }
|
||||||
crossterm = { version = "0.28", optional = true }
|
crossterm = { version = "0.28", optional = true }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
regex = "1"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["tui"]
|
default = ["tui"]
|
||||||
tui = ["ratatui", "crossterm"]
|
tui = ["ratatui", "crossterm"]
|
||||||
|
|
@ -38,6 +41,7 @@ tui = ["ratatui", "crossterm"]
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
prost-build = "0.14"
|
prost-build = "0.14"
|
||||||
cc = "1.0"
|
cc = "1.0"
|
||||||
|
chrono = "0.4"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
opt-level = "z"
|
opt-level = "z"
|
||||||
|
|
|
||||||
79
build.rs
79
build.rs
|
|
@ -1,5 +1,3 @@
|
||||||
use std::process::Command;
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
// Compile protobuf definitions
|
// Compile protobuf definitions
|
||||||
prost_build::compile_protos(&["proto/agent.proto"], &["proto/"]).unwrap();
|
prost_build::compile_protos(&["proto/agent.proto"], &["proto/"]).unwrap();
|
||||||
|
|
@ -13,80 +11,15 @@ fn main() {
|
||||||
// Link against netsnmp library
|
// Link against netsnmp library
|
||||||
println!("cargo:rustc-link-lib=netsnmp");
|
println!("cargo:rustc-link-lib=netsnmp");
|
||||||
|
|
||||||
// Inject git-based version if available, otherwise use Cargo.toml version
|
// Inject compile timestamp as version
|
||||||
// This ensures the binary version matches the Docker image tag
|
// This allows tracking when a specific agent binary was built
|
||||||
let version = get_version();
|
let version = get_version();
|
||||||
println!("cargo:rustc-env=BUILD_VERSION={}", version);
|
println!("cargo:rustc-env=BUILD_VERSION={}", version);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_version() -> String {
|
fn get_version() -> String {
|
||||||
// First check if BUILD_VERSION is already set (e.g., from Docker build arg)
|
// Generate RFC 3339 timestamp at compile time
|
||||||
if let Ok(version) = std::env::var("BUILD_VERSION") {
|
// Format: YYYY-MM-DDTHH:MM:SSZ
|
||||||
if !version.is_empty() && version != "0.1.0-unknown" {
|
let now = chrono::Utc::now();
|
||||||
return version;
|
now.format("%Y-%m-%dT%H:%M:%SZ").to_string()
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try git describe - gives us the most descriptive version
|
|
||||||
// Examples:
|
|
||||||
// v0.2.0 -> "0.2.0" (exact tag)
|
|
||||||
// v0.2.0-5-g831588e -> "0.2.0.5.831588e" (5 commits after v0.2.0)
|
|
||||||
if let Ok(output) = Command::new("git")
|
|
||||||
.args(["describe", "--tags", "--always", "--dirty=-modified"])
|
|
||||||
.output()
|
|
||||||
{
|
|
||||||
if output.status.success() {
|
|
||||||
let desc = String::from_utf8_lossy(&output.stdout).trim().to_string();
|
|
||||||
|
|
||||||
// Parse git describe output
|
|
||||||
if let Some(version) = parse_git_describe(&desc) {
|
|
||||||
return version;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback: Try short commit hash only
|
|
||||||
if let Ok(output) = Command::new("git")
|
|
||||||
.args(["rev-parse", "--short", "HEAD"])
|
|
||||||
.output()
|
|
||||||
{
|
|
||||||
if output.status.success() {
|
|
||||||
let commit = String::from_utf8_lossy(&output.stdout).trim().to_string();
|
|
||||||
return format!("{}.{}", env!("CARGO_PKG_VERSION"), commit);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Final fallback to Cargo.toml version
|
|
||||||
env!("CARGO_PKG_VERSION").to_string()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_git_describe(desc: &str) -> Option<String> {
|
|
||||||
// Strip 'v' prefix if present
|
|
||||||
let desc = desc.strip_prefix('v').unwrap_or(desc);
|
|
||||||
|
|
||||||
// Check for dirty flag
|
|
||||||
let dirty = desc.ends_with("-modified");
|
|
||||||
let desc = desc.strip_suffix("-modified").unwrap_or(desc);
|
|
||||||
|
|
||||||
if let Some((base, rest)) = desc.split_once('-') {
|
|
||||||
// Format: tag-N-ghash (e.g., "0.2.0-5-g831588e")
|
|
||||||
let parts: Vec<&str> = rest.split('-').collect();
|
|
||||||
if parts.len() == 2 {
|
|
||||||
let commit_count = parts[0];
|
|
||||||
let hash = parts[1].strip_prefix('g').unwrap_or(parts[1]);
|
|
||||||
let version = format!("{}.{}.{}", base, commit_count, hash);
|
|
||||||
return Some(if dirty {
|
|
||||||
format!("{}-modified", version)
|
|
||||||
} else {
|
|
||||||
version
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// No commits after tag, just use the tag
|
|
||||||
Some(if dirty {
|
|
||||||
format!("{}-modified", desc)
|
|
||||||
} else {
|
|
||||||
desc.to_string()
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -355,9 +355,7 @@ impl SnmpSession {
|
||||||
Err(_) => Ok(SnmpValue::OctetString(value_buf[..value_len].to_vec())),
|
Err(_) => Ok(SnmpValue::OctetString(value_buf[..value_len].to_vec())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ASN_OPAQUE => {
|
ASN_OPAQUE => Ok(SnmpValue::OctetString(value_buf[..value_len].to_vec())),
|
||||||
Ok(SnmpValue::OctetString(value_buf[..value_len].to_vec()))
|
|
||||||
}
|
|
||||||
ASN_IPADDRESS => {
|
ASN_IPADDRESS => {
|
||||||
// IP addresses are 4 bytes - convert to dotted notation
|
// IP addresses are 4 bytes - convert to dotted notation
|
||||||
if value_len == 4 {
|
if value_len == 4 {
|
||||||
|
|
@ -466,7 +464,9 @@ impl SnmpSession {
|
||||||
// Try UTF-8 conversion first
|
// Try UTF-8 conversion first
|
||||||
match String::from_utf8(res.value[..res.value_len].to_vec()) {
|
match String::from_utf8(res.value[..res.value_len].to_vec()) {
|
||||||
Ok(s) => SnmpValue::String(s),
|
Ok(s) => SnmpValue::String(s),
|
||||||
Err(_) => SnmpValue::OctetString(res.value[..res.value_len].to_vec()),
|
Err(_) => {
|
||||||
|
SnmpValue::OctetString(res.value[..res.value_len].to_vec())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ASN_OPAQUE => SnmpValue::OctetString(res.value[..res.value_len].to_vec()),
|
ASN_OPAQUE => SnmpValue::OctetString(res.value[..res.value_len].to_vec()),
|
||||||
|
|
@ -483,7 +483,9 @@ impl SnmpSession {
|
||||||
ASN_OBJECT_ID => {
|
ASN_OBJECT_ID => {
|
||||||
match String::from_utf8(res.value[..res.value_len].to_vec()) {
|
match String::from_utf8(res.value[..res.value_len].to_vec()) {
|
||||||
Ok(s) => SnmpValue::Oid(s),
|
Ok(s) => SnmpValue::Oid(s),
|
||||||
Err(_) => SnmpValue::OctetString(res.value[..res.value_len].to_vec()),
|
Err(_) => {
|
||||||
|
SnmpValue::OctetString(res.value[..res.value_len].to_vec())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ASN_INTEGER | ASN_COUNTER | ASN_GAUGE | ASN_TIMETICKS | ASN_UINTEGER => {
|
ASN_INTEGER | ASN_COUNTER | ASN_GAUGE | ASN_TIMETICKS | ASN_UINTEGER => {
|
||||||
|
|
|
||||||
|
|
@ -58,20 +58,29 @@ impl DevicePoller {
|
||||||
let config_clone = config.clone();
|
let config_clone = config.clone();
|
||||||
|
|
||||||
// Spawn the polling thread with 8MB stack for SNMPv3 crypto operations
|
// Spawn the polling thread with 8MB stack for SNMPv3 crypto operations
|
||||||
tracing::info!("Spawning device poller thread for {} at {}:{}", device_id, config.ip, config.port);
|
tracing::info!(
|
||||||
|
"Spawning device poller thread for {} at {}:{}",
|
||||||
|
device_id,
|
||||||
|
config.ip,
|
||||||
|
config.port
|
||||||
|
);
|
||||||
std::thread::Builder::new()
|
std::thread::Builder::new()
|
||||||
.name(format!("poller-{}", device_id))
|
.name(format!("poller-{}", device_id))
|
||||||
.stack_size(8 * 1024 * 1024) // 8MB stack (default is 2MB)
|
.stack_size(8 * 1024 * 1024) // 8MB stack (default is 2MB)
|
||||||
.spawn(move || {
|
.spawn(move || {
|
||||||
tracing::info!("Device poller thread starting for {}", device_id_clone);
|
tracing::info!("Device poller thread starting for {}", device_id_clone);
|
||||||
if let Err(e) = run_poller_thread(device_id_clone.clone(), config_clone, request_rx) {
|
if let Err(e) = run_poller_thread(device_id_clone.clone(), config_clone, request_rx)
|
||||||
|
{
|
||||||
tracing::error!("Device poller thread failed for {}: {}", device_id_clone, e);
|
tracing::error!("Device poller thread failed for {}: {}", device_id_clone, e);
|
||||||
}
|
}
|
||||||
tracing::info!("Device poller thread exited for {}", device_id_clone);
|
tracing::info!("Device poller thread exited for {}", device_id_clone);
|
||||||
})
|
})
|
||||||
.expect("Failed to spawn device poller thread");
|
.expect("Failed to spawn device poller thread");
|
||||||
|
|
||||||
tracing::info!("Successfully spawned device poller thread for {}", device_id);
|
tracing::info!(
|
||||||
|
"Successfully spawned device poller thread for {}",
|
||||||
|
device_id
|
||||||
|
);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
device_id,
|
device_id,
|
||||||
|
|
@ -184,7 +193,11 @@ fn run_poller_thread(
|
||||||
SnmpRequest::Get { oid, response_tx } => {
|
SnmpRequest::Get { oid, response_tx } => {
|
||||||
tracing::debug!("Poller thread {} executing GET", device_id);
|
tracing::debug!("Poller thread {} executing GET", device_id);
|
||||||
let result = perform_get(&runtime, &client, &config, &oid);
|
let result = perform_get(&runtime, &client, &config, &oid);
|
||||||
tracing::debug!("Poller thread {} GET result: {:?}", device_id, result.is_ok());
|
tracing::debug!(
|
||||||
|
"Poller thread {} GET result: {:?}",
|
||||||
|
device_id,
|
||||||
|
result.is_ok()
|
||||||
|
);
|
||||||
let _ = response_tx.send(result);
|
let _ = response_tx.send(result);
|
||||||
}
|
}
|
||||||
SnmpRequest::Walk {
|
SnmpRequest::Walk {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
/// Get version at runtime - prefers BUILD_VERSION from build.rs, falls back to Cargo.toml
|
/// Get compile timestamp at runtime - returns RFC 3339 formatted timestamp from build.rs
|
||||||
|
/// Format: YYYY-MM-DDTHH:MM:SSZ (e.g., "2025-02-09T15:30:45Z")
|
||||||
pub fn current_version() -> &'static str {
|
pub fn current_version() -> &'static str {
|
||||||
option_env!("BUILD_VERSION").unwrap_or(env!("CARGO_PKG_VERSION"))
|
option_env!("BUILD_VERSION").unwrap_or(env!("CARGO_PKG_VERSION"))
|
||||||
}
|
}
|
||||||
|
|
@ -25,9 +26,14 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_current_version_format() {
|
fn test_current_version_format() {
|
||||||
let version = current_version();
|
let version = current_version();
|
||||||
// Version should be in semver-like format (e.g., "0.1.0" or custom BUILD_VERSION)
|
// Version should be RFC 3339 timestamp format (YYYY-MM-DDTHH:MM:SSZ)
|
||||||
// At minimum, should have some content
|
// Regex: ^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$
|
||||||
assert!(!version.is_empty());
|
let rfc3339_pattern = regex::Regex::new(r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$").unwrap();
|
||||||
|
assert!(
|
||||||
|
rfc3339_pattern.is_match(version),
|
||||||
|
"Version should be RFC 3339 timestamp, got: {}",
|
||||||
|
version
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -686,7 +686,7 @@ impl AgentClient {
|
||||||
/// Send heartbeat to server.
|
/// Send heartbeat to server.
|
||||||
async fn send_heartbeat(&mut self) -> Result<()> {
|
async fn send_heartbeat(&mut self) -> Result<()> {
|
||||||
let heartbeat = AgentHeartbeat {
|
let heartbeat = AgentHeartbeat {
|
||||||
version: env!("CARGO_PKG_VERSION").to_string(),
|
version: crate::version::current_version().to_string(),
|
||||||
hostname: self.cached_hostname.clone(),
|
hostname: self.cached_hostname.clone(),
|
||||||
uptime_seconds: get_uptime_seconds(),
|
uptime_seconds: get_uptime_seconds(),
|
||||||
ip_address: get_local_ip().unwrap_or_default(),
|
ip_address: get_local_ip().unwrap_or_default(),
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue