Add Docker image version checking on agent startup
The agent now checks Docker Hub on startup to see if a newer version is available and logs a warning if an update is detected. Changes: - Add version.rs module to query Docker Hub API - Check for newer versions on startup (non-blocking) - Log warning with docker pull command if update available - Gracefully handle Docker Hub API failures - Update CLAUDE.md with version checking documentation
This commit is contained in:
parent
37b3e1c8be
commit
468528de66
3 changed files with 91 additions and 0 deletions
|
|
@ -49,6 +49,13 @@ Lightweight Rust agent for remote SNMP polling. Deployed on customer networks to
|
||||||
- Environment variable support
|
- Environment variable support
|
||||||
- Logging with tracing
|
- Logging with tracing
|
||||||
- Graceful startup/shutdown
|
- Graceful startup/shutdown
|
||||||
|
- Docker image version checking on startup
|
||||||
|
|
||||||
|
- [x] **Version Checking** (`version.rs`)
|
||||||
|
- Checks Docker Hub for newer image versions on startup
|
||||||
|
- Compares current version with latest available
|
||||||
|
- Logs warnings if updates are available
|
||||||
|
- Non-blocking, fails gracefully if Docker Hub unavailable
|
||||||
|
|
||||||
### Protocol Buffers Integration
|
### Protocol Buffers Integration
|
||||||
- [x] **Protobuf Definitions** (`proto/agent.proto`)
|
- [x] **Protobuf Definitions** (`proto/agent.proto`)
|
||||||
|
|
@ -246,6 +253,7 @@ towerops-agent/
|
||||||
│ ├── main.rs # Entry point, CLI, initialization
|
│ ├── main.rs # Entry point, CLI, initialization
|
||||||
│ ├── config.rs # Types matching API responses
|
│ ├── config.rs # Types matching API responses
|
||||||
│ ├── api_client.rs # HTTP client for Towerops API
|
│ ├── api_client.rs # HTTP client for Towerops API
|
||||||
|
│ ├── version.rs # Docker image version checking
|
||||||
│ ├── metrics/
|
│ ├── metrics/
|
||||||
│ │ └── mod.rs # Metric types (SensorReading, InterfaceStat)
|
│ │ └── mod.rs # Metric types (SensorReading, InterfaceStat)
|
||||||
│ ├── snmp/
|
│ ├── snmp/
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ mod metrics;
|
||||||
mod poller;
|
mod poller;
|
||||||
mod proto;
|
mod proto;
|
||||||
mod snmp;
|
mod snmp;
|
||||||
|
mod version;
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use log::{info, LevelFilter, Metadata, Record};
|
use log::{info, LevelFilter, Metadata, Record};
|
||||||
|
|
@ -71,6 +72,10 @@ async fn main() {
|
||||||
let args = Args::parse();
|
let args = Args::parse();
|
||||||
|
|
||||||
info!("Towerops agent starting");
|
info!("Towerops agent starting");
|
||||||
|
|
||||||
|
// Check for newer Docker image version
|
||||||
|
version::check_for_updates();
|
||||||
|
|
||||||
info!("API URL: {}", args.api_url);
|
info!("API URL: {}", args.api_url);
|
||||||
info!(
|
info!(
|
||||||
"Config refresh interval: {} seconds",
|
"Config refresh interval: {} seconds",
|
||||||
|
|
|
||||||
78
src/version.rs
Normal file
78
src/version.rs
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
use log::{info, warn};
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
const DOCKER_IMAGE: &str = "gmcintire/towerops-agent";
|
||||||
|
const CURRENT_VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct DockerHubResponse {
|
||||||
|
results: Vec<DockerHubTag>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct DockerHubTag {
|
||||||
|
name: String,
|
||||||
|
#[allow(dead_code)]
|
||||||
|
last_updated: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if a newer version of the Docker image is available
|
||||||
|
pub fn check_for_updates() {
|
||||||
|
info!("Current version: {}", CURRENT_VERSION);
|
||||||
|
info!("Checking for newer Docker image versions...");
|
||||||
|
|
||||||
|
match check_docker_hub() {
|
||||||
|
Ok(Some(latest_version)) => {
|
||||||
|
if latest_version != CURRENT_VERSION {
|
||||||
|
warn!("⚠️ Newer version available: {} (current: {})", latest_version, CURRENT_VERSION);
|
||||||
|
warn!(" Update with: docker pull {}:latest", DOCKER_IMAGE);
|
||||||
|
} else {
|
||||||
|
info!("✓ Running latest version ({})", CURRENT_VERSION);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(None) => {
|
||||||
|
info!("Unable to determine latest version from Docker Hub");
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
warn!("Failed to check for updates: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_docker_hub() -> Result<Option<String>, Box<dyn std::error::Error>> {
|
||||||
|
let url = format!(
|
||||||
|
"https://hub.docker.com/v2/repositories/{}/tags?page_size=10",
|
||||||
|
DOCKER_IMAGE
|
||||||
|
);
|
||||||
|
|
||||||
|
let response: DockerHubResponse = ureq::get(&url)
|
||||||
|
.timeout(std::time::Duration::from_secs(10))
|
||||||
|
.call()?
|
||||||
|
.into_json()?;
|
||||||
|
|
||||||
|
// Look for version tags (e.g., "0.1.0", "0.2.0")
|
||||||
|
// Ignore "latest" tag as it doesn't tell us the actual version
|
||||||
|
let version_tags: Vec<&str> = response
|
||||||
|
.results
|
||||||
|
.iter()
|
||||||
|
.map(|t| t.name.as_str())
|
||||||
|
.filter(|name| !name.eq_ignore_ascii_case("latest"))
|
||||||
|
.filter(|name| is_semver(name))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if let Some(latest) = version_tags.first() {
|
||||||
|
Ok(Some(latest.to_string()))
|
||||||
|
} else {
|
||||||
|
// If no version tags found, try to get info from "latest" tag
|
||||||
|
// but we can't determine actual version number
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_semver(s: &str) -> bool {
|
||||||
|
// Simple check: version string should match pattern like "0.1.0" or "v0.1.0"
|
||||||
|
let s = s.strip_prefix('v').unwrap_or(s);
|
||||||
|
let parts: Vec<&str> = s.split('.').collect();
|
||||||
|
|
||||||
|
parts.len() == 3 && parts.iter().all(|p| p.parse::<u32>().is_ok())
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue