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
|
||||
- Logging with tracing
|
||||
- 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
|
||||
- [x] **Protobuf Definitions** (`proto/agent.proto`)
|
||||
|
|
@ -246,6 +253,7 @@ towerops-agent/
|
|||
│ ├── main.rs # Entry point, CLI, initialization
|
||||
│ ├── config.rs # Types matching API responses
|
||||
│ ├── api_client.rs # HTTP client for Towerops API
|
||||
│ ├── version.rs # Docker image version checking
|
||||
│ ├── metrics/
|
||||
│ │ └── mod.rs # Metric types (SensorReading, InterfaceStat)
|
||||
│ ├── snmp/
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ mod metrics;
|
|||
mod poller;
|
||||
mod proto;
|
||||
mod snmp;
|
||||
mod version;
|
||||
|
||||
use clap::Parser;
|
||||
use log::{info, LevelFilter, Metadata, Record};
|
||||
|
|
@ -71,6 +72,10 @@ async fn main() {
|
|||
let args = Args::parse();
|
||||
|
||||
info!("Towerops agent starting");
|
||||
|
||||
// Check for newer Docker image version
|
||||
version::check_for_updates();
|
||||
|
||||
info!("API URL: {}", args.api_url);
|
||||
info!(
|
||||
"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