Admin API to get the version of the service (#5102)
This commit is contained in:
@@ -9,7 +9,7 @@ use std::{convert::Infallible, net::IpAddr, sync::Arc};
|
||||
use axum::extract::{FromRef, FromRequestParts};
|
||||
use ipnetwork::IpNetwork;
|
||||
use mas_context::LogContext;
|
||||
use mas_data_model::{BoxClock, BoxRng, SiteConfig, SystemClock};
|
||||
use mas_data_model::{AppVersion, BoxClock, BoxRng, SiteConfig, SystemClock};
|
||||
use mas_handlers::{
|
||||
ActivityTracker, BoundActivityTracker, CookieManager, ErrorWrapper, GraphQLSchema, Limiter,
|
||||
MetadataCache, RequesterFingerprint, passwords::PasswordManager,
|
||||
@@ -27,7 +27,7 @@ use rand::SeedableRng;
|
||||
use sqlx::PgPool;
|
||||
use tracing::Instrument;
|
||||
|
||||
use crate::telemetry::METER;
|
||||
use crate::{VERSION, telemetry::METER};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AppState {
|
||||
@@ -214,6 +214,12 @@ impl FromRef<AppState> for Arc<dyn HomeserverConnection> {
|
||||
}
|
||||
}
|
||||
|
||||
impl FromRef<AppState> for AppVersion {
|
||||
fn from_ref(_input: &AppState) -> Self {
|
||||
AppVersion(VERSION)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromRequestParts<AppState> for BoxClock {
|
||||
type Rejection = Infallible;
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ pub(crate) mod upstream_oauth2;
|
||||
pub(crate) mod user_agent;
|
||||
pub(crate) mod users;
|
||||
mod utils;
|
||||
mod version;
|
||||
|
||||
/// Error when an invalid state transition is attempted.
|
||||
#[derive(Debug, Error)]
|
||||
@@ -57,4 +58,5 @@ pub use self::{
|
||||
UserRecoveryTicket, UserRegistration, UserRegistrationPassword, UserRegistrationToken,
|
||||
},
|
||||
utils::{BoxClock, BoxRng},
|
||||
version::AppVersion,
|
||||
};
|
||||
|
||||
8
crates/data-model/src/version.rs
Normal file
8
crates/data-model/src/version.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
// Copyright 2025 New Vector Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
// Please see LICENSE files in the repository root for full details.
|
||||
|
||||
/// A structure which holds information about the running version of the app
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct AppVersion(pub &'static str);
|
||||
@@ -20,7 +20,7 @@ use axum::{
|
||||
use hyper::header::{ACCEPT, AUTHORIZATION, CONTENT_TYPE};
|
||||
use indexmap::IndexMap;
|
||||
use mas_axum_utils::InternalError;
|
||||
use mas_data_model::{BoxRng, SiteConfig};
|
||||
use mas_data_model::{AppVersion, BoxRng, SiteConfig};
|
||||
use mas_http::CorsLayerExt;
|
||||
use mas_matrix::HomeserverConnection;
|
||||
use mas_policy::PolicyFactory;
|
||||
@@ -164,6 +164,7 @@ where
|
||||
UrlBuilder: FromRef<S>,
|
||||
Arc<PolicyFactory>: FromRef<S>,
|
||||
SiteConfig: FromRef<S>,
|
||||
AppVersion: FromRef<S>,
|
||||
{
|
||||
// We *always* want to explicitly set the possible responses, beacuse the
|
||||
// infered ones are not necessarily correct
|
||||
|
||||
@@ -11,7 +11,7 @@ use aide::axum::{
|
||||
routing::{get_with, post_with},
|
||||
};
|
||||
use axum::extract::{FromRef, FromRequestParts};
|
||||
use mas_data_model::{BoxRng, SiteConfig};
|
||||
use mas_data_model::{AppVersion, BoxRng, SiteConfig};
|
||||
use mas_matrix::HomeserverConnection;
|
||||
use mas_policy::PolicyFactory;
|
||||
|
||||
@@ -28,6 +28,7 @@ mod user_emails;
|
||||
mod user_registration_tokens;
|
||||
mod user_sessions;
|
||||
mod users;
|
||||
mod version;
|
||||
|
||||
pub fn router<S>() -> ApiRouter<S>
|
||||
where
|
||||
@@ -35,6 +36,7 @@ where
|
||||
Arc<dyn HomeserverConnection>: FromRef<S>,
|
||||
PasswordManager: FromRef<S>,
|
||||
SiteConfig: FromRef<S>,
|
||||
AppVersion: FromRef<S>,
|
||||
Arc<PolicyFactory>: FromRef<S>,
|
||||
BoxRng: FromRequestParts<S>,
|
||||
CallContext: FromRequestParts<S>,
|
||||
@@ -44,6 +46,10 @@ where
|
||||
"/site-config",
|
||||
get_with(self::site_config::handler, self::site_config::doc),
|
||||
)
|
||||
.api_route(
|
||||
"/version",
|
||||
get_with(self::version::handler, self::version::doc),
|
||||
)
|
||||
.api_route(
|
||||
"/compat-sessions",
|
||||
get_with(self::compat_sessions::list, self::compat_sessions::list_doc),
|
||||
|
||||
62
crates/handlers/src/admin/v1/version.rs
Normal file
62
crates/handlers/src/admin/v1/version.rs
Normal file
@@ -0,0 +1,62 @@
|
||||
// Copyright 2025 New Vector Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
// Please see LICENSE files in the repository root for full details.
|
||||
|
||||
use aide::transform::TransformOperation;
|
||||
use axum::{Json, extract::State};
|
||||
use mas_data_model::AppVersion;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::admin::call_context::CallContext;
|
||||
|
||||
#[derive(Serialize, JsonSchema)]
|
||||
pub struct Version {
|
||||
/// The semver version of the app
|
||||
pub version: &'static str,
|
||||
}
|
||||
|
||||
pub fn doc(operation: TransformOperation) -> TransformOperation {
|
||||
operation
|
||||
.id("version")
|
||||
.tag("server")
|
||||
.summary("Get the version currently running")
|
||||
.response_with::<200, Json<Version>, _>(|t| t.example(Version { version: "v1.0.0" }))
|
||||
}
|
||||
|
||||
#[tracing::instrument(name = "handler.admin.v1.version", skip_all)]
|
||||
pub async fn handler(
|
||||
_: CallContext,
|
||||
State(AppVersion(version)): State<mas_data_model::AppVersion>,
|
||||
) -> Json<Version> {
|
||||
Json(Version { version })
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use hyper::{Request, StatusCode};
|
||||
use insta::assert_json_snapshot;
|
||||
use sqlx::PgPool;
|
||||
|
||||
use crate::test_utils::{RequestBuilderExt, ResponseExt, TestState, setup};
|
||||
|
||||
#[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")]
|
||||
async fn test_add_user(pool: PgPool) {
|
||||
setup();
|
||||
let mut state = TestState::from_pool(pool).await.unwrap();
|
||||
let token = state.token_with_scope("urn:mas:admin").await;
|
||||
|
||||
let request = Request::get("/api/admin/v1/version").bearer(&token).empty();
|
||||
|
||||
let response = state.request(request).await;
|
||||
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
let body: serde_json::Value = response.json();
|
||||
assert_json_snapshot!(body, @r#"
|
||||
{
|
||||
"version": "v0.0.0-test"
|
||||
}
|
||||
"#);
|
||||
}
|
||||
}
|
||||
@@ -60,6 +60,7 @@ impl_from_ref!(mas_keystore::Keystore);
|
||||
impl_from_ref!(mas_handlers::passwords::PasswordManager);
|
||||
impl_from_ref!(Arc<mas_policy::PolicyFactory>);
|
||||
impl_from_ref!(mas_data_model::SiteConfig);
|
||||
impl_from_ref!(mas_data_model::AppVersion);
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let (mut api, _) = mas_handlers::admin_api_router::<DummyState>();
|
||||
|
||||
@@ -28,7 +28,7 @@ use mas_axum_utils::{
|
||||
cookies::{CookieJar, CookieManager},
|
||||
};
|
||||
use mas_config::RateLimitingConfig;
|
||||
use mas_data_model::{BoxClock, BoxRng, SiteConfig, clock::MockClock};
|
||||
use mas_data_model::{AppVersion, BoxClock, BoxRng, SiteConfig, clock::MockClock};
|
||||
use mas_email::{MailTransport, Mailer};
|
||||
use mas_i18n::Translator;
|
||||
use mas_keystore::{Encrypter, JsonWebKey, JsonWebKeySet, Keystore, PrivateKey};
|
||||
@@ -575,6 +575,12 @@ impl FromRef<TestState> for reqwest::Client {
|
||||
}
|
||||
}
|
||||
|
||||
impl FromRef<TestState> for AppVersion {
|
||||
fn from_ref(_input: &TestState) -> Self {
|
||||
AppVersion("v0.0.0-test")
|
||||
}
|
||||
}
|
||||
|
||||
impl FromRequestParts<TestState> for ActivityTracker {
|
||||
type Rejection = Infallible;
|
||||
|
||||
|
||||
@@ -50,6 +50,30 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/admin/v1/version": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"server"
|
||||
],
|
||||
"summary": "Get the version currently running",
|
||||
"operationId": "version",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Version"
|
||||
},
|
||||
"example": {
|
||||
"version": "v1.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/admin/v1/compat-sessions": {
|
||||
"get": {
|
||||
"tags": [
|
||||
@@ -3710,6 +3734,18 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Version": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"version"
|
||||
],
|
||||
"properties": {
|
||||
"version": {
|
||||
"description": "The semver version of the app",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"PaginationParams": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
Reference in New Issue
Block a user