Skip to content

Commit eb7ba84

Browse files
author
Kile
committed
Major Grafana updates
1 parent 4bcfd1f commit eb7ba84

File tree

31 files changed

+2481
-551
lines changed

31 files changed

+2481
-551
lines changed

.github/workflows/api-test.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ jobs:
5454

5555
- name: Setup local cards
5656
run: "echo '[{\"id\": 0, \"name\": \"Name\", \"description\": \"One two\", \"image\": \"/image/image.png\", \"emoji\": \":pensive\", \"rank\": \"S\", \"limit\": 10, \"type\": \"monster\", \"available\": true}]' > cards.json"
57+
- name: Make update.sh executable
58+
run: chmod +x scripts/update.sh
5759
- name: Run clippy
5860
run: cargo clippy --all --all-features --tests -- -D warnings
5961
working-directory: api

alloy/config.alloy

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// ###############################
2+
// #### Metrics Configuration ####
3+
// ###############################
4+
5+
// Host Cadvisor on the Docker socket to expose container metrics.
6+
prometheus.exporter.cadvisor "example" {
7+
docker_host = "unix:///var/run/docker.sock"
8+
9+
storage_duration = "5m"
10+
}
11+
12+
13+
// Configure a prometheus.scrape component to collect cadvisor metrics.
14+
prometheus.scrape "scraper" {
15+
targets = prometheus.exporter.cadvisor.example.targets
16+
forward_to = [ prometheus.remote_write.demo.receiver ]
17+
18+
19+
scrape_interval = "10s"
20+
}
21+
22+
// Configure a prometheus.remote_write component to send metrics to a Prometheus server.
23+
prometheus.remote_write "demo" {
24+
endpoint {
25+
url = "http://prometheus:9090/api/v1/write"
26+
}
27+
}
28+
29+
// ###############################
30+
// #### Logging Configuration ####
31+
// ###############################
32+
33+
// Discover Docker containers and extract metadata.
34+
discovery.docker "linux" {
35+
host = "unix:///var/run/docker.sock"
36+
}
37+
38+
// Define a relabeling rule to create a service name from the container name.
39+
discovery.relabel "logs_integrations_docker" {
40+
targets = []
41+
42+
rule {
43+
source_labels = ["__meta_docker_container_name"]
44+
regex = "/(.*)"
45+
target_label = "service_name"
46+
}
47+
48+
}
49+
50+
51+
// Configure a loki.source.docker component to collect logs from Docker containers.
52+
loki.source.docker "default" {
53+
host = "unix:///var/run/docker.sock"
54+
targets = discovery.docker.linux.targets
55+
labels = {"platform" = "docker"}
56+
relabel_rules = discovery.relabel.logs_integrations_docker.rules
57+
forward_to = [loki.write.local.receiver]
58+
}
59+
60+
loki.write "local" {
61+
endpoint {
62+
url = "http://loki:3100/loki/api/v1/push"
63+
}
64+
}

api/Dockerfile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# Use an official Rust runtime as a parent image
2-
FROM rust:1.78 AS base
2+
FROM rust:1.87 AS base
33

44
ARG MYUID=1000
55
ARG MYGID=1000
@@ -8,6 +8,7 @@ ARG MYGID=1000
88
WORKDIR /app
99

1010
COPY api/ api/
11+
COPY scripts/ scripts/
1112

1213
# This is mainly for reading the config.json
1314
WORKDIR /app/api

api/src/main.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use routes::commands::get_commands;
1515
use routes::diagnostics::get_diagnostics;
1616
use routes::image::image;
1717
use routes::stats::get_stats;
18+
use routes::update::{update, update_cors};
1819
use routes::vote::register_vote;
1920

2021
use fairings::cors::Cors;
@@ -33,7 +34,9 @@ fn rocket() -> _ {
3334
image,
3435
get_diagnostics,
3536
get_cards,
36-
get_public_cards
37+
get_public_cards,
38+
update,
39+
update_cors,
3740
],
3841
)
3942
.attach(db::init())

api/src/routes/commands.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ static CACHE: OnceCell<HashMap<String, Category>> = OnceCell::const_new();
4343
pub async fn get_commands() -> Result<Json<HashMap<String, Category>>, BadRequest<Json<Value>>> {
4444
let commands = CACHE
4545
.get_or_try_init(|| async {
46-
let commands = make_request("commands", NoData {})
46+
let commands = make_request("commands", NoData {}, 0_u8)
4747
.await
4848
.context("Failed to get commands")?;
4949
// Parse the commands into a HashMap using the defined structs and rocket

api/src/routes/common/utils.rs

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use serde::{Deserialize, Serialize};
22
use serde_json::{json, Value};
33
use tokio::task;
4-
use zmq::{Context, REQ};
4+
use zmq::{Context, SocketType::DEALER};
55

66
use rocket::response::status::BadRequest;
77
use rocket::serde::json::Json;
@@ -27,9 +27,10 @@ pub struct NoData {}
2727
pub fn make_request_inner<'a, T: Serialize + Deserialize<'a>>(
2828
route: &str,
2929
data: T,
30+
first_bit: u8,
3031
) -> Result<String, zmq::Error> {
3132
let ctx = Context::new();
32-
let socket = ctx.socket(REQ).unwrap();
33+
let socket = ctx.socket(DEALER).unwrap();
3334

3435
assert!(socket.set_linger(0).is_ok());
3536
// Omg this function...
@@ -53,6 +54,7 @@ pub fn make_request_inner<'a, T: Serialize + Deserialize<'a>>(
5354
// Get route from environment variable
5455
let address = std::env::var("ZMQ_ADDRESS").unwrap_or("tcp://0.0.0.0:3210".to_string());
5556
assert!(socket.connect(&address).is_ok());
57+
assert!(socket.set_identity("api-client".as_bytes()).is_ok());
5658

5759
let request_data = RequestData {
5860
route: route.to_owned(),
@@ -62,21 +64,25 @@ pub fn make_request_inner<'a, T: Serialize + Deserialize<'a>>(
6264
let mut msg = zmq::Message::new();
6365
let request_json = serde_json::to_string(&request_data).unwrap();
6466

65-
socket.send(request_json.as_bytes(), 0).unwrap();
66-
let result = socket.recv(&mut msg, 0);
67+
let mut data = vec![first_bit];
68+
data.extend_from_slice(request_json.as_bytes());
69+
socket.send("", zmq::SNDMORE)?; // delimiter
70+
socket.send(data, 0)?;
71+
72+
socket.recv(&mut msg, 0)?; // Receive acknowledgment from the server
6773

6874
// Close the socket
6975
assert!(socket.disconnect(&address).is_ok());
7076

71-
result?; // Return error if error (Rust is cool)
72-
Ok(msg.as_str().unwrap().to_string())
77+
Ok(msg.as_str().unwrap_or("").to_string())
7378
}
7479

7580
pub async fn make_request<T: Serialize + std::marker::Send + Deserialize<'static> + 'static>(
7681
route: &'static str,
7782
data: T,
83+
first_bit: u8,
7884
) -> Result<String, zmq::Error> {
79-
task::spawn_blocking(move || make_request_inner(route, data))
85+
task::spawn_blocking(move || make_request_inner(route, data, first_bit))
8086
.await
8187
.unwrap()
8288
}

api/src/routes/diagnostics.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ pub async fn get_diagnostics(
6868
None => insert(&mut formatted),
6969
};
7070
let start_time = SystemTime::now();
71-
let res = make_request("heartbeat", NoData {}).await;
71+
let res = make_request("heartbeat", NoData {}, 0_u8).await;
7272
let success = res.is_ok();
7373
let response_time = match success {
7474
true => Some(start_time.elapsed().unwrap().as_secs_f64() * 1000.0),

api/src/routes/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@ pub mod common;
44
pub mod diagnostics;
55
pub mod image;
66
pub mod stats;
7+
pub mod update;
78
pub mod vote;

api/src/routes/stats.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ pub struct Stats {
1515

1616
#[get("/stats")]
1717
pub async fn get_stats() -> Result<Json<Stats>, BadRequest<Json<Value>>> {
18-
let stats = make_request("stats", NoData {})
18+
let stats = make_request("stats", NoData {}, 0_u8)
1919
.await
2020
.context("Failed to get stats")?;
2121
let stats: Stats = serde_json::from_str(&stats).unwrap();

api/src/routes/update.rs

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
use super::common::keys::ApiKey;
2+
3+
use serde_json::Value;
4+
5+
use super::common::utils::{make_request, ResultExt};
6+
use rocket::response::status::BadRequest;
7+
use rocket::serde::json::Json;
8+
9+
#[derive(FromFormField, Debug)]
10+
pub enum TestOption {
11+
Pass,
12+
Fail,
13+
}
14+
15+
struct CommandResult {
16+
exit_code: i32,
17+
output: String,
18+
}
19+
20+
async fn send_command_and_get_result(
21+
command: String,
22+
first_bit: u8,
23+
) -> Result<CommandResult, BadRequest<Json<Value>>> {
24+
// List files in the directory
25+
let response = make_request("update", command, first_bit)
26+
.await
27+
.context("Failed to get command output")?;
28+
29+
let exit_code = response
30+
.split("\n")
31+
.next()
32+
.unwrap_or("EXIT_CODE=0")
33+
.to_string();
34+
let parsed_exit_code = exit_code
35+
.split('=')
36+
.nth(1)
37+
.unwrap_or("0")
38+
.parse::<i32>()
39+
.unwrap_or(0);
40+
let output = response
41+
.split("\n")
42+
.skip(1)
43+
.collect::<Vec<&str>>()
44+
.join("\n");
45+
46+
// Remove OUTPUT= prefix if it exists
47+
let output = if let Some(stripped) = output.strip_prefix("OUTPUT=") {
48+
stripped.to_string()
49+
} else {
50+
output
51+
};
52+
53+
Ok(CommandResult {
54+
exit_code: parsed_exit_code,
55+
output,
56+
})
57+
}
58+
59+
#[post("/update?<force>&<test>")]
60+
pub async fn update(
61+
_key: ApiKey,
62+
force: Option<bool>,
63+
test: Option<TestOption>,
64+
) -> Result<Json<Value>, BadRequest<Json<Value>>> {
65+
// Runs a shell script which is in scripts/update.sh
66+
let command = "scripts/update.sh".to_owned()
67+
+ match test {
68+
Some(TestOption::Pass) => " --test-pass",
69+
Some(TestOption::Fail) => " --test-fail",
70+
None => "",
71+
}
72+
+ if force.unwrap_or(false) {
73+
" --force"
74+
} else {
75+
""
76+
};
77+
78+
let output = send_command_and_get_result(command, 1_u8).await?;
79+
80+
if output.exit_code != 0 {
81+
return Err(BadRequest(Json(
82+
serde_json::json!({"error": format!("Update script failed: {}", output.output)}),
83+
)));
84+
}
85+
86+
Ok(Json(
87+
serde_json::json!({"status": format!("Success: {}", output.output)}),
88+
))
89+
}
90+
91+
#[options("/update")] // Sucks I have to do this
92+
pub fn update_cors() -> Json<Value> {
93+
Json(serde_json::json!({
94+
"status": "CORS preflight request"
95+
}))
96+
}

0 commit comments

Comments
 (0)