Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ jobs:
run: cargo clippy --all-targets --no-default-features --features discovery -- -D warnings
- name: Run clippy (http-config only)
run: cargo clippy --all-targets --no-default-features --features http-config -- -D warnings
- name: Run clippy (discovery-kubernetes only)
run: cargo clippy --all-targets --no-default-features --features discovery-kubernetes -- -D warnings
- name: Run tests (all features)
run: cargo test --all-features
- name: Run tests (no default features)
Expand All @@ -44,6 +46,8 @@ jobs:
run: cargo test --no-default-features --features discovery
- name: Run tests (http-config only)
run: cargo test --no-default-features --features http-config
- name: Run tests (discovery-kubernetes only)
run: cargo test --no-default-features --features discovery-kubernetes
- name: Run clippy (OTAP compatibility suite)
env:
CARGO_TARGET_DIR: target
Expand All @@ -68,6 +72,8 @@ jobs:
run: cargo check --all-targets --no-default-features --features discovery
- name: Check MSRV (http-config only)
run: cargo check --all-targets --no-default-features --features http-config
- name: Check MSRV (discovery-kubernetes only)
run: cargo check --all-targets --no-default-features --features discovery-kubernetes

go-fixtures:
name: Go testdata sync check
Expand Down
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ exclude = ["tests/otap-compat"]
default = ["arrow", "dto"]
arrow = ["dep:arrow-array", "dep:arrow-buffer", "dep:arrow-schema"]
discovery = ["dep:serde", "dep:serde_json", "dep:tokio"]
discovery-kubernetes = ["discovery", "http-config"]
dto = ["dep:prost", "dep:prost-types"]
http-config = ["dep:reqwest", "dep:serde", "dep:serde_json"]

Expand Down
55 changes: 54 additions & 1 deletion src/discovery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,30 @@
// See the License for the specific language governing permissions and
// limitations under the License.

//! Prometheus-compatible target discovery (`static_configs` and `dns_sd_configs`).
//! Prometheus-compatible target discovery (`static_configs`, `dns_sd_configs`,
//! and `reqwest`-based `kubernetes_sd_configs` for `role: pod`).

pub mod dns;
#[cfg(feature = "discovery-kubernetes")]
pub mod kubernetes;

use std::collections::BTreeMap;
use std::time::Duration;

use serde::{Deserialize, Deserializer, Serialize, Serializer};

#[cfg(feature = "http-config")]
pub use crate::http_config::{
Authorization, BasicAuth, HttpClientConfig, HttpConfigError, HttpHeader, HttpHeaders, OAuth2,
ProxyConfig, TlsConfig, TlsVersion,
};
pub use dns::{DnsDiscovery, DnsRecord, DnsRecordType, DnsSdConfig, ResolvConf};
#[cfg(feature = "discovery-kubernetes")]
pub use kubernetes::{
KubernetesAttachMetadataConfig, KubernetesMetadataContext, KubernetesNamespaceDiscovery,
KubernetesPodDiscovery, KubernetesRole, KubernetesSdConfig, KubernetesSelectorConfig, Pod,
build_pod_target_group, build_pod_target_groups,
};

/// Reserved Prometheus label holding the `<host>:<port>` scrape target address.
pub const ADDRESS_LABEL: &str = "__address__";
Expand All @@ -37,6 +51,10 @@ pub enum DiscoveryError {
/// I/O or filesystem error.
#[error("I/O error: {0}")]
Io(#[from] std::io::Error),
/// HTTP client or transport error during discovery.
#[cfg(feature = "http-config")]
#[error("HTTP error: {0}")]
Http(#[from] reqwest::Error),
/// DNS protocol or lookup error.
#[error("DNS error: {0}")]
Dns(String),
Expand All @@ -45,6 +63,17 @@ pub enum DiscoveryError {
Json(#[from] serde_json::Error),
}

#[cfg(feature = "http-config")]
impl From<crate::http_config::HttpConfigError> for DiscoveryError {
fn from(err: crate::http_config::HttpConfigError) -> Self {
match err {
crate::http_config::HttpConfigError::InvalidConfig(msg) => Self::InvalidConfig(msg),
crate::http_config::HttpConfigError::Io(e) => Self::Io(e),
crate::http_config::HttpConfigError::Http(e) => Self::Http(e),
}
}
}

/// Static target group configuration matching Prometheus `static_configs` entries.
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
Expand Down Expand Up @@ -139,6 +168,10 @@ pub struct DiscoveryConfigs {
/// DNS service discovery configurations (`dns_sd_configs`).
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub dns_sd_configs: Vec<DnsSdConfig>,
/// Kubernetes service discovery configurations (`kubernetes_sd_configs`).
#[cfg(feature = "discovery-kubernetes")]
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub kubernetes_sd_configs: Vec<KubernetesSdConfig>,
}

impl DiscoveryConfigs {
Expand All @@ -147,6 +180,10 @@ impl DiscoveryConfigs {
for dns_cfg in &mut self.dns_sd_configs {
dns_cfg.validate()?;
}
#[cfg(feature = "discovery-kubernetes")]
for k8s_cfg in &mut self.kubernetes_sd_configs {
k8s_cfg.validate()?;
}
Ok(())
}

Expand All @@ -165,6 +202,22 @@ pub(crate) fn join_host_port(host: &str, port: &str) -> String {
}
}

/// Sanitizes an arbitrary string into a valid Prometheus ASCII label name suffix by replacing
/// any character outside `[a-zA-Z0-9_]` with `'_'`, matching Prometheus `strutil.SanitizeLabelName`.
#[cfg(feature = "discovery-kubernetes")]
pub(crate) fn sanitize_label_name(input: &str) -> String {
input
.chars()
.map(|c| {
if c.is_ascii_alphanumeric() || c == '_' {
c
} else {
'_'
}
})
.collect()
}

/// Parses a Prometheus duration string (such as `"30s"`, `"1m"`, `"500ms"`, `"1h30m"`, `"7d"`,
/// `"1w"`, `"1y"`, or `"0"`) into a [`Duration`], matching `prometheus/common/model.ParseDuration`.
pub fn parse_duration(s: &str) -> Result<Duration, DiscoveryError> {
Expand Down
Loading
Loading