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;