diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml
index 3ab895f..e541386 100644
--- a/.github/workflows/master.yml
+++ b/.github/workflows/master.yml
@@ -28,7 +28,7 @@ jobs:
- name: Setup Rust
uses: actions-rs/toolchain@v1
with:
- toolchain: nightly
+ toolchain: stable
override: true
components: rustfmt, clippy
@@ -49,15 +49,3 @@ jobs:
with:
command: test
args: --workspace --no-fail-fast
- env:
- CARGO_INCREMENTAL: '0'
- RUSTFLAGS: '-Zprofile -Ccodegen-units=1 -Cinline-threshold=0 -Clink-dead-code -Coverflow-checks=off -Cpanic=abort -Zpanic_abort_tests'
- RUSTDOCFLAGS: '-Zprofile -Ccodegen-units=1 -Cinline-threshold=0 -Clink-dead-code -Coverflow-checks=off -Cpanic=abort -Zpanic_abort_tests'
-
- - name: Generate coverage
- id: coverage
- uses: actions-rs/grcov@v0.1
-
- - uses: codecov/codecov-action@v3
- with:
- files: ${{ steps.coverage.outputs.report }}
\ No newline at end of file
diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml
index 348c20d..0056f54 100644
--- a/.github/workflows/pr.yml
+++ b/.github/workflows/pr.yml
@@ -25,7 +25,7 @@ jobs:
- name: Setup Rust
uses: actions-rs/toolchain@v1
with:
- toolchain: nightly
+ toolchain: stable
override: true
components: rustfmt, clippy
@@ -46,15 +46,3 @@ jobs:
with:
command: test
args: --workspace --no-fail-fast
- env:
- CARGO_INCREMENTAL: '0'
- RUSTFLAGS: '-Zprofile -Ccodegen-units=1 -Cinline-threshold=0 -Clink-dead-code -Coverflow-checks=off -Cpanic=abort -Zpanic_abort_tests'
- RUSTDOCFLAGS: '-Zprofile -Ccodegen-units=1 -Cinline-threshold=0 -Clink-dead-code -Coverflow-checks=off -Cpanic=abort -Zpanic_abort_tests'
-
- - name: Generate coverage
- id: coverage
- uses: actions-rs/grcov@v0.1
-
- - uses: codecov/codecov-action@v3
- with:
- files: ${{ steps.coverage.outputs.report }}
diff --git a/.idea/discord.xml b/.idea/discord.xml
new file mode 100644
index 0000000..66c4b97
--- /dev/null
+++ b/.idea/discord.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index ef5f323..07076a9 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -8,16 +8,17 @@ Bear in mind to keep your contributions under the [Code of Conduct](./.github/CO
## Bug report, ideas, and suggestion
-The [issues](https://github.com/teknologi-umum/polarite/issues) page is a great way to communicate to us.
-Other than that, we have a [Telegram group](https://t.me/teknologi_umum_v2) that you can discuss your ideas into.
+The [issues](https://github.com/teknologi-umum/polarite/issues) page is a great way to communicate to us.
+Other than that, we have a [Telegram group](https://t.me/teknologi_umum_v2) that you can discuss your ideas into.
If you're not an Indonesian speaker, it's 100% fine to talk in English there.
-Please make sure that the issue you're creating is in as much detail as possible. Poor communication might lead to a big mistake, we're trying to avoid that.
+Please make sure that the issue you're creating is in as much detail as possible. Poor communication might lead to a big
+mistake, we're trying to avoid that.
## Pull request
-**A big heads up before you're writing a breaking change code or a new feature: Please open up an
-[issue](https://github.com/teknologi-umum/polarite/issues) regarding what you're working on, or just talk in the
+**A big heads up before you're writing a breaking change code or a new feature: Please open up an
+[issue](https://github.com/teknologi-umum/polarite/issues) regarding what you're working on, or just talk in the
[Telegram group](https://t.me/teknologi_umum_v2).**
### Prerequisites
@@ -29,12 +30,13 @@ You will need a few things to get things working:
### Getting Started
-1. [Fork](https://help.github.com/articles/fork-a-repo/) this repository to your own Github account and [clone](https://help.github.com/articles/cloning-a-repository/) it to your local machine.
+1. [Fork](https://help.github.com/articles/fork-a-repo/) this repository to your own Github account
+ and [clone](https://help.github.com/articles/cloning-a-repository/) it to your local machine.
2. Run `cargo update` to install the dependencies needed.
3. Run `cargo run` to start the development application.
4. Have fun!
-You are encouraged to use [Conventional Commit](https://www.conventionalcommits.org/en/v1.0.0-beta.2/)
+You are encouraged to use [Conventional Commit](https://www.conventionalcommits.org/en/v1.0.0-beta.2/)
for your commit message. But it's not really compulsory.
### Testing your change
diff --git a/Cargo.lock b/Cargo.lock
index 9946c0e..380d558 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -17,6 +17,15 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
+[[package]]
+name = "aho-corasick"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c378d78423fdad8089616f827526ee33c19f2fddbd5de1629152c9593ba4783"
+dependencies = [
+ "memchr",
+]
+
[[package]]
name = "anstream"
version = "0.3.2"
@@ -83,7 +92,7 @@ checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.16",
]
[[package]]
@@ -156,12 +165,6 @@ dependencies = [
"rustc-demangle",
]
-[[package]]
-name = "base64"
-version = "0.13.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
-
[[package]]
name = "base64"
version = "0.21.0"
@@ -240,7 +243,7 @@ dependencies = [
"heck",
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.16",
]
[[package]]
@@ -340,6 +343,18 @@ dependencies = [
"libc",
]
+[[package]]
+name = "fastping-rs"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bed7f653ac8d713246f65291b25523c37d91e47056b26161cd6ed0e11f3815ff"
+dependencies = [
+ "log",
+ "pnet",
+ "pnet_macros_support",
+ "rand",
+]
+
[[package]]
name = "fastrand"
version = "1.9.0"
@@ -447,7 +462,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.16",
]
[[package]]
@@ -507,6 +522,12 @@ version = "0.27.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad0a93d233ebf96623465aad4046a8d3aa4da22d4f4beba5388838c8a434bbb4"
+[[package]]
+name = "glob"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
+
[[package]]
name = "h2"
version = "0.3.19"
@@ -687,6 +708,15 @@ version = "2.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12b6ee2129af8d4fb011108c73d99a1b83a85977f23b82460c0ae2e25bb4b57f"
+[[package]]
+name = "ipnetwork"
+version = "0.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4088d739b183546b239688ddbc79891831df421773df95e236daf7867866d355"
+dependencies = [
+ "serde",
+]
+
[[package]]
name = "is-terminal"
version = "0.4.7"
@@ -873,7 +903,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.16",
]
[[package]]
@@ -964,7 +994,7 @@ dependencies = [
"pest_meta",
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.16",
]
[[package]]
@@ -995,7 +1025,7 @@ checksum = "39407670928234ebc5e6e580247dd567ad73a3578460c5990f9503df207e8f07"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.16",
]
[[package]]
@@ -1016,6 +1046,94 @@ version = "0.3.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
+[[package]]
+name = "pnet"
+version = "0.28.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b6d2a0409666964722368ef5fb74b9f93fac11c18bef3308693c16c6733f103"
+dependencies = [
+ "ipnetwork",
+ "pnet_base",
+ "pnet_datalink",
+ "pnet_packet",
+ "pnet_sys",
+ "pnet_transport",
+]
+
+[[package]]
+name = "pnet_base"
+version = "0.28.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "25488cd551a753dcaaa6fffc9f69a7610a412dd8954425bf7ffad5f7d1156fb8"
+
+[[package]]
+name = "pnet_datalink"
+version = "0.28.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4d1f8ab1ef6c914cf51dc5dfe0be64088ea5f3b08bbf5a31abc70356d271198"
+dependencies = [
+ "ipnetwork",
+ "libc",
+ "pnet_base",
+ "pnet_sys",
+ "winapi",
+]
+
+[[package]]
+name = "pnet_macros"
+version = "0.28.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "30490e0852e58402b8fae0d39897b08a24f493023a4d6cf56b2e30f31ed57548"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "regex",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "pnet_macros_support"
+version = "0.28.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4714e10f30cab023005adce048f2d30dd4ac4f093662abf2220855655ef8f90"
+dependencies = [
+ "pnet_base",
+]
+
+[[package]]
+name = "pnet_packet"
+version = "0.28.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8588067671d03c9f4254b2e66fecb4d8b93b5d3e703195b84f311cd137e32130"
+dependencies = [
+ "glob",
+ "pnet_base",
+ "pnet_macros",
+ "pnet_macros_support",
+]
+
+[[package]]
+name = "pnet_sys"
+version = "0.28.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9a3f32b0df45515befd19eed04616f6b56a488da92afc61164ef455e955f07f"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "pnet_transport"
+version = "0.28.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "932b2916d693bcc5fa18443dc99142e0a6fd31a6ce75a511868f7174c17e2bce"
+dependencies = [
+ "libc",
+ "pnet_base",
+ "pnet_packet",
+ "pnet_sys",
+]
+
[[package]]
name = "ppv-lite86"
version = "0.2.17"
@@ -1094,6 +1212,8 @@ version = "1.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81ca098a9821bd52d6b24fd8b10bd081f47d39c22778cafaa75a2857a62c6390"
dependencies = [
+ "aho-corasick",
+ "memchr",
"regex-syntax",
]
@@ -1109,7 +1229,7 @@ version = "0.11.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55"
dependencies = [
- "base64 0.21.0",
+ "base64",
"bytes",
"encoding_rs",
"futures-core",
@@ -1151,6 +1271,7 @@ dependencies = [
"roselite-request",
"roselite-server",
"sentry",
+ "sentry-tracing",
"tokio",
]
@@ -1177,9 +1298,12 @@ name = "roselite-request"
version = "0.1.0"
dependencies = [
"anyhow",
+ "fastping-rs",
"reqwest",
"roselite-common",
"roselite-config",
+ "sentry",
+ "sentry-tracing",
"tokio",
]
@@ -1284,9 +1408,9 @@ checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed"
[[package]]
name = "sentry"
-version = "0.31.3"
+version = "0.31.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "de31c6e03322af2175d3c850c5b5e11efcadc01948cd1fb7b5ad0a7c7b6c7ff2"
+checksum = "2e95efd0cefa32028cdb9766c96de71d96671072f9fb494dc9fb84c0ef93e52b"
dependencies = [
"httpdate",
"native-tls",
@@ -1304,9 +1428,9 @@ dependencies = [
[[package]]
name = "sentry-anyhow"
-version = "0.31.3"
+version = "0.31.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4df1501f58a7821af9d3bd11435d1c0c74121a6e6931f7615592fdfa85a5fb84"
+checksum = "27c30fb3ec036e3210a51d102890e734be62951d876e626205db8412a2a57c1a"
dependencies = [
"anyhow",
"sentry-backtrace",
@@ -1315,9 +1439,9 @@ dependencies = [
[[package]]
name = "sentry-backtrace"
-version = "0.31.3"
+version = "0.31.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "264e3ad27da3d1ad81b499dbcceae0a50e0e6ffc4b65b93f47d5180d46827644"
+checksum = "6ac2bac6f310c4c4c4bb094d1541d32ae497f8c5c23405e85492cefdfe0971a9"
dependencies = [
"backtrace",
"once_cell",
@@ -1327,9 +1451,9 @@ dependencies = [
[[package]]
name = "sentry-contexts"
-version = "0.31.3"
+version = "0.31.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7144590f7950647e4df5bd95f234c3aa29124729c54bd2457e1224d701d1a91c"
+checksum = "6c3e17295cecdbacf66c5bd38d6e1147e09e1e9d824d2d5341f76638eda02a3a"
dependencies = [
"hostname",
"libc",
@@ -1341,9 +1465,9 @@ dependencies = [
[[package]]
name = "sentry-core"
-version = "0.31.3"
+version = "0.31.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "35614ecf115f55d93583baa02a85cb63acb6567cf91b17690d1147bac1739ca4"
+checksum = "8339474f587f36cb110fa1ed1b64229eea6d47b0b886375579297b7e47aeb055"
dependencies = [
"once_cell",
"rand",
@@ -1354,9 +1478,9 @@ dependencies = [
[[package]]
name = "sentry-debug-images"
-version = "0.31.3"
+version = "0.31.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "53c4288a1b255e6ff55f111d2e14a48d369da76e86fae15a00ee26a371d82ad4"
+checksum = "1c11e7d2b809b06497a18a2e60f513206462ae2db27081dfb7be9ade1f329cc8"
dependencies = [
"findshlibs",
"once_cell",
@@ -1365,9 +1489,9 @@ dependencies = [
[[package]]
name = "sentry-panic"
-version = "0.31.3"
+version = "0.31.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0a941028a24baf0a5a994d8a39670cecc72a61971bb0155f771537447a46211a"
+checksum = "875b69f506da75bd664029eafb05f8934297d2990192896d17325f066bd665b7"
dependencies = [
"sentry-backtrace",
"sentry-core",
@@ -1375,9 +1499,9 @@ dependencies = [
[[package]]
name = "sentry-tracing"
-version = "0.31.3"
+version = "0.31.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eec56ebafd7cfc1175bccdf277be582ccc3308b8c353dca5831261a967a6e28c"
+checksum = "89feead9bdd116f8035e89567651340fc382db29240b6c55ef412078b08d1aa3"
dependencies = [
"sentry-backtrace",
"sentry-core",
@@ -1387,9 +1511,9 @@ dependencies = [
[[package]]
name = "sentry-types"
-version = "0.31.3"
+version = "0.31.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9c56f616602a3b282bf4b4e8e5b4d10bcf9412a987df91c592b95a1f6ef1ee43"
+checksum = "99dc599bd6646884fc403d593cdcb9816dd67c50cff3271c01ff123617908dcd"
dependencies = [
"debugid",
"getrandom",
@@ -1419,7 +1543,7 @@ checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.16",
]
[[package]]
@@ -1527,6 +1651,17 @@ version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
+[[package]]
+name = "syn"
+version = "1.0.109"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
[[package]]
name = "syn"
version = "2.0.16"
@@ -1574,7 +1709,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.16",
]
[[package]]
@@ -1646,7 +1781,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.16",
]
[[package]]
@@ -1822,11 +1957,11 @@ checksum = "1865806a559042e51ab5414598446a5871b561d21b6764f2eabb0dd481d880a6"
[[package]]
name = "ureq"
-version = "2.6.2"
+version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "338b31dd1314f68f3aabf3ed57ab922df95ffcd902476ca7ba3c4ce7b908c46d"
+checksum = "0b11c96ac7ee530603dcdf68ed1557050f374ce55a5a07193ebf8cbc9f8927e9"
dependencies = [
- "base64 0.13.1",
+ "base64",
"log",
"native-tls",
"once_cell",
@@ -1916,7 +2051,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.16",
"wasm-bindgen-shared",
]
@@ -1950,7 +2085,7 @@ checksum = "e128beba882dd1eb6200e1dc92ae6c5dbaa4311aa7bb211ca035779e5efc39f8"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.16",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
diff --git a/Dockerfile b/Dockerfile
index 340f821..cb379fe 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,5 +1,7 @@
FROM rust:1.69.0-bullseye AS builder
WORKDIR /app
+ENV DEBIAN_FRONTEND=noninteractive
+RUN apt-get update && apt-get install -y pkg-config libudev-dev perl libssl-dev
COPY . .
RUN cargo build --release
diff --git a/roselite-config/src/lib.rs b/roselite-config/src/lib.rs
index 8c2d330..2e7e00f 100644
--- a/roselite-config/src/lib.rs
+++ b/roselite-config/src/lib.rs
@@ -1,32 +1,54 @@
use std::collections::BTreeMap;
use std::fs::File;
use std::io::Read;
+use std::str::FromStr;
use anyhow::{Error, Result};
use serde::Deserialize;
+#[derive(Debug, Deserialize, Clone, PartialEq)]
+pub enum MonitorType {
+ HTTP,
+ ICMP,
+}
+
+impl FromStr for MonitorType {
+ type Err = ();
+
+ fn from_str(s: &str) -> std::result::Result {
+ match s {
+ "HTTP" => Ok(MonitorType::HTTP),
+ "ICMP" => Ok(MonitorType::ICMP),
+ _ => Err(()),
+ }
+ }
+}
+
/// Monitor defines a single monitor configuration
-#[derive(Deserialize, Clone)]
+#[derive(Deserialize, Clone, Debug)]
pub struct Monitor {
+ pub monitor_type: MonitorType,
pub push_url: String,
- pub monitor_url: String,
+ pub monitor_target: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub request_headers: Option>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub skip_tls_verify: Option,
}
-#[derive(Deserialize, Clone)]
+#[derive(Deserialize, Clone, Debug)]
pub struct ErrorReporting {
pub sentry_dsn: String,
}
-#[derive(Deserialize, Clone)]
+#[derive(Deserialize, Clone, Debug)]
pub struct ServerConfig {
pub listen_address: String,
pub upstream_kuma: Option,
}
/// Configuration sets a global configuration for the application.
-#[derive(Deserialize, Clone)]
+#[derive(Deserialize, Clone, Debug)]
pub struct Configuration {
pub error_reporting: Option,
pub server: Option,
@@ -77,23 +99,27 @@ impl Configuration {
#[cfg(test)]
mod tests {
- use crate::Configuration;
- use anyhow::Result;
use std::env::temp_dir;
use std::fs::File;
use std::io::Write;
+ use anyhow::Result;
+
+ use crate::{Configuration, MonitorType};
+
#[test]
fn parse_toml_configuration() {
let configuration = r#"[[monitors]]
+monitor_type = "HTTP"
push_url = "https://your-uptime-kuma.com/api/push/Eq15E23yc3"
-monitor_url = "https://github.com/healthz""#;
+monitor_target = "https://github.com/healthz""#;
let parsed_configuration = Configuration::from_toml(configuration);
assert_eq!(parsed_configuration.monitors.len(), 1);
if let Some(first_monitor) = parsed_configuration.monitors.first() {
- assert_eq!(first_monitor.monitor_url, "https://github.com/healthz");
+ assert_eq!(first_monitor.monitor_type, MonitorType::HTTP);
+ assert_eq!(first_monitor.monitor_target, "https://github.com/healthz");
assert_eq!(
first_monitor.push_url,
"https://your-uptime-kuma.com/api/push/Eq15E23yc3"
@@ -104,14 +130,16 @@ monitor_url = "https://github.com/healthz""#;
#[test]
fn parse_yaml_configuration() {
let configuration = r#"monitors:
- - push_url: "https://your-uptime-kuma.com/api/push/Eq15E23yc3"
- monitor_url: "https://github.com/healthz""#;
+ - monitor_type: "HTTP"
+ push_url: "https://your-uptime-kuma.com/api/push/Eq15E23yc3"
+ monitor_target: "https://github.com/healthz""#;
let parsed_configuration = Configuration::from_yaml(configuration);
assert_eq!(parsed_configuration.monitors.len(), 1);
if let Some(first_monitor) = parsed_configuration.monitors.first() {
- assert_eq!(first_monitor.monitor_url, "https://github.com/healthz");
+ assert_eq!(first_monitor.monitor_type, MonitorType::HTTP);
+ assert_eq!(first_monitor.monitor_target, "https://github.com/healthz");
assert_eq!(
first_monitor.push_url,
"https://your-uptime-kuma.com/api/push/Eq15E23yc3"
@@ -124,8 +152,9 @@ monitor_url = "https://github.com/healthz""#;
let configuration = r#"{
"monitors": [
{
+ "monitor_type": "HTTP",
"push_url": "https://your-uptime-kuma.com/api/push/Eq15E23yc3",
- "monitor_url": "https://github.com/healthz"
+ "monitor_target": "https://github.com/healthz"
}
]
}"#;
@@ -134,7 +163,8 @@ monitor_url = "https://github.com/healthz""#;
assert_eq!(parsed_configuration.monitors.len(), 1);
if let Some(first_monitor) = parsed_configuration.monitors.first() {
- assert_eq!(first_monitor.monitor_url, "https://github.com/healthz");
+ assert_eq!(first_monitor.monitor_type, MonitorType::HTTP);
+ assert_eq!(first_monitor.monitor_target, "https://github.com/healthz");
assert_eq!(
first_monitor.push_url,
"https://your-uptime-kuma.com/api/push/Eq15E23yc3"
@@ -155,8 +185,10 @@ sentry_dsn = "https://sentry.io"
listen_address = "127.0.0.1:8321"
[[monitors]]
+monitor_type = "ICMP"
push_url = "https://your-uptime-kuma.com/api/push/Eq15E23yc3"
-monitor_url = "https://github.com/healthz""#;
+monitor_target = "https://github.com/healthz"
+"#;
let _ = file.write(configuration.as_bytes())?;
file.flush()?;
@@ -178,7 +210,8 @@ monitor_url = "https://github.com/healthz""#;
assert_eq!(parsed_configuration.monitors.len(), 1);
if let Some(first_monitor) = parsed_configuration.monitors.first() {
- assert_eq!(first_monitor.monitor_url, "https://github.com/healthz");
+ assert_eq!(first_monitor.monitor_type, MonitorType::ICMP);
+ assert_eq!(first_monitor.monitor_target, "https://github.com/healthz");
assert_eq!(
first_monitor.push_url,
"https://your-uptime-kuma.com/api/push/Eq15E23yc3"
diff --git a/roselite-request/Cargo.toml b/roselite-request/Cargo.toml
index 037aa48..0a6d49e 100644
--- a/roselite-request/Cargo.toml
+++ b/roselite-request/Cargo.toml
@@ -9,5 +9,8 @@ edition = "2021"
reqwest = { version = "0.11" }
tokio = { version = "1", features = ["full"] }
anyhow = "1.0.71"
+fastping-rs = "0.2"
+sentry = { version = "0.31", features = ["default", "anyhow"] }
+sentry-tracing = { version = "0.31" }
roselite-config = { path = "../roselite-config" }
roselite-common = { path = "../roselite-common" }
\ No newline at end of file
diff --git a/roselite-request/src/bonk_caller.rs b/roselite-request/src/bonk_caller.rs
new file mode 100644
index 0000000..b2e9248
--- /dev/null
+++ b/roselite-request/src/bonk_caller.rs
@@ -0,0 +1,33 @@
+use anyhow::Result;
+use roselite_common::heartbeat::{Heartbeat, HeartbeatStatus};
+use roselite_config::Monitor;
+
+use crate::RequestCaller;
+
+#[derive(Clone)]
+/// BonKCaller is a mock or empty struct that's implement
+/// RequestCaller. Do not use this as a normal caller transport
+/// unless you know what you're doing.
+pub struct BonkCaller {}
+
+impl BonkCaller {
+ pub fn new() -> Self {
+ BonkCaller {}
+ }
+}
+
+impl Default for BonkCaller {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+impl RequestCaller for BonkCaller {
+ fn call(&self, _monitor: Monitor) -> Result {
+ Ok(Heartbeat {
+ msg: "OK".to_string(),
+ status: HeartbeatStatus::Up,
+ ping: 0,
+ })
+ }
+}
diff --git a/roselite-request/src/http_caller.rs b/roselite-request/src/http_caller.rs
new file mode 100644
index 0000000..a648c57
--- /dev/null
+++ b/roselite-request/src/http_caller.rs
@@ -0,0 +1,79 @@
+use std::time::Duration;
+
+use anyhow::Result;
+use reqwest::blocking::Client;
+use reqwest::{Method, StatusCode};
+use tokio::time::Instant;
+
+use roselite_common::heartbeat::{Heartbeat, HeartbeatStatus};
+use roselite_config::Monitor;
+
+use crate::RequestCaller;
+
+#[derive(Clone)]
+pub struct HttpCaller {
+ client: Client,
+}
+
+impl HttpCaller {
+ pub fn new() -> Self {
+ HttpCaller {
+ client: Client::builder()
+ .user_agent("Roselite/1.0")
+ .build()
+ .unwrap(),
+ }
+ }
+}
+
+impl Default for HttpCaller {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+impl RequestCaller for HttpCaller {
+ fn call(&self, monitor: Monitor) -> Result {
+ // Retrieve the currently running span
+ let parent_span = sentry::configure_scope(|scope| scope.get_span());
+
+ let span: sentry::TransactionOrSpan = match &parent_span {
+ Some(parent) => parent
+ .start_child("http_caller.call", "Call target HTTP request")
+ .into(),
+ None => {
+ let ctx =
+ sentry::TransactionContext::new("Call target HTTP request", "http_caller.call");
+ sentry::start_transaction(ctx).into()
+ }
+ };
+
+ let current_instant = Instant::now();
+ let response = self
+ .client
+ .request(Method::GET, monitor.monitor_target.as_str())
+ .timeout(Duration::from_secs(30))
+ .send()?;
+
+ let elapsed: Duration = current_instant.elapsed();
+
+ let status_code: StatusCode = response.status();
+ let mut ok = true;
+ // everything from 2xx-3xx is considered ok
+ if status_code >= StatusCode::BAD_REQUEST {
+ ok = false;
+ }
+
+ span.finish();
+
+ Ok(Heartbeat {
+ msg: "OK".to_string(),
+ status: if ok {
+ HeartbeatStatus::Up
+ } else {
+ HeartbeatStatus::Down
+ },
+ ping: elapsed.as_millis(),
+ })
+ }
+}
diff --git a/roselite-request/src/icmp_caller.rs b/roselite-request/src/icmp_caller.rs
new file mode 100644
index 0000000..9c94daa
--- /dev/null
+++ b/roselite-request/src/icmp_caller.rs
@@ -0,0 +1,82 @@
+use std::time::Duration;
+
+use anyhow::{Error, Result};
+use fastping_rs::PingResult::{Idle, Receive};
+use fastping_rs::Pinger;
+
+use roselite_common::heartbeat::{Heartbeat, HeartbeatStatus};
+use roselite_config::Monitor;
+
+use crate::RequestCaller;
+
+#[derive(Debug, Clone)]
+pub struct IcmpCaller {}
+
+impl IcmpCaller {
+ pub fn new() -> Self {
+ IcmpCaller {}
+ }
+}
+
+impl Default for IcmpCaller {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+impl RequestCaller for IcmpCaller {
+ fn call(&self, monitor: Monitor) -> Result {
+ // Retrieve the currently running span
+ let parent_span = sentry::configure_scope(|scope| scope.get_span());
+
+ let span: sentry::TransactionOrSpan = match &parent_span {
+ Some(parent) => parent
+ .start_child("icmp_caller.call", "Call target ICMP request")
+ .into(),
+ None => {
+ let ctx =
+ sentry::TransactionContext::new("Call target ICMP request", "icmp_caller.call");
+ sentry::start_transaction(ctx).into()
+ }
+ };
+
+ let (pinger, results) = Pinger::new(None, None).unwrap();
+
+ pinger.add_ipaddr(monitor.monitor_target.as_str());
+ pinger.run_pinger();
+
+ let mut ok = true;
+ let mut ping_duration = Duration::from_secs(0);
+ for _ in 0..4 {
+ match results.recv() {
+ Ok(result) => match result {
+ Idle { addr: _addr } => {
+ ok = false;
+ }
+ Receive { addr: _addr, rtt } => {
+ ok = true;
+ ping_duration = rtt;
+ }
+ },
+ Err(err) => {
+ span.finish();
+ return Err(Error::from(err));
+ }
+ }
+ }
+
+ pinger.stop_pinger();
+
+ span.finish();
+
+ Ok(Heartbeat {
+ msg: "OK".to_string(),
+ status: if ok {
+ HeartbeatStatus::Up
+ } else {
+ HeartbeatStatus::Down
+ },
+ ping: ping_duration.as_millis(),
+ })
+ }
+}
diff --git a/roselite-request/src/lib.rs b/roselite-request/src/lib.rs
index e9c14c4..049d8c3 100644
--- a/roselite-request/src/lib.rs
+++ b/roselite-request/src/lib.rs
@@ -2,89 +2,142 @@ use std::time::Duration;
use anyhow::Result;
use reqwest::{Client, Method, StatusCode, Url};
-use tokio::time::Instant;
-use roselite_common::heartbeat::{Heartbeat, HeartbeatStatus};
-use roselite_config::Monitor;
+use roselite_common::heartbeat::Heartbeat;
+use roselite_config::{Monitor, MonitorType};
-pub async fn call_monitor_endpoint(monitor: Monitor) -> Result {
- let monitor_client = Client::builder().user_agent("Roselite/1.0").build()?;
+use crate::bonk_caller::BonkCaller;
+use crate::http_caller::HttpCaller;
+use crate::icmp_caller::IcmpCaller;
- let current_instant = Instant::now();
- let response = monitor_client
- .request(Method::GET, monitor.monitor_url.as_str())
- .timeout(Duration::from_secs(30))
- .send()
- .await?;
+mod bonk_caller;
+pub mod http_caller;
+pub mod icmp_caller;
- let elapsed: Duration = current_instant.elapsed();
+pub trait RequestCaller: Send + Sync {
+ fn call(&self, monitor: Monitor) -> Result;
+}
- let status_code: StatusCode = response.status();
- let mut ok = true;
- // everything from 2xx-3xx is considered ok
- if status_code >= StatusCode::BAD_REQUEST {
- ok = false;
- }
+pub struct RoseliteRequest {
+ http_caller: Box,
+ icmp_caller: Box,
+}
+
+unsafe impl Send for RoseliteRequest {}
- Ok(Heartbeat {
- msg: "OK".to_string(),
- status: if ok {
- HeartbeatStatus::Up
- } else {
- HeartbeatStatus::Down
- },
- ping: elapsed.as_millis(),
- })
+unsafe impl Sync for RoseliteRequest {}
+
+impl Default for RoseliteRequest {
+ fn default() -> Self {
+ RoseliteRequest {
+ http_caller: Box::new(BonkCaller::new()),
+ icmp_caller: Box::new(BonkCaller::new()),
+ }
+ }
}
-pub async fn call_kuma_endpoint(upstream_url: String, heartbeat: Heartbeat) -> Result<()> {
- let push_client = Client::builder().user_agent("Roselite/1.0").build()?;
-
- let mut push_url = Url::parse(upstream_url.as_str())?;
- push_url
- .query_pairs_mut()
- .append_pair("msg", heartbeat.msg.as_str())
- .append_pair("status", heartbeat.status.to_string().as_str())
- .append_pair("ping", heartbeat.ping.to_string().as_str());
-
- match push_client
- .request(Method::GET, push_url)
- .timeout(Duration::from_secs(60))
- .send()
- .await
- {
- Ok(response) => {
- if response.status() >= StatusCode::BAD_REQUEST {
- println!(
- "Received response status of {} during sending event to remote push url",
- response.status()
+impl RoseliteRequest {
+ pub fn new(http_caller: Box, icmp_caller: Box) -> Self {
+ RoseliteRequest {
+ http_caller,
+ icmp_caller,
+ }
+ }
+
+ pub async fn call_kuma_endpoint(
+ &self,
+ upstream_url: String,
+ heartbeat: Heartbeat,
+ ) -> Result<()> {
+ // Retrieve the currently running span
+ let parent_span = sentry::configure_scope(|scope| scope.get_span());
+
+ let span: sentry::TransactionOrSpan = match &parent_span {
+ Some(parent) => parent
+ .start_child(
+ "request.call_kuma_endpoint",
+ "Call upstream Uptime Kuma endpoint",
+ )
+ .into(),
+ None => {
+ let ctx = sentry::TransactionContext::new(
+ "Call upstream Uptime Kuma endpoint",
+ "request.call_kuma_endpoint",
);
- if let Ok(body) = response.text().await {
- println!("Response body: {}", body)
+ sentry::start_transaction(ctx).into()
+ }
+ };
+
+ let push_client = Client::builder().user_agent("Roselite/1.0").build()?;
+
+ let mut push_url = Url::parse(upstream_url.as_str())?;
+ push_url
+ .query_pairs_mut()
+ .append_pair("msg", heartbeat.msg.as_str())
+ .append_pair("status", heartbeat.status.to_string().as_str())
+ .append_pair("ping", heartbeat.ping.to_string().as_str());
+
+ match push_client
+ .request(Method::GET, push_url)
+ .timeout(Duration::from_secs(60))
+ .send()
+ .await
+ {
+ Ok(response) => {
+ if response.status() >= StatusCode::BAD_REQUEST {
+ println!(
+ "Received response status of {} during sending event to remote push url",
+ response.status()
+ );
+ if let Ok(body) = response.text().await {
+ println!("Response body: {}", body)
+ }
+ return Ok(());
}
- return Ok(());
+ println!("Successfully sent an event to remote push url");
+ Ok(())
}
- println!("Successfully sent an event to remote push url");
- Ok(())
- }
- Err(err) => {
- println!(
- "An error occurred during sending event to remote push url: {}",
- err
- );
- Err(err)
- }
- }?;
+ Err(err) => {
+ println!(
+ "An error occurred during sending event to remote push url: {}",
+ err
+ );
+ span.clone().finish();
+ Err(err)
+ }
+ }?;
- Ok(())
-}
+ span.finish();
+ Ok(())
+ }
-/// It calls the monitor endpoint to create a heartbeat that will be sent to the
-/// push endpoint.
-pub async fn perform_task(monitor: Monitor) -> Result {
- let heartbeat = call_monitor_endpoint(monitor.clone()).await?;
+ /// It calls the monitor endpoint to create a heartbeat that will be sent to the
+ /// push endpoint.
+ pub async fn perform_task(&self, monitor: Monitor) -> Result {
+ // Retrieve the currently running span
+ let parent_span = sentry::configure_scope(|scope| scope.get_span());
+
+ let span: sentry::TransactionOrSpan = match &parent_span {
+ Some(parent) => parent
+ .start_child("request.perform_task", "Perform request task")
+ .into(),
+ None => {
+ let ctx =
+ sentry::TransactionContext::new("Perform request task", "request.perform_task");
+ sentry::start_transaction(ctx).into()
+ }
+ };
- call_kuma_endpoint(monitor.clone().push_url, heartbeat.clone()).await?;
+ let heartbeat = match monitor.monitor_type {
+ MonitorType::HTTP => self.http_caller.call(monitor.clone())?,
+ MonitorType::ICMP => self.icmp_caller.call(monitor.clone())?,
+ };
- Ok(heartbeat)
+ self.call_kuma_endpoint(monitor.clone().push_url, heartbeat.clone())
+ .await?;
+
+ span.finish();
+
+ Ok(heartbeat)
+ }
}
diff --git a/roselite-server/src/handlers/remote_write.rs b/roselite-server/src/handlers/remote_write.rs
index 9c94bf0..6abb5fa 100644
--- a/roselite-server/src/handlers/remote_write.rs
+++ b/roselite-server/src/handlers/remote_write.rs
@@ -1,32 +1,39 @@
-use crate::config::DynServerConfig;
+use std::sync::Arc;
+
use anyhow::Result;
use axum::extract::{Path, Query, State};
use axum::http::StatusCode;
+use axum::response::IntoResponse;
use axum::Json;
use reqwest::Url;
-use roselite_common::heartbeat::Heartbeat;
-use roselite_request::call_kuma_endpoint;
use sentry::integrations::anyhow::capture_anyhow;
use sentry::{capture_message, Level};
use serde::{Deserialize, Serialize};
+use roselite_common::heartbeat::Heartbeat;
+use roselite_request::RoseliteRequest;
+
+use crate::config::{ServerConfig, ServerOptions};
+
#[derive(Clone, Serialize, Deserialize)]
pub struct RemoteWriteResponse {
pub ok: bool,
}
pub async fn remote_write(
- State(server_config): State,
Path(id): Path,
Query(params): Query,
-) -> (StatusCode, Json) {
+ State(server_config): State>,
+) -> impl IntoResponse {
+ let request = Arc::new(RoseliteRequest::default());
let upstream_kuma = server_config.get_upstream_kuma();
if let Some(upstream_url) = upstream_kuma {
match convert_to_upstream(upstream_url, id) {
- Ok(push_url) => match call_kuma_endpoint(push_url, params).await {
+ Ok(push_url) => match request.call_kuma_endpoint(push_url, params).await {
Ok(_) => (StatusCode::OK, Json(RemoteWriteResponse { ok: true })),
Err(error) => {
capture_anyhow(&error);
+
(
StatusCode::INTERNAL_SERVER_ERROR,
Json(RemoteWriteResponse { ok: false }),
diff --git a/roselite-server/src/lib.rs b/roselite-server/src/lib.rs
index fa58de7..8d5bf6f 100644
--- a/roselite-server/src/lib.rs
+++ b/roselite-server/src/lib.rs
@@ -1,8 +1,10 @@
-use crate::config::ServerConfig;
-use crate::routes::register_routes;
+use std::net::SocketAddr;
+
use anyhow::Result;
use axum::Server;
-use std::net::SocketAddr;
+
+use crate::config::ServerConfig;
+use crate::routes::register_routes;
pub mod config;
mod handlers;
diff --git a/roselite-server/src/routes.rs b/roselite-server/src/routes.rs
index 97cb392..67cd10f 100644
--- a/roselite-server/src/routes.rs
+++ b/roselite-server/src/routes.rs
@@ -1,14 +1,16 @@
-use crate::config::{DynServerConfig, ServerConfig};
+use std::sync::Arc;
+
+use axum::routing::get;
+use axum::Router;
+
+use crate::config::ServerConfig;
use crate::handlers::ping::ping;
use crate::handlers::remote_write::remote_write;
-use axum::routing::{get, post};
-use axum::Router;
-use std::sync::Arc;
pub fn register_routes(config: ServerConfig) -> Router {
- let server_config = Arc::new(config) as DynServerConfig;
+ let server_config = Arc::new(config);
Router::new()
.route("/ping", get(ping))
- .route("/api/push/:id", post(remote_write))
+ .route("/api/push/:id", get(remote_write).post(remote_write))
.with_state(server_config)
}
diff --git a/roselite/Cargo.toml b/roselite/Cargo.toml
index 0959c92..f123c00 100644
--- a/roselite/Cargo.toml
+++ b/roselite/Cargo.toml
@@ -11,6 +11,7 @@ futures = "0.3.28"
anyhow = "1.0.71"
clap = { version = "4.3", features = ["derive"] }
sentry = { version = "0.31", features = ["default", "anyhow"] }
+sentry-tracing = { version = "0.31" }
roselite-request = { path = "../roselite-request" }
roselite-config = { path = "../roselite-config" }
roselite-server = { path = "../roselite-server" }
\ No newline at end of file
diff --git a/roselite/src/main.rs b/roselite/src/main.rs
index 1d60ef7..cd77f67 100644
--- a/roselite/src/main.rs
+++ b/roselite/src/main.rs
@@ -1,15 +1,18 @@
-mod cli;
-mod monitor;
+use std::{env, process};
+
use anyhow::Result;
use futures::future;
-use std::{env, process};
use tokio::{signal, spawn};
-use crate::cli::cli;
-use crate::monitor::configure_monitors;
use roselite_config::Configuration;
use roselite_server::config::ServerConfig;
+use crate::cli::cli;
+use crate::monitor::configure_monitors;
+
+mod cli;
+mod monitor;
+
#[tokio::main]
async fn main() -> Result<()> {
let matches = cli().get_matches();
@@ -32,7 +35,26 @@ async fn main() -> Result<()> {
Some(error_reporting) => error_reporting.sentry_dsn,
None => String::new(),
});
- let _guard = sentry::init(sentry_dsn);
+ let _guard = sentry::init((
+ sentry_dsn,
+ sentry::ClientOptions {
+ environment: Some(
+ env::var("ENVIRONMENT")
+ .unwrap_or("production".to_string())
+ .into(),
+ ),
+ sample_rate: env::var("SENTRY_SAMPLE_RATE")
+ .unwrap_or("1.0".to_string())
+ .parse::()
+ .unwrap_or(1.0),
+ traces_sample_rate: env::var("SENTRY_TRACES_SAMPLE_RATE")
+ .unwrap_or("0.2".to_string())
+ .parse::()
+ .unwrap_or(0.2),
+ attach_stacktrace: true,
+ ..Default::default()
+ },
+ ));
let mut handles = vec![];
@@ -71,6 +93,7 @@ async fn main() -> Result<()> {
process::exit(0);
}
Err(err) => {
+ sentry::capture_error(&err);
eprintln!("Unable to listen for shutdown signal: {}", err);
}
}
diff --git a/roselite/src/monitor.rs b/roselite/src/monitor.rs
index 25386d0..579dd74 100644
--- a/roselite/src/monitor.rs
+++ b/roselite/src/monitor.rs
@@ -1,32 +1,55 @@
-use roselite_config::Monitor;
-use roselite_request::perform_task;
use std::time::Duration;
+
+use sentry::integrations::anyhow::capture_anyhow;
use tokio::spawn;
use tokio::task::JoinHandle;
use tokio::time::{sleep, Instant};
+use roselite_config::Monitor;
+use roselite_request::http_caller::HttpCaller;
+use roselite_request::icmp_caller::IcmpCaller;
+use roselite_request::RoseliteRequest;
+
pub fn configure_monitors(monitors: Vec) -> Vec> {
let mut handles: Vec> = vec![];
+ // Build dependency for http_caller and icmp_caller
+ let http_caller = Box::new(HttpCaller::new());
+ let icmp_caller = Box::new(IcmpCaller::new());
+
+ // Start the monitors
for monitor in monitors {
- println!("Starting monitor for {}", monitor.monitor_url);
+ println!("Starting monitor for {}", monitor.monitor_target);
+ let http_copy = http_caller.clone();
+ let icmp_copy = icmp_caller.clone();
handles.push(spawn(async move {
- let cloned_monitor: Monitor = monitor.clone();
-
loop {
+ let tx_ctx = sentry::TransactionContext::new(
+ "Start Roselite monitor",
+ "roselite.start_monitor",
+ );
+ let transaction = sentry::start_transaction(tx_ctx);
+
+ // Bind the transaction / span to the scope:
+ sentry::configure_scope(|scope| scope.set_span(Some(transaction.clone().into())));
+
+ let request = RoseliteRequest::new(http_copy.clone(), icmp_copy.clone());
let current_time = Instant::now();
- if let Err(err) = perform_task(monitor.clone()).await {
+ if let Err(err) = request.perform_task(monitor.clone()).await {
// Do nothing of this error
+ capture_anyhow(&err);
eprintln!("Unexpected error during performing task: {}", err);
}
+ transaction.finish();
+
let elapsed = current_time.elapsed();
if 60 - elapsed.as_secs() > 0 {
let sleeping_duration = Duration::from_secs(60 - elapsed.as_secs());
println!(
"Monitor for {0} will be sleeping for {1} seconds",
- cloned_monitor.monitor_url,
+ monitor.monitor_target,
sleeping_duration.as_secs()
);
sleep(sleeping_duration).await;