use std::path::PathBuf;
use crate::error::Result;
use crate::generate::GeneratedFile;
use crate::plan::Step;
use crate::registry::service_def::ServiceDef;
pub fn target_file_path(store_name: &str, consumer_name: &str) -> Result<PathBuf> {
Ok(crate::service_home(store_name)?
.join("targets")
.join(format!("{consumer_name}.json")))
}
pub fn scrape_target_step(
store_name: &str,
consumer: &ServiceDef,
resolved_host_port: Option<u16>,
) -> Result<Option<Step>> {
let Some(metrics) = &consumer.metrics else {
return Ok(None);
};
let name = &consumer.service.name;
let target = if metrics.host_network {
let Some(host_port) = resolved_host_port else {
return Ok(None);
};
format!("host.containers.internal:{host_port}")
} else {
let Some(port) = consumer.ports.iter().find(|p| p.name == metrics.port) else {
return Ok(None);
};
format!("{name}:{}", port.container_port)
};
let content = format!(
"[{{\"targets\": [\"{target}\"], \"labels\": {{\"service\": \"{name}\", \"__metrics_path__\": \"{path}\"}}}}]\n",
path = metrics.path,
);
Ok(Some(Step::WriteFile(GeneratedFile {
path: target_file_path(store_name, name)?,
content,
})))
}
pub fn datasource_file_path(dashboard_name: &str, store_name: &str) -> Result<PathBuf> {
Ok(crate::service_home(dashboard_name)?
.join("provisioning-datasources")
.join(format!("ryra-{store_name}.yml")))
}
pub fn datasource_step(
dashboard_name: &str,
store_name: &str,
store_container_port: u16,
) -> Result<Step> {
let content = format!(
"# Managed by ryra - datasource for the installed metrics store.\n\
apiVersion: 1\n\
datasources:\n\
\x20 - name: {store_name}\n\
\x20 type: prometheus\n\
\x20 access: proxy\n\
\x20 url: http://{store_name}:{store_container_port}\n\
\x20 isDefault: true\n"
);
Ok(Step::WriteFile(GeneratedFile {
path: datasource_file_path(dashboard_name, store_name)?,
content,
}))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::registry::service_def::ServiceDef;
fn def_with_metrics(name: &str, port_name: &str, container_port: u16) -> ServiceDef {
toml::from_str(&format!(
"[service]\nname = \"{name}\"\ndescription = \"x\"\n\n\
[[ports]]\nname = \"{port_name}\"\ncontainer_port = {container_port}\n\n\
[metrics]\nport = \"{port_name}\"\n"
))
.unwrap_or_else(|e| unreachable!("minimal def must parse: {e}"))
}
#[test]
fn scrape_target_uses_container_port_and_default_path() {
let def = def_with_metrics("forgejo", "http", 3000);
let step = scrape_target_step("prometheus", &def, Some(38123))
.unwrap_or_else(|e| unreachable!("step build should not fail: {e}"));
let Some(Step::WriteFile(file)) = step else {
unreachable!("expected a WriteFile step")
};
assert!(file.content.contains("\"forgejo:3000\""));
assert!(file.content.contains("\"__metrics_path__\": \"/metrics\""));
assert!(file.path.ends_with("prometheus/targets/forgejo.json"));
}
#[test]
fn host_network_target_uses_host_gateway_and_host_port() {
let mut def = def_with_metrics("node-exporter", "http", 9100);
if let Some(m) = def.metrics.as_mut() {
m.host_network = true;
}
let step = scrape_target_step("prometheus", &def, Some(9100))
.unwrap_or_else(|e| unreachable!("step build should not fail: {e}"));
let Some(Step::WriteFile(file)) = step else {
unreachable!("expected a WriteFile step")
};
assert!(file.content.contains("\"host.containers.internal:9100\""));
let none = scrape_target_step("prometheus", &def, None)
.unwrap_or_else(|e| unreachable!("step build should not fail: {e}"));
assert!(none.is_none());
}
#[test]
fn no_metrics_decl_no_step() {
let mut def = def_with_metrics("plain", "http", 80);
def.metrics = None;
let step = scrape_target_step("prometheus", &def, None)
.unwrap_or_else(|e| unreachable!("step build should not fail: {e}"));
assert!(step.is_none());
}
#[test]
fn datasource_points_at_store_container() {
let step = datasource_step("grafana", "prometheus", 9090)
.unwrap_or_else(|e| unreachable!("step build should not fail: {e}"));
let Step::WriteFile(file) = step else {
unreachable!("expected a WriteFile step")
};
assert!(file.content.contains("url: http://prometheus:9090"));
assert!(file.content.contains("type: prometheus"));
assert!(
file.path
.ends_with("grafana/provisioning-datasources/ryra-prometheus.yml")
);
}
}