From eb3807d0529224dfe4f9286a4296089fb2588331 Mon Sep 17 00:00:00 2001 From: Peter Holloway Date: Wed, 8 Jan 2025 17:56:44 +0000 Subject: [PATCH] Return status information from status endpoint The status endpoint is currently only used as a liveness probe for kubernetes deployments but returning a version and start time provides a useful sanity check of the running version without having to use the kubernetes dashboard. The dockerfile needs to be updated to include the build.rs file and set the workdir. Previously, cargo was running in the root directory and would complain about infinite file system loops in /proc/. --- Cargo.lock | 59 +++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 5 +++- Dockerfile | 5 +++- build.rs | 5 +++- src/build_info.rs | 42 +++++++++++++++++++++++++++++++++ src/cli.rs | 2 ++ src/graphql.rs | 6 +++-- src/main.rs | 1 + 8 files changed, 120 insertions(+), 5 deletions(-) create mode 100644 src/build_info.rs diff --git a/Cargo.lock b/Cargo.lock index 5d249e6a..227264ec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -599,6 +599,16 @@ dependencies = [ "piper", ] +[[package]] +name = "built" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c360505aed52b7ec96a3636c3f039d99103c37d1d9b4f7a8c743d3ea9ffcd03b" +dependencies = [ + "chrono", + "git2", +] + [[package]] name = "bumpalo" version = "3.16.0" @@ -626,6 +636,8 @@ version = "1.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a012a0df96dd6d06ba9a1b29d6402d1a5d77c6befd2566afdc26e10603dc93d7" dependencies = [ + "jobserver", + "libc", "shlex", ] @@ -1129,6 +1141,19 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +[[package]] +name = "git2" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b903b73e45dc0c6c596f2d37eccece7c1c8bb6e4407b001096387c63d0d93724" +dependencies = [ + "bitflags", + "libc", + "libgit2-sys", + "log", + "url", +] + [[package]] name = "glob" version = "0.3.2" @@ -1680,6 +1705,15 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" +[[package]] +name = "jobserver" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +dependencies = [ + "libc", +] + [[package]] name = "js-sys" version = "0.3.76" @@ -1720,6 +1754,18 @@ version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" +[[package]] +name = "libgit2-sys" +version = "0.17.0+1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10472326a8a6477c3c20a64547b0059e4b0d086869eee31e6d7da728a8eb7224" +dependencies = [ + "cc", + "libc", + "libz-sys", + "pkg-config", +] + [[package]] name = "libm" version = "0.2.11" @@ -1737,6 +1783,18 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "libz-sys" +version = "1.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2d16453e800a8cf6dd2fc3eb4bc99b786a9b90c663b8559a5b1a041bf89e472" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "linux-raw-sys" version = "0.4.14" @@ -1909,6 +1967,7 @@ dependencies = [ "async-std", "axum", "axum-extra", + "built", "chrono", "clap", "futures", diff --git a/Cargo.toml b/Cargo.toml index 3355f25f..5f671e62 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ async-graphql-axum = "7.0.13" axum = "0.7.9" axum-extra = { version = "0.9.3", features = ["typed-header"] } chrono = "0.4.39" -clap = { version = "4.5.23", features = ["cargo", "derive", "env"] } +clap = { version = "4.5.23", features = ["cargo", "derive", "env", "string"] } futures = "0.3.31" opentelemetry = "0.27.1" opentelemetry-otlp = "0.27.0" @@ -38,3 +38,6 @@ httpmock = { version = "0.7.0", default-features = false } rstest = "0.23.0" serde_json = "1.0.133" tempfile = "3.14.0" + +[build-dependencies] +built = { version = "0.7.5", features = ["git2", "chrono"] } diff --git a/Dockerfile b/Dockerfile index 4b4f489f..704c001f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,8 +5,11 @@ RUN rustup target add x86_64-unknown-linux-musl && \ apt-get install -y musl-tools musl-dev && \ update-ca-certificates +WORKDIR /build + COPY ./Cargo.toml ./Cargo.toml COPY ./Cargo.lock ./Cargo.lock +COPY ./build.rs ./build.rs COPY ./.env ./.env COPY ./src ./src COPY ./.sqlx ./.sqlx @@ -20,7 +23,7 @@ LABEL org.opencontainers.image.source=https://github.com/DiamondLightSource/numt LABEL org.opencontainers.image.description="Central co-ordinator for scan numbers and file locations" LABEL org.opencontainers.image.licenses=Apache-2.0 -COPY --from=build ./target/x86_64-unknown-linux-musl/release/numtracker /app/numtracker +COPY --from=build /build/target/x86_64-unknown-linux-musl/release/numtracker /app/numtracker CMD ["serve"] ENTRYPOINT ["/app/numtracker"] diff --git a/build.rs b/build.rs index 3a8149ef..e8290fd3 100644 --- a/build.rs +++ b/build.rs @@ -1,3 +1,6 @@ fn main() { - println!("cargo:rerun-if-changed=migrations"); + println!("cargo::rerun-if-changed=migrations"); + built::write_built_file().expect("Failed to write build time information"); + // Force the application to be rebuilt after committing to ensure build info is up to date + println!("cargo::rerun-if-changed=.git/refs"); } diff --git a/src/build_info.rs b/src/build_info.rs new file mode 100644 index 00000000..692b86f0 --- /dev/null +++ b/src/build_info.rs @@ -0,0 +1,42 @@ +//! Compile time build information provided by built + +use chrono::Local; +use serde::Serialize; + +include!(concat!(env!("OUT_DIR"), "/built.rs")); + +/// User friendly label for marking a build as debug or not +pub const DEBUG_LABEL: &str = if DEBUG { " (debug)" } else { "" }; +/// User friendly label for indicating repo state +pub const DIRTY_LABEL: &str = match GIT_DIRTY { + Some(true) => " (+unstaged changes)", + _ => "", +}; + +pub fn build_info() -> String { + format!( + concat!("- {}{}\n", "Built: {}\n", "Commit: {}{}"), + PKG_VERSION, + DEBUG_LABEL, + BUILT_TIME_UTC, + GIT_COMMIT_HASH.unwrap_or("Unknown"), + DIRTY_LABEL + ) +} + +#[derive(Debug, Clone, Serialize)] +pub struct ServerStatus { + version: String, + start_time: String, + build: String, +} + +impl ServerStatus { + pub fn new() -> Self { + Self { + version: PKG_VERSION.into(), + start_time: Local::now().to_rfc3339(), + build: GIT_COMMIT_HASH.unwrap_or("Unknown").into(), + } + } +} diff --git a/src/cli.rs b/src/cli.rs index 2017c65d..7adaece1 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -21,6 +21,8 @@ use tracing::Level; use url::Url; #[derive(Debug, Parser)] +#[clap(version)] +#[clap(long_version = crate::build_info::build_info())] pub struct Cli { #[clap(short, long, default_value = "numtracker.db", env = "NUMTRACKER_DB")] pub(crate) db: PathBuf, diff --git a/src/graphql.rs b/src/graphql.rs index 389a7bf6..e9dcbd30 100644 --- a/src/graphql.rs +++ b/src/graphql.rs @@ -32,7 +32,7 @@ use async_graphql_axum::{GraphQLRequest, GraphQLResponse}; use auth::{AuthError, PolicyCheck}; use axum::response::{Html, IntoResponse}; use axum::routing::{get, post}; -use axum::{Extension, Router}; +use axum::{Extension, Json, Router}; use axum_extra::headers::authorization::Bearer; use axum_extra::headers::Authorization; use axum_extra::TypedHeader; @@ -40,6 +40,7 @@ use chrono::{Datelike, Local}; use tokio::net::TcpListener; use tracing::{info, instrument, trace, warn}; +use crate::build_info::ServerStatus; use crate::cli::ServeOptions; use crate::db_service::{ BeamlineConfiguration, BeamlineConfigurationUpdate, SqliteScanPathService, @@ -54,6 +55,7 @@ use crate::template::{FieldSource, PathTemplate}; mod auth; pub async fn serve_graphql(db: &Path, opts: ServeOptions) { + let server_status = Json(ServerStatus::new()); let db = SqliteScanPathService::connect(db) .await .expect("Unable to open DB"); @@ -71,7 +73,7 @@ pub async fn serve_graphql(db: &Path, opts: ServeOptions) { let app = Router::new() // status check endpoint allows external processes to monitor status of server without // making graphql queries - .route("/status", get(|| async {})) + .route("/status", get(server_status)) .route("/graphql", post(graphql_handler)) .route("/graphiql", get(graphiql)) .layer(Extension(schema)); diff --git a/src/main.rs b/src/main.rs index e7dedb89..3a1d227e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,6 +17,7 @@ use std::error::Error; use cli::{Cli, Command}; use tracing::debug; +mod build_info; mod cli; mod db_service; mod graphql;