From 58a66099c380ce0dc2f666ba4fc1aebdb2bcd1db Mon Sep 17 00:00:00 2001
From: Evelyn Alicke <e.alicke@famedly.com>
Date: Tue, 27 Jun 2023 21:00:25 +0200
Subject: [PATCH] chore(everything): too much honestly

---
 .dockerignore         |   1 +
 .gitignore            |   3 +
 .gitlab-ci.yml        |  23 +++
 Cargo.toml            |   4 +-
 Dockerfile            |  11 ++
 config.yml            |  10 +-
 docker-compose.yml    |  15 ++
 rustfmt.toml          |   1 +
 shell.nix             |   5 +
 src/config.rs         |  41 +++++
 src/main.rs           | 134 +++++----------
 src/metrics.rs        |  25 ---
 src/metrics/labels.rs |  65 --------
 src/vici.rs           | 374 +++++++++++++++++++++++-------------------
 14 files changed, 357 insertions(+), 355 deletions(-)
 create mode 100644 .dockerignore
 create mode 100644 .gitignore
 create mode 100644 .gitlab-ci.yml
 create mode 100644 Dockerfile
 create mode 100644 docker-compose.yml
 create mode 100644 rustfmt.toml
 create mode 100644 shell.nix
 create mode 100644 src/config.rs
 delete mode 100644 src/metrics.rs
 delete mode 100644 src/metrics/labels.rs

diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..eb5a316
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1 @@
+target
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..0cbf498
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+/debug
+/target
+Cargo.lock
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000..975b4cc
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,23 @@
+include:
+  - project: 'famedly/infra/templates/ci-cd'
+    ref: 'rust-v1'
+    file: '/rust.yml'
+  - project: 'famedly/infra/templates/ci-cd'
+    ref: 'docker-v1'
+    file: '/docker.yml'
+
+stages:
+  - test
+  - build
+
+cargo-check:
+  extends: .cargo_check
+
+cargo-build:
+  extends: .cargo_build
+
+docker_releases:
+  extends: .docker_releases
+
+docker_tags:
+  extends: .docker_tags
diff --git a/Cargo.toml b/Cargo.toml
index abf5540..ba807ea 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -8,9 +8,9 @@ edition = "2021"
 [dependencies]
 config = { version = "0.13.1", features = ["yaml"] }
 serde = { version = "1.0", features = ["derive"] }
-prometheus-client = "0.19.0"
+metrics = "0.21.0"
+metrics-exporter-prometheus = { version = "0.12.1", features = ["http-listener"] }
 futures-util = "0.3.25"
-axum = "0.6.12"
 tokio = { version = "1.21.2", features = ["rt-multi-thread","macros"] }
 rsvici = "0.1"
 anyhow = "1.0.70"
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..ca61e4a
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,11 @@
+FROM registry.gitlab.com/famedly/infra/containers/rust:main as builder
+COPY . /app
+WORKDIR /app
+
+RUN cargo build --release
+
+FROM debian:stable-slim
+RUN mkdir -p /opt/openmetrics-vici-exporter
+WORKDIR /opt/openmetrics-vici-exporter
+COPY --from=builder /app/target/release/openmetrics-vici-exporter /usr/local/bin/openmetrics-vici-exporter
+CMD ["/usr/local/bin/openmetrics-vici-exporter"]
diff --git a/config.yml b/config.yml
index 7c42627..1d34255 100644
--- a/config.yml
+++ b/config.yml
@@ -1,5 +1,7 @@
 ---
-vici_socket: "/var/run/charon.vici"
-actix_bind_addr: "0.0.0.0"
-actix_bind_port: "80"
-actix_auth_token: ""
+vici:
+  socket: "/var/run/charon.vici"
+  interval: 10
+server:
+  address: "0.0.0.0"
+  port: 8001
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..4da34ef
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,15 @@
+version: '3'
+
+services:
+  ove:
+    restart: "unless-stopped"
+    environment:
+      - VICI_EXPORTER_VICI_SOCKET="/var/run/charon.vici"
+      - VICI_EXPORTER_VICI_INTERVAL=10
+      - VICI_EXPORTER_SERVER_ADDRESS=0.0.0.0
+      - VICI_EXPORTER_SERVER_PORT=8001
+    volumes:
+      #- ./config.yml:/opt/openmetrics-vici-exporter/config.yml
+      - /var/run/charon.vici:/var/run/charon.vici
+    ports:
+      - 8111:80/tcp
diff --git a/rustfmt.toml b/rustfmt.toml
new file mode 100644
index 0000000..7530651
--- /dev/null
+++ b/rustfmt.toml
@@ -0,0 +1 @@
+max_width = 120
diff --git a/shell.nix b/shell.nix
new file mode 100644
index 0000000..cb1f424
--- /dev/null
+++ b/shell.nix
@@ -0,0 +1,5 @@
+{ pkgs ? import <nixpkgs> {} }:
+  pkgs.mkShell {
+    packages = with pkgs; [ rustc cargo gcc rustfmt clippy ];
+    name = "rust-env";
+}
diff --git a/src/config.rs b/src/config.rs
new file mode 100644
index 0000000..fb80dd3
--- /dev/null
+++ b/src/config.rs
@@ -0,0 +1,41 @@
+use anyhow::Result;
+use config::Config;
+use serde::Deserialize;
+use std::net::{IpAddr, SocketAddr};
+
+#[derive(Debug, Deserialize)]
+pub struct WebServerConfig {
+    pub address: IpAddr,
+    pub port: u16,
+}
+impl Into<SocketAddr> for &WebServerConfig {
+    fn into(self) -> SocketAddr {
+        SocketAddr::from((self.address, self.port))
+    }
+}
+
+#[derive(Debug, Deserialize)]
+pub struct VICIConfig {
+    pub socket: String,
+    pub interval: u64,
+}
+
+#[derive(Debug, Deserialize)]
+pub struct Configuration {
+    pub server: WebServerConfig,
+    pub vici: VICIConfig,
+}
+
+impl Configuration {
+    pub async fn load() -> Result<Configuration> {
+        let mut s = Config::builder();
+        if std::fs::metadata("config").is_ok(){
+            s = s.add_source(config::File::with_name("config"));
+        } else { println!("config file not found. continuing with env vars... ") };
+
+        s = s.add_source(config::Environment::with_prefix("VICI_EXPORTER").separator("_"));
+//        s.build().unwrap();
+        let conf: Configuration = s.build().unwrap().try_deserialize().unwrap();
+        Ok(conf)
+    }
+}
diff --git a/src/main.rs b/src/main.rs
index 40a3d41..80a12cb 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,104 +1,56 @@
-#![allow(dead_code)]
-
-use axum::{
-    response::IntoResponse,
-    http::{
-        StatusCode,
-        header::{self}
-    },
-    extract::State,
-    routing::get,
-    Router,
-};
-
-use prometheus_client::{
-    registry::Registry,
-    metrics::{
-        family::Family,
-    }
-};
-
-use std::{
-//    collections::HashMap,
-//    error::Error,
-    sync::Arc,
-    net::{IpAddr,SocketAddr},
-//    path::Path,
-};
-
-use serde::Deserialize;
-
-use config::Config;
+use metrics::{
+    describe_gauge, 
+    gauge, 
+    describe_counter,
+    counter, 
+    IntoLabels, 
+    Unit};
+use metrics_exporter_prometheus::PrometheusBuilder;
+use tokio::time::{interval, Duration, MissedTickBehavior};
 
+pub mod config;
 pub mod vici;
-pub mod metrics;
-
-
-#[derive(Debug, Deserialize)]
-struct Configuration {
-    vici_socket: String,
-    axum_bind_addr: IpAddr,
-    axum_bind_port: u16,
-}
-
-pub async fn metrics_handler(State(state): State<Arc<AppState>>) -> impl IntoResponse {
-    let state: Arc<AppState> = state.clone();
-    let mut buffer = String::new();
-    prometheus_client::encoding::text::encode(&mut buffer, &state.registry).unwrap();
-    (
-        StatusCode::OK,
-        [(header::CONTENT_TYPE, "application/openmetrics-text; version=1.0.0; charset=utf-8")],
-        buffer,
-    )
-}
-
-pub struct AppState {
-    pub registry: Registry,
-    pub vici: vici::VICIState,
-}
-
 
 #[tokio::main]
 async fn main() -> anyhow::Result<()> {
-    let settings = Config::builder()
-        .add_source(config::File::with_name("config"))
-        .add_source(config::Environment::with_prefix("VICI_EXPORTER"))
-        .build()
-        .unwrap();
-    let mut conf: Configuration = settings.try_deserialize().unwrap();
-    let mut vici_client = rsvici::unix::connect(conf.vici_socket).await?;
-    let mut vici_state: vici::VICIState;
+    let conf = config::Configuration::load().await?;
+    let mut vici_client = rsvici::unix::connect(conf.vici.socket).await?;
 
-    let metrics = Arc::new(metrics::Metrics {
-        sa_uptime: Family::default(),
-    });
+    let mut interval = interval(Duration::from_secs(conf.vici.interval));
+    interval.set_missed_tick_behavior(MissedTickBehavior::Skip);
 
-    let mut initial_registery = Registry::default();
+    PrometheusBuilder::new()
+        .with_http_listener(&conf.server)
+        .install()
+        .expect("failed to install recorder/exporter");
 
-    initial_registery.register(
-        "sa_uptime",
-        "How Long a connection has been established",
-        metrics.sa_uptime.clone(),
-    );
+    describe_gauge!("sa_uptime", Unit::Seconds, "");
+    describe_gauge!("sa_rekey_time", Unit::Seconds, "");
 
-    let mut state = Arc::new(
-        AppState {
-            registry: initial_registery,
-            vici: vici::VICIState::update(&mut vici_client).await?,
-        },
-    );
+    describe_counter!("sa_child_bytes_out", Unit::Bytes, "");
+    describe_counter!("sa_child_bytes_in", Unit::Bytes, "");
 
+    loop {
+        let vici_state = vici::VICIState::update(&mut vici_client).await?;
 
+        for (sa_name, sa_values) in vici_state.security_associations {
+            let mut labels = sa_values.into_labels();
+            labels.push((&("sa_name", sa_name.clone())).into());
 
-    let addr = SocketAddr::from((conf.axum_bind_addr,conf.axum_bind_port));
-    let app = Router::new()
-        .route("/metrics",get(metrics_handler))
-        .with_state(state);
-
-    axum::Server::bind(&addr)
-        .serve(app.into_make_service())
-        .await
-        .unwrap();
-
-    Ok(())
+            gauge!("sa_uptime", sa_values.established as f64, labels.clone());
+            gauge!("sa_rekey_time", sa_values.rekey_time as f64, labels.clone());
+            //gauge!("sa_state")
+            for (sa_child_name, sa_child_values) in sa_values.child_security_associations {
+                let mut child_labels = sa_child_values.into_labels();
+                child_labels.push((&("sa_name", sa_name.clone())).into());
+                child_labels.push((&("sa_child_name", sa_child_name)).into());
+                counter!("sa_child_bytes_in", sa_child_values.bytes_in, child_labels.clone());
+                counter!("sa_child_bytes_out", sa_child_values.bytes_out, child_labels.clone());
+                counter!("sa_child_packets_in", sa_child_values.packets_in, child_labels.clone());
+                counter!("sa_child_packets_out", sa_child_values.packets_out, child_labels.clone());
+            
+            }
+        }
+        interval.tick().await;
+    }
 }
diff --git a/src/metrics.rs b/src/metrics.rs
deleted file mode 100644
index 726e7ef..0000000
--- a/src/metrics.rs
+++ /dev/null
@@ -1,25 +0,0 @@
-use prometheus_client::{
-    metrics::{
-        family::Family,
-        gauge::Gauge,
-    }
-};
-use anyhow::Result;
-
-use crate::vici;
-pub mod labels;
-
-pub struct Metrics {
-    pub sa_uptime: Family<labels::SecurityAssociationLabels, Gauge>,
-}
-
-impl Metrics {
-    pub async fn sa_uptime(&self, security_associations: vici::SecurityAssociations) -> Result<()>{
-        for named_sa in security_associations.into_iter() {
-            let label_set = labels::SecurityAssociationLabels::set_from_sa(&mut named_sa).await?;
-            let (_sa_name, sa_value) = named_sa;
-            self.sa_uptime.get_or_create(&label_set).set(sa_value.established as i64);
-        }
-        Ok(());
-    }
-}
diff --git a/src/metrics/labels.rs b/src/metrics/labels.rs
deleted file mode 100644
index a2dd2cb..0000000
--- a/src/metrics/labels.rs
+++ /dev/null
@@ -1,65 +0,0 @@
-use serde::Deserialize;
-use prometheus_client::encoding::{EncodeLabelValue,EncodeLabelSet};
-
-use crate::vici;
-use anyhow::Result;
-
-
-// I don't really wanna define *all* of this here, it's gonna get really tedious and uncomfortable to maintain.
-
-/*
-#[derive(Debug, Deserialize, Clone, Hash, PartialEq, Eq)]
-pub struct SecurityAssociationLabels {
-    pub uniqueid: String,
-    pub local_id: String,
-    pub local_host: String,
-    pub local_port: u16,
-    pub remote_id: String,
-    pub remote_host: String,
-    pub remote_port: u16,
-}
-*/
-#[derive(Debug, Deserialize, Clone, Hash, PartialEq, Eq)]
-pub struct SecurityAssociationInfo {
-    pub uniqueid:       String,
-    pub version:        u8,
-    pub local_host:     String,
-    pub local_port:     u16,
-    pub local_id:       String,
-    pub remote_host:    String,
-    pub remote_port:    u16,
-    pub remote_id:      String,
-    pub if_id_in:       String,
-    pub if_id_out:      String,
-    pub encr_alg:       String,
-    pub encr_keysize:   String,
-    pub integ_alg:      String,
-    pub integ_keysize:  String,
-    pub prf_alg:        String,
-    pub dh_group:       Option<String>,
-    pub local_vips:     Vec<String>,
-    pub remote_vips:    Vec<String>,
-}
-
-#[derive(Debug, Clone, Hash, PartialEq, Eq, EncodeLabelSet)]
-pub struct SecurityAssociationLabels {
-    pub name: String,
-    pub uniqueid: String,
-    pub ike_version: u8,
-    pub local_id: String,
-    pub remote_id: String,
-}
-
-
-impl SecurityAssociationLabels {
-    pub async fn set_from_sa(sa: &mut vici::NamedSecurityAssociation) -> Result<SecurityAssociationLabels> {
-        let (sa_name, sa_value) = sa;
-        Ok(SecurityAssociationLabels {
-            name: sa_name,
-            uniqueid: sa_value.uniqueid,
-            ike_version: sa_value.version,
-            local_id: sa_value.local_id.unwrap(),
-            remote_id: sa_value.remote_id.unwrap(),
-        })
-    }
-}
diff --git a/src/vici.rs b/src/vici.rs
index 46b87e0..7f22db4 100644
--- a/src/vici.rs
+++ b/src/vici.rs
@@ -6,47 +6,57 @@ use std::collections::HashMap;
 use futures_util::stream::StreamExt;
 
 use anyhow::Result;
+use metrics::{IntoLabels,Label};
 
 #[derive(Debug, Deserialize)]
 pub struct VICIState {
-    pub version:                Version,
-    pub statistics:             Statistics,
-    pub policies:               Policies,
-    pub connections:            Connections,
-    pub security_associations:  SecurityAssociations,
-    pub certificates:           Certificates,
-    pub authorities:            Authorities,
-    pub pools:                  Pools,
+    pub version: Version,
+    pub statistics: Statistics,
+    pub policies: Policies,
+    pub connections: Connections,
+    pub security_associations: SecurityAssociations,
+    pub certificates: Certificates,
+    pub authorities: Authorities,
+    pub pools: Pools,
 }
 
 impl VICIState {
     pub async fn update(client: &mut rsvici::Client) -> Result<VICIState> {
         Ok(VICIState {
-                version:                client.request("version", ()).await?,
-                statistics:             client.request("statistics", ()).await?,
-                policies:               collected_stream::<NamedPolicy, Policies>(client, "list-policies", "list-policy").await,
-                connections:            collected_stream::<NamedConnection, Connections>(client, "list-connections", "list-conn").await,
-                security_associations:  collected_stream::<NamedSecurityAssociation, SecurityAssociations>(client, "list-sas", "list-sa").await,
-                certificates:           collected_stream::<NamedCertificate, Certificates>(client, "list-certs", "list-cert").await,
-                authorities:            collected_stream::<NamedAuthority, Authorities>(client, "list-authorities", "list-authority").await,
-                pools:                  collected_stream::<NamedPool, Pools>(client, "list-pools", "list-pool").await,
+            version: client.request("version", ()).await?,
+            statistics: client.request("statistics", ()).await?,
+            policies: collected_stream::<NamedPolicy, Policies>(client, "list-policies", "list-policy").await?,
+            connections: collected_stream::<NamedConnection, Connections>(client, "list-connections", "list-conn")
+                .await?,
+            security_associations: collected_stream::<NamedSecurityAssociation, SecurityAssociations>(
+                client, "list-sas", "list-sa",
+            )
+            .await?,
+            certificates: collected_stream::<NamedCertificate, Certificates>(client, "list-certs", "list-cert").await?,
+            authorities: collected_stream::<NamedAuthority, Authorities>(client, "list-authorities", "list-authority")
+                .await?,
+            pools: collected_stream::<NamedPool, Pools>(client, "list-pools", "list-pool").await?,
         })
     }
 }
 
-async fn collected_stream<N,C>(client: &mut rsvici::Client, command: &str, event: &str) -> C
+async fn collected_stream<N, C>(client: &mut rsvici::Client, command: &str, event: &str) -> Result<C>
 where
     N: for<'de> serde::Deserialize<'de>,
     C: std::iter::Extend<N> + Default,
 {
-    client.stream_request::<(), N>(command, event, ()).filter_map(|event| async move {event.ok()}).collect::<C>().await
+    Ok(client
+        .stream_request::<(), N>(command, event, ())
+        .filter_map(|event| async move { event.ok() })
+        .collect::<C>()
+        .await)
 }
 
 // Structs for parsing the control interface
 
 #[derive(Debug, Deserialize)]
 pub struct Version {
-    pub daemon:  String,
+    pub daemon: String,
     pub version: String,
     pub sysname: String,
     pub release: String,
@@ -55,46 +65,46 @@ pub struct Version {
 
 #[derive(Debug, Deserialize)]
 pub struct Statistics {
-    pub uptime:                   StatisticsUptime,
-    pub workers:                  StatisticsWorkers,
-    pub queues:                   StatisticsJobPriorities,
-    pub scheduled:                String,
-    pub ikesecurity_associations: StatisticsIKESecurityAssociations,
-    pub plugins:                  Vec<String>,
-    pub mem:                      Option<StatisticsMem>,
-    pub mallinfo:                 Option<StatisticsMallinfo>,
+    pub uptime: StatisticsUptime,
+    pub workers: StatisticsWorkers,
+    pub queues: StatisticsJobPriorities,
+    pub scheduled: String,
+    pub ikesas: StatisticsIKESecurityAssociations,
+    pub plugins: Vec<String>,
+    pub mem: Option<StatisticsMem>,
+    pub mallinfo: Option<StatisticsMallinfo>,
 }
 
 #[derive(Debug, Deserialize)]
 pub struct StatisticsUptime {
     pub running: String,
-    pub since:   String,
+    pub since: String,
 }
 
 #[derive(Debug, Deserialize)]
 pub struct StatisticsWorkers {
-    pub total:  String,
-    pub idle:   String,
+    pub total: String,
+    pub idle: String,
     pub active: StatisticsJobPriorities,
 }
 
 #[derive(Debug, Deserialize)]
 pub struct StatisticsJobPriorities {
     pub critical: String,
-    pub high:     String,
-    pub medium:   String,
-    pub low:      String,
+    pub high: String,
+    pub medium: String,
+    pub low: String,
 }
 
 #[derive(Debug, Deserialize)]
 pub struct StatisticsIKESecurityAssociations {
-    pub total:     String,
+    pub total: String,
     pub half_open: Option<String>,
 }
 
 #[derive(Debug, Deserialize)]
 pub struct StatisticsMem {
-    pub total:  String,
+    pub total: String,
     pub allocs: String,
 }
 #[derive(Debug, Deserialize)]
@@ -111,19 +121,20 @@ pub type NamedPolicy = (String, Policy);
 
 #[derive(Debug, Deserialize)]
 pub struct Policy {
-    pub child:     String,
-    pub ike:       Option<String>,
-    pub mode:      PolicyMode,
-    pub local_ts:  Option<Vec<String>>,
+    pub child: String,
+    pub ike: Option<String>,
+    pub mode: PolicyMode,
+    pub local_ts: Option<Vec<String>>,
     pub remote_ts: Option<Vec<String>>,
 }
 
 #[derive(Debug, Deserialize)]
+#[serde(rename_all = "snake_case")]
 pub enum PolicyMode {
-    tunnel,
-    transport,
-    pass,
-    drop,
+    Tunnel,
+    Transport,
+    Pass,
+    Drop,
 }
 
 pub type Connections = HashMap<String, Conn>;
@@ -132,45 +143,46 @@ pub type NamedConnection = (String, Conn);
 
 #[derive(Debug, Deserialize)]
 pub struct Conn {
-    pub local_addrs:  Vec<String>,
+    pub local_addrs: Vec<String>,
     pub remote_addrs: Vec<String>,
-    pub version:      String,
-    pub reauth_time:  u32,
-    pub rekey_time:   u32,
-    pub children:     HashMap<String, ConnChildSection>,
+    pub version: String,
+    pub reauth_time: u32,
+    pub rekey_time: u32,
+    pub children: HashMap<String, ConnChildSection>,
 }
 
 #[derive(Debug, Deserialize)]
 pub struct ConnAuthSection {
-    pub class:        String,
-    pub eap_type:     Option<String>,
-    pub eap_vendor:   Option<String>,
-    pub xauth:        Option<String>,
-    pub revocation:   Option<String>,
-    pub id:           String,
-    pub aaa_id:       Option<String>,
-    pub eap_id:       Option<String>,
-    pub xauth_id:     Option<String>,
-    pub groups:       Option<Vec<String>>,
+    pub class: String,
+    pub eap_type: Option<String>,
+    pub eap_vendor: Option<String>,
+    pub xauth: Option<String>,
+    pub revocation: Option<String>,
+    pub id: String,
+    pub aaa_id: Option<String>,
+    pub eap_id: Option<String>,
+    pub xauth_id: Option<String>,
+    pub groups: Option<Vec<String>>,
     pub certificates: Option<Vec<String>>,
-    pub cacerts:      Option<Vec<String>>,
+    pub cacerts: Option<Vec<String>>,
 }
 
 #[derive(Debug, Deserialize)]
 pub struct ConnChildSection {
-    pub mode:          ChildSecurityAssociationMode,
-    pub rekey_time:    u32,
-    pub rekey_bytes:   u64,
+    pub mode: ChildSecurityAssociationMode,
+    pub rekey_time: u32,
+    pub rekey_bytes: u64,
     pub rekey_packets: u64,
-    pub local_ts:      Option<Vec<String>>,
-    pub remote_ts:     Option<Vec<String>>,
+    pub local_ts: Option<Vec<String>>,
+    pub remote_ts: Option<Vec<String>>,
 }
 
 #[derive(Debug, Deserialize, Clone, Hash, PartialEq, Eq)]
+#[serde(rename_all = "UPPERCASE")]
 pub enum ChildSecurityAssociationMode {
-    TUNNEL,
-    TRANSPORT,
-    BEET,
+    Tunnel,
+    Transport,
+    Beet,
 }
 #[derive(Debug, Deserialize, Clone, Hash, PartialEq, Eq)]
 pub enum ChildSecurityAssociationProtocol {
@@ -182,82 +194,103 @@ pub type SecurityAssociations = HashMap<String, SecurityAssociation>;
 
 pub type NamedSecurityAssociation = (String, SecurityAssociation);
 
-#[derive(Debug, Deserialize)]
-pub struct SecurityAssociation {
-    pub uniqueid:                    String,
-    pub version:                     u8,
-    pub state:                       String,
-    pub local_host:                  Option<String>,
-    pub local_port:                  Option<u16>,
-    pub local_id:                    Option<String>,
-    pub remote_host:                 Option<String>,
-    pub remote_port:                 Option<u16>,
-    pub remote_id:                   Option<String>,
-    pub remote_xauth_id:             Option<String>,
-    pub remote_epa_id:               Option<String>,
-    pub initiator:                   Option<bool>,
-    pub initiator_spi:               Option<String>,
-    pub responder_spi:               Option<String>,
-    pub nat_local:                   Option<bool>,
-    pub nat_remote:                  Option<bool>,
-    pub nat_fake:                    Option<bool>,
-    pub nat_any:                     Option<bool>,
-    pub if_id_in:                    Option<String>,
-    pub if_id_out:                   Option<String>,
-    pub encr_alg:                    Option<String>,
-    pub encr_keysize:                Option<String>,
-    pub integ_alg:                   Option<String>,
-    pub integ_keysize:               Option<String>,
-    pub prf_alg:                     Option<String>,
-    pub dh_group:                    Option<String>,
-    pub established:                 u64,
-    pub rekey_time:                  Option<u32>,
-    pub reauth_time:                 Option<u32>,
-    pub local_vips:                  Option<Vec<String>>,
-    pub remote_vips:                 Option<Vec<String>>,
-    pub tasks_queued:                Option<Vec<String>>,
-    pub tasks_active:                Option<Vec<String>>,
-    pub tasks_passive:               Option<Vec<String>>,
-    pub child_security_associations: Option<HashMap<String, SecurityAssociationChild>>,
+impl IntoLabels for &SecurityAssociation {
+    fn into_labels(self) -> Vec<Label> {
+        vec![
+            (&("uniqueid", self.uniqueid.clone())).into(),
+            (&("remote_id", self.remote_id.clone())).into(),
+            (&("local_id", self.local_id.clone())).into(),
+        ]
+    }
 }
 
+#[derive(Debug, Deserialize)]
+pub struct SecurityAssociation {
+    pub uniqueid: String,
+    pub version: u8,
+    pub state: String,
+    pub local_host: String,
+    pub local_port: u16,
+    pub local_id: String,
+    pub remote_host: String,
+    pub remote_port: u16,
+    pub remote_id: String,
+    pub remote_xauth_id: Option<String>,
+    pub remote_epa_id: Option<String>,
+    pub initiator: Option<bool>,
+    pub initiator_spi: Option<String>,
+    pub responder_spi: Option<String>,
+    pub nat_local: Option<bool>,
+    pub nat_remote: Option<bool>,
+    pub nat_fake: Option<bool>,
+    pub nat_any: Option<bool>,
+    pub if_id_in: Option<String>,
+    pub if_id_out: Option<String>,
+    pub encr_alg: Option<String>,
+    pub encr_keysize: Option<String>,
+    pub integ_alg: Option<String>,
+    pub integ_keysize: Option<String>,
+    pub prf_alg: Option<String>,
+    pub dh_group: Option<String>,
+    pub established: u64,
+    pub rekey_time: u32,
+    pub reauth_time: Option<u32>,
+    pub local_vips: Option<Vec<String>>,
+    pub remote_vips: Option<Vec<String>>,
+    pub tasks_queued: Option<Vec<String>>,
+    pub tasks_active: Option<Vec<String>>,
+    pub tasks_passive: Option<Vec<String>>,
+    pub child_security_associations: HashMap<String, SecurityAssociationChild>,
+}
+
+
+impl IntoLabels for &SecurityAssociationChild {
+    fn into_labels(self) -> Vec<Label> {
+        vec![
+            (&("child_uniqueid", self.uniqueid.clone())).into(),
+            (&("child_reqid", self.reqid.clone())).into(),
+        ]
+    }
+}
+
+
 #[derive(Debug, Deserialize)]
 pub struct SecurityAssociationChild {
-    pub name:           String,
-    pub uniqueid:       String,
-    pub reqid:          String,
-    pub state:          String,
-    pub mode:           ChildSecurityAssociationMode,
-    pub protocol:       ChildSecurityAssociationProtocol,
-    pub encap:          Option<bool>,
-    pub spi_in:         String,
-    pub spi_out:        String,
-    pub cpi_in:         Option<String>,
-    pub cpi_out:        Option<String>,
-    pub mark_in:        Option<String>,
-    pub mark_mask_in:   Option<String>,
-    pub mark_out:       Option<String>,
-    pub mark_mask_out:  Option<String>,
-    pub if_id_in:       Option<String>,
-    pub if_id_out:      Option<String>,
-    pub encr_alg:       Option<String>,
-    pub encr_keysize:   Option<String>,
-    pub integ_alg:      Option<String>,
-    pub integ_keysize:  Option<String>,
-    pub prf_alg:        Option<String>,
-    pub dh_group:       Option<String>,
-    pub esn:            Option<u16>,
-    pub bytes_in:       u64,
-    pub packets_in:     u64,
-    pub use_in:         Option<u32>,
-    pub bytes_out:      u64,
-    pub packets_out:    u64,
-    pub use_out:        Option<u32>,
-    pub rekey_time:     Option<u32>,
-    pub life_time:      u32,
-    pub install_time:   u64,
-    pub local_ts:       Vec<String>,
-    pub remote_ts:      Vec<String>,
+    pub name: String,
+    pub uniqueid: String,
+    pub reqid: String,
+    pub state: String,
+    pub mode: ChildSecurityAssociationMode,
+    pub protocol: ChildSecurityAssociationProtocol,
+    pub encap: Option<bool>,
+    pub spi_in: String,
+    pub spi_out: String,
+    pub cpi_in: Option<String>,
+    pub cpi_out: Option<String>,
+    pub mark_in: Option<String>,
+    pub mark_mask_in: Option<String>,
+    pub mark_out: Option<String>,
+    pub mark_mask_out: Option<String>,
+    pub if_id_in: Option<String>,
+    pub if_id_out: Option<String>,
+    pub encr_alg: Option<String>,
+    pub encr_keysize: Option<String>,
+    pub integ_alg: Option<String>,
+    pub integ_keysize: Option<String>,
+    pub prf_alg: Option<String>,
+    pub dh_group: Option<String>,
+    pub esn: Option<u16>,
+    pub bytes_in: u64,
+    pub packets_in: u64,
+    pub use_in: Option<u32>,
+    pub bytes_out: u64,
+    pub packets_out: u64,
+    pub use_out: Option<u32>,
+    pub rekey_time: u32,
+    pub life_time: u32,
+    pub install_time: u64,
+    pub local_ts: Vec<String>,
+    pub remote_ts: Vec<String>,
 }
 
 pub type Certificates = HashMap<String, Cert>;
@@ -267,21 +300,25 @@ pub type NamedCertificate = (String, Cert);
 #[derive(Debug, Deserialize)]
 pub struct Cert {
     pub r#type: CertType,
-    pub flag:        X509CertFlag,
+    pub flag: X509CertFlag,
     pub has_privkey: Option<String>,
-    pub data:        String,
-    pub subject:     Option<String>,
-    pub not_before:  Option<String>,
-    pub not_after:   Option<String>,
+    pub data: String,
+    pub subject: Option<String>,
+    pub not_before: Option<String>,
+    pub not_after: Option<String>,
 }
 
 #[derive(Debug, Deserialize)]
 pub enum CertType {
     X509,
-    X509_AC,
-    X509_CRL,
-    OSCP_RESPONSE,
-    PUBKEY,
+    #[serde(alias = "X509_AC")]
+    X509AC,
+    #[serde(alias = "X509_CRL")]
+    X509CRL,
+    #[serde(alias = "OSCP_RESPONSE")]
+    OSCPResponse,
+    #[serde(alias = "PUBKEY")]
+    PubKey,
 }
 
 #[derive(Debug, Deserialize)]
@@ -298,35 +335,36 @@ pub type NamedAuthority = (String, Authority);
 
 #[derive(Debug, Deserialize)]
 pub struct Authority {
-    pub cacert:         String,
-    pub crl_uris:       Vec<String>,
-    pub ocsp_uris:      Vec<String>,
-    pub cert_uri_base:  String,
+    pub cacert: String,
+    pub crl_uris: Vec<String>,
+    pub ocsp_uris: Vec<String>,
+    pub cert_uri_base: String,
 }
 
 pub type Pools = HashMap<String, Pool>;
 
 pub type NamedPool = (String, Pool);
 
-#[derive(Debug,Deserialize)]
+#[derive(Debug, Deserialize)]
 pub struct Pool {
-    pub name:    String,
-    pub base:    String,
-    pub size:    u128,
-    pub online:  u128,
+    pub name: String,
+    pub base: String,
+    pub size: u128,
+    pub online: u128,
     pub offline: u128,
-    pub leases:  Option<HashMap<u16,PoolLease>>,
-}
-
-#[derive(Debug,Deserialize)]
-pub struct PoolLease {
-    pub address:  String,
-    pub identity: String,
-    pub status:   PoolLeaseStatus,
+    pub leases: Option<HashMap<u16, PoolLease>>,
 }
 
 #[derive(Debug, Deserialize)]
-pub enum PoolLeaseStatus {
-    online,
-    offline,
+pub struct PoolLease {
+    pub address: String,
+    pub identity: String,
+    pub status: PoolLeaseStatus,
+}
+
+#[derive(Debug, Deserialize)]
+#[serde(rename_all = "lowercase")]
+pub enum PoolLeaseStatus {
+    Online,
+    Offline,
 }