The sd module provides all functionality for integrating with the Status Dashboard API, including component management, incident creation, cache operations, and authentication.
- Source:
src/sd.rs - Public export:
cloudmon_metrics::sd
This module consolidates all Status Dashboard V2 API integration logic in one place, providing:
- Component fetching and caching
- Component ID resolution with subset attribute matching
- Incident creation with static payloads
- HMAC-JWT authentication
Key-value pair for identifying components:
#[derive(Clone, Deserialize, Serialize, Debug, PartialEq, Eq, Hash, Ord, PartialOrd)]
pub struct ComponentAttribute {
pub name: String,
pub value: String,
}Derives Ord and PartialOrd for deterministic sorting in cache keys.
Component definition from configuration:
#[derive(Clone, Deserialize, Serialize, Debug)]
pub struct Component {
pub name: String,
pub attributes: Vec<ComponentAttribute>,
}API response from GET /v2/components:
#[derive(Clone, Deserialize, Serialize, Debug)]
pub struct StatusDashboardComponent {
pub id: u32,
pub name: String,
#[serde(default)]
pub attributes: Vec<ComponentAttribute>,
}API request for POST /v2/events:
#[derive(Clone, Deserialize, Serialize, Debug)]
pub struct IncidentData {
pub title: String,
pub description: String,
pub impact: u8,
pub components: Vec<u32>,
pub start_date: String, // RFC3339 format
pub system: bool,
#[serde(rename = "type")]
pub incident_type: String,
}Type alias for the component ID cache:
pub type ComponentCache = HashMap<(String, Vec<ComponentAttribute>), u32>;Key: (component_name, sorted_attributes) → Value: component_id
pub fn build_auth_headers(
secret: Option<&str>,
preferred_username: Option<&str>,
group: Option<&str>,
) -> HeaderMapGenerates HMAC-JWT authorization headers for Status Dashboard API.
- Creates Bearer token using HMAC-SHA256 signing
- Returns empty HeaderMap if no secret provided (optional auth)
- Optionally includes
preferred_usernameclaim for audit logging - Optionally includes
groupsarray claim with single group for authorization
Example:
// With claims
let headers = build_auth_headers(
Some("status-dashboard-secret"),
Some("operator-sd"),
Some("sd-operators"),
);
// JWT payload: {"preferred_username": "operator-sd", "groups": ["sd-operators"]}
// Without claims (backward compatible)
let headers = build_auth_headers(Some("my-secret"), None, None);
// JWT payload: {}pub async fn fetch_components(
client: &reqwest::Client,
base_url: &str,
headers: &HeaderMap,
) -> anyhow::Result<Vec<StatusDashboardComponent>>Fetches all components from Status Dashboard API V2 (GET /v2/components).
pub fn build_component_id_cache(
components: Vec<StatusDashboardComponent>
) -> ComponentCacheBuilds component ID cache from fetched components. Sorts attributes for deterministic cache keys.
pub fn find_component_id(
cache: &ComponentCache,
target: &Component
) -> Option<u32>Finds component ID in cache with subset attribute matching:
- Config attributes must be a subset of cache attributes
- Example: config
{region: "EU-DE"}matches cache{region: "EU-DE", category: "Storage"}
pub fn build_incident_data(
component_id: u32,
impact: u8,
timestamp: i64
) -> IncidentDataBuilds incident data structure for V2 API:
- Static title: "System incident from monitoring system"
- Static description: "System-wide incident affecting one or multiple components. Created automatically."
- Timestamp: RFC3339 format, minus 1 second from input
- system: true (indicates auto-generated)
pub async fn create_incident(
client: &reqwest::Client,
base_url: &str,
headers: &HeaderMap,
incident_data: &IncidentData,
) -> anyhow::Result<()>Creates incident via Status Dashboard API V2 (POST /v2/events).
use cloudmon_metrics::sd::{
build_auth_headers, build_component_id_cache, build_incident_data,
create_incident, fetch_components, find_component_id,
Component, ComponentAttribute,
};
// Build auth headers with optional claims
let headers = build_auth_headers(
config.jwt_secret.as_deref(),
config.claim_preferred_username.as_deref(),
config.claim_group.as_deref(),
);
// Fetch and cache components
let components = fetch_components(&client, &url, &headers).await?;
let cache = build_component_id_cache(components);
// Find component ID
let target = Component {
name: "Object Storage Service".to_string(),
attributes: vec![ComponentAttribute {
name: "region".to_string(),
value: "EU-DE".to_string(),
}],
};
let component_id = find_component_id(&cache, &target)?;
// Create incident
let incident = build_incident_data(component_id, 2, timestamp);
create_incident(&client, &url, &headers, &incident).await?;Integration tests are in tests/integration_sd.rs:
cargo test --test integration_sdTest coverage:
test_fetch_components_success- API fetchingtest_build_component_id_cache- Cache structuretest_find_component_id_subset_matching- Subset matching logictest_build_incident_data_structure- Static payload generationtest_timestamp_rfc3339_minus_one_second- Timestamp handlingtest_create_incident_success- API postingtest_build_auth_headers- JWT generation- Additional edge case tests
- Reporter Overview - How reporter uses this module
- API Contracts - V2 API specifications
- Spec - Feature specification