From f0f8d8a348299d00915396d2e4f218fa2f95d957 Mon Sep 17 00:00:00 2001 From: David Crespo Date: Tue, 7 Apr 2026 17:16:41 -0400 Subject: [PATCH 1/4] add /v1/system/version endpoint (placeholder version) --- nexus/external-api/output/nexus_tags.txt | 1 + nexus/external-api/src/lib.rs | 22 ++++++++++ nexus/tests/integration_tests/basic.rs | 10 +++++ .../output/uncovered-authz-endpoints.txt | 1 + nexus/types/versions/src/latest.rs | 1 + nexus/types/versions/src/lib.rs | 2 + .../types/versions/src/system_version/mod.rs | 9 ++++ .../versions/src/system_version/system.rs | 15 +++++++ .../nexus-2026032500.0.0-cf1221.json.gitstub | 1 + ....json => nexus-2026040800.0.0-37972f.json} | 44 ++++++++++++++++++- openapi/nexus/nexus-latest.json | 2 +- 11 files changed, 106 insertions(+), 2 deletions(-) create mode 100644 nexus/types/versions/src/system_version/mod.rs create mode 100644 nexus/types/versions/src/system_version/system.rs create mode 100644 openapi/nexus/nexus-2026032500.0.0-cf1221.json.gitstub rename openapi/nexus/{nexus-2026032500.0.0-cf1221.json => nexus-2026040800.0.0-37972f.json} (99%) diff --git a/nexus/external-api/output/nexus_tags.txt b/nexus/external-api/output/nexus_tags.txt index 348798bc282..238d1345f88 100644 --- a/nexus/external-api/output/nexus_tags.txt +++ b/nexus/external-api/output/nexus_tags.txt @@ -325,6 +325,7 @@ user_builtin_view GET /v1/system/users-builtin/{user API operations found with tag "system/status" OPERATION ID METHOD URL PATH ping GET /v1/ping +system_version GET /v1/system/version API operations found with tag "system/subnet-pools" OPERATION ID METHOD URL PATH diff --git a/nexus/external-api/src/lib.rs b/nexus/external-api/src/lib.rs index e6416f360df..a6977887d91 100644 --- a/nexus/external-api/src/lib.rs +++ b/nexus/external-api/src/lib.rs @@ -79,6 +79,7 @@ api_versions!([ // | date-based version should be at the top of the list. // v // (next_yyyy_mm_dd_nn, IDENT), + (2026_04_08_00, SYSTEM_VERSION), (2026_03_25_00, SUBNET_POOL_UTILIZATION_REMAINING), (2026_03_24_00, ADD_ICMPV6_FIREWALL_SUPPORT), (2026_03_23_00, RENAME_PREFIX_LEN), @@ -397,6 +398,27 @@ pub trait NexusExternalApi { })) } + // No auth required: this only returns hard-coded values. + + /// Fetch system version + /// + /// Returns the current version of the Oxide software running on this + /// system. + #[endpoint { + method = GET, + path = "/v1/system/version", + tags = ["system/status"], + versions = VERSION_SYSTEM_VERSION.., + }] + async fn system_version( + _rqctx: RequestContext, + ) -> Result, HttpError> { + // TODO: replace with the real version string + Ok(HttpResponseOk(latest::system::SystemVersion { + version: "0.0.0".parse().unwrap(), + })) + } + /// Fetch top-level IAM policy #[endpoint { method = GET, diff --git a/nexus/tests/integration_tests/basic.rs b/nexus/tests/integration_tests/basic.rs index 87c65816060..b5e143a8dc3 100644 --- a/nexus/tests/integration_tests/basic.rs +++ b/nexus/tests/integration_tests/basic.rs @@ -557,3 +557,13 @@ async fn test_ping(cptestctx: &ControlPlaneTestContext) { .await; assert_eq!(health.status, system::PingStatus::Ok); } + +#[nexus_test] +async fn test_system_version(cptestctx: &ControlPlaneTestContext) { + let client = &cptestctx.external_client; + + let version = NexusRequest::object_get(client, "/v1/system/version") + .execute_and_parse_unwrap::() + .await; + assert_eq!(version.version, semver::Version::new(0, 0, 0)); +} diff --git a/nexus/tests/output/uncovered-authz-endpoints.txt b/nexus/tests/output/uncovered-authz-endpoints.txt index c164f37b32f..923431b80eb 100644 --- a/nexus/tests/output/uncovered-authz-endpoints.txt +++ b/nexus/tests/output/uncovered-authz-endpoints.txt @@ -16,6 +16,7 @@ console_settings_page (get "/settings/{path}") console_system_page (get "/system/{path}") console_silo_utilization (get "/utilization") ping (get "/v1/ping") +system_version (get "/v1/system/version") device_auth_request (post "/device/auth") device_auth_confirm (post "/device/confirm") device_access_token (post "/device/token") diff --git a/nexus/types/versions/src/latest.rs b/nexus/types/versions/src/latest.rs index 7aab6ceaa2f..a79f3c772f7 100644 --- a/nexus/types/versions/src/latest.rs +++ b/nexus/types/versions/src/latest.rs @@ -388,6 +388,7 @@ pub mod system { pub use crate::v2025_11_20_00::system::AllowListUpdate; pub use crate::v2025_11_20_00::system::Ping; pub use crate::v2025_11_20_00::system::PingStatus; + pub use crate::v2026_04_05_00::system::SystemVersion; } pub mod timeseries { diff --git a/nexus/types/versions/src/lib.rs b/nexus/types/versions/src/lib.rs index ae02c281c59..0c12952fef6 100644 --- a/nexus/types/versions/src/lib.rs +++ b/nexus/types/versions/src/lib.rs @@ -77,3 +77,5 @@ pub mod v2026_03_14_00; pub mod v2026_03_23_00; #[path = "subnet_pool_utilization_remaining/mod.rs"] pub mod v2026_03_25_00; +#[path = "system_version/mod.rs"] +pub mod v2026_04_05_00; diff --git a/nexus/types/versions/src/system_version/mod.rs b/nexus/types/versions/src/system_version/mod.rs new file mode 100644 index 00000000000..9245213733a --- /dev/null +++ b/nexus/types/versions/src/system_version/mod.rs @@ -0,0 +1,9 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! Version `SYSTEM_VERSION` of the Nexus external API. +//! +//! Adds the `SystemVersion` type for the new `/v1/system/version` endpoint. + +pub mod system; diff --git a/nexus/types/versions/src/system_version/system.rs b/nexus/types/versions/src/system_version/system.rs new file mode 100644 index 00000000000..f332b35027a --- /dev/null +++ b/nexus/types/versions/src/system_version/system.rs @@ -0,0 +1,15 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! System types for version `SYSTEM_VERSION`. + +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +/// The version of the Oxide software running on this system. +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, JsonSchema)] +pub struct SystemVersion { + /// The current system software version + pub version: semver::Version, +} diff --git a/openapi/nexus/nexus-2026032500.0.0-cf1221.json.gitstub b/openapi/nexus/nexus-2026032500.0.0-cf1221.json.gitstub new file mode 100644 index 00000000000..2914b100282 --- /dev/null +++ b/openapi/nexus/nexus-2026032500.0.0-cf1221.json.gitstub @@ -0,0 +1 @@ +254a0c51bc0beecb79c8a9dfccce8e7bc35b5ca4:openapi/nexus/nexus-2026032500.0.0-cf1221.json diff --git a/openapi/nexus/nexus-2026032500.0.0-cf1221.json b/openapi/nexus/nexus-2026040800.0.0-37972f.json similarity index 99% rename from openapi/nexus/nexus-2026032500.0.0-cf1221.json rename to openapi/nexus/nexus-2026040800.0.0-37972f.json index 72d5a2e5d37..4a4f7c05429 100644 --- a/openapi/nexus/nexus-2026032500.0.0-cf1221.json +++ b/openapi/nexus/nexus-2026040800.0.0-37972f.json @@ -7,7 +7,7 @@ "url": "https://oxide.computer", "email": "api@oxide.computer" }, - "version": "2026032500.0.0" + "version": "2026040800.0.0" }, "paths": { "/device/auth": { @@ -13418,6 +13418,34 @@ } } }, + "/v1/system/version": { + "get": { + "tags": [ + "system/status" + ], + "summary": "Fetch system version", + "description": "Returns the current version of the Oxide software running on this system.", + "operationId": "system_version", + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SystemVersion" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, "/v1/timeseries/query": { "post": { "tags": [ @@ -29361,6 +29389,20 @@ "vlan_id" ] }, + "SystemVersion": { + "description": "The version of the Oxide software running on this system.", + "type": "object", + "properties": { + "version": { + "description": "The current system software version", + "type": "string", + "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$" + } + }, + "required": [ + "version" + ] + }, "TargetRelease": { "description": "View of a system software target release", "type": "object", diff --git a/openapi/nexus/nexus-latest.json b/openapi/nexus/nexus-latest.json index bb7ce584356..da67378009c 120000 --- a/openapi/nexus/nexus-latest.json +++ b/openapi/nexus/nexus-latest.json @@ -1 +1 @@ -nexus-2026032500.0.0-cf1221.json \ No newline at end of file +nexus-2026040800.0.0-37972f.json \ No newline at end of file From e7d49bb466768ed6180d6f5096c4c4324cbf00c0 Mon Sep 17 00:00:00 2001 From: David Crespo Date: Wed, 8 Apr 2026 11:05:04 -0500 Subject: [PATCH 2/4] return RELEASE_VERSION from /v1/system/version endpoint --- Cargo.lock | 1 + common/src/lib.rs | 12 ++++++++++++ dev-tools/releng/Cargo.toml | 1 + dev-tools/releng/src/main.rs | 13 +------------ nexus/external-api/src/lib.rs | 3 +-- nexus/tests/integration_tests/basic.rs | 2 +- 6 files changed, 17 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 641ebcf1653..9522a55bbae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8978,6 +8978,7 @@ dependencies = [ "fs-err 3.3.0", "futures", "hex", + "omicron-common", "omicron-pins", "omicron-workspace-hack", "omicron-zone-package", diff --git a/common/src/lib.rs b/common/src/lib.rs index bd471889a9c..071b62d8794 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -69,6 +69,18 @@ pub fn now_db_precision() -> chrono::DateTime { ts - std::time::Duration::from_nanos(u64::from(only_nanos)) } +/// The base release version of the Oxide software. +/// +/// Under current policy, each new release is a major version bump, and +/// generally referred to only by the major version (e.g. 8.0.0 is referred +/// to as "v8", "version 8", or "release 8" to customers). The use of semantic +/// versioning is mostly to hedge for perhaps wanting something more granular in +/// the future. +/// +/// This is used in the releng build process (which appends build metadata to +/// produce the full version string) and by the system version API endpoint. +pub const RELEASE_VERSION: semver::Version = semver::Version::new(19, 0, 0); + pub const OMICRON_DPD_TAG: &str = "omicron"; /// A wrapper struct that does nothing other than elide the inner value from diff --git a/dev-tools/releng/Cargo.toml b/dev-tools/releng/Cargo.toml index d6542dd3b7f..9cea3a9043b 100644 --- a/dev-tools/releng/Cargo.toml +++ b/dev-tools/releng/Cargo.toml @@ -14,6 +14,7 @@ clap.workspace = true fs-err = { workspace = true, features = ["tokio"] } futures.workspace = true hex.workspace = true +omicron-common.workspace = true omicron-pins.workspace = true omicron-workspace-hack.workspace = true omicron-zone-package.workspace = true diff --git a/dev-tools/releng/src/main.rs b/dev-tools/releng/src/main.rs index b84c3d7c11c..4db8b54b007 100644 --- a/dev-tools/releng/src/main.rs +++ b/dev-tools/releng/src/main.rs @@ -25,7 +25,6 @@ use clap::Parser; use fs_err::tokio as fs; use omicron_zone_package::config::Config; use omicron_zone_package::config::PackageName; -use semver::Version; use slog::Drain; use slog::Logger; use slog::debug; @@ -38,16 +37,6 @@ use tokio::sync::Semaphore; use crate::cmd::Command; use crate::job::Jobs; -/// The base version we're currently building. Build information is appended to -/// this later on. -/// -/// Under current policy, each new release is a major version bump, and -/// generally referred to only by the major version (e.g. 8.0.0 is referred -/// to as "v8", "version 8", or "release 8" to customers). The use of semantic -/// versioning is mostly to hedge for perhaps wanting something more granular in -/// the future. -const BASE_VERSION: Version = Version::new(19, 0, 0); - const RETRY_ATTEMPTS: usize = 3; #[derive(Debug, Clone, Copy)] @@ -249,7 +238,7 @@ async fn main() -> Result<()> { .trim() .to_owned(); - let mut version = BASE_VERSION.clone(); + let mut version = omicron_common::RELEASE_VERSION.clone(); // Differentiate between CI and local builds. We use `0.word` as the // prerelease field because it comes before `alpha`. version.pre = diff --git a/nexus/external-api/src/lib.rs b/nexus/external-api/src/lib.rs index a6977887d91..ca64ae1a901 100644 --- a/nexus/external-api/src/lib.rs +++ b/nexus/external-api/src/lib.rs @@ -413,9 +413,8 @@ pub trait NexusExternalApi { async fn system_version( _rqctx: RequestContext, ) -> Result, HttpError> { - // TODO: replace with the real version string Ok(HttpResponseOk(latest::system::SystemVersion { - version: "0.0.0".parse().unwrap(), + version: omicron_common::RELEASE_VERSION, })) } diff --git a/nexus/tests/integration_tests/basic.rs b/nexus/tests/integration_tests/basic.rs index b5e143a8dc3..1c9bbd3807d 100644 --- a/nexus/tests/integration_tests/basic.rs +++ b/nexus/tests/integration_tests/basic.rs @@ -565,5 +565,5 @@ async fn test_system_version(cptestctx: &ControlPlaneTestContext) { let version = NexusRequest::object_get(client, "/v1/system/version") .execute_and_parse_unwrap::() .await; - assert_eq!(version.version, semver::Version::new(0, 0, 0)); + assert_eq!(version.version, omicron_common::RELEASE_VERSION); } From 6fa118d44c0bc0509333e0c47bed17f64f35e14a Mon Sep 17 00:00:00 2001 From: David Crespo Date: Thu, 16 Apr 2026 12:07:36 -0500 Subject: [PATCH 3/4] /v1/api-version --- common/src/lib.rs | 2 +- nexus/external-api/output/nexus_tags.txt | 2 +- nexus/external-api/src/lib.rs | 18 ++-- nexus/tests/integration_tests/basic.rs | 6 +- .../output/uncovered-authz-endpoints.txt | 2 +- .../{system_version => api_version}/mod.rs | 4 +- .../{system_version => api_version}/system.rs | 9 +- nexus/types/versions/src/latest.rs | 2 +- nexus/types/versions/src/lib.rs | 4 +- ....json => nexus-2026040800.0.0-ab2780.json} | 84 +++++++++---------- openapi/nexus/nexus-latest.json | 2 +- 11 files changed, 68 insertions(+), 67 deletions(-) rename nexus/types/versions/src/{system_version => api_version}/mod.rs (62%) rename nexus/types/versions/src/{system_version => api_version}/system.rs (61%) rename openapi/nexus/{nexus-2026040800.0.0-37972f.json => nexus-2026040800.0.0-ab2780.json} (99%) diff --git a/common/src/lib.rs b/common/src/lib.rs index 071b62d8794..d326370c465 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -78,7 +78,7 @@ pub fn now_db_precision() -> chrono::DateTime { /// the future. /// /// This is used in the releng build process (which appends build metadata to -/// produce the full version string) and by the system version API endpoint. +/// produce the full version string) and by the API version endpoint. pub const RELEASE_VERSION: semver::Version = semver::Version::new(19, 0, 0); pub const OMICRON_DPD_TAG: &str = "omicron"; diff --git a/nexus/external-api/output/nexus_tags.txt b/nexus/external-api/output/nexus_tags.txt index 238d1345f88..790a381289a 100644 --- a/nexus/external-api/output/nexus_tags.txt +++ b/nexus/external-api/output/nexus_tags.txt @@ -324,8 +324,8 @@ user_builtin_view GET /v1/system/users-builtin/{user API operations found with tag "system/status" OPERATION ID METHOD URL PATH +api_version GET /v1/api-version ping GET /v1/ping -system_version GET /v1/system/version API operations found with tag "system/subnet-pools" OPERATION ID METHOD URL PATH diff --git a/nexus/external-api/src/lib.rs b/nexus/external-api/src/lib.rs index ca64ae1a901..2f492c3c0bd 100644 --- a/nexus/external-api/src/lib.rs +++ b/nexus/external-api/src/lib.rs @@ -79,7 +79,7 @@ api_versions!([ // | date-based version should be at the top of the list. // v // (next_yyyy_mm_dd_nn, IDENT), - (2026_04_08_00, SYSTEM_VERSION), + (2026_04_08_00, API_VERSION), (2026_03_25_00, SUBNET_POOL_UTILIZATION_REMAINING), (2026_03_24_00, ADD_ICMPV6_FIREWALL_SUPPORT), (2026_03_23_00, RENAME_PREFIX_LEN), @@ -400,20 +400,20 @@ pub trait NexusExternalApi { // No auth required: this only returns hard-coded values. - /// Fetch system version + /// Fetch API version /// - /// Returns the current version of the Oxide software running on this - /// system. + /// Returns the version of the Oxide API currently being served, + /// corresponding to a numbered Oxide release. #[endpoint { method = GET, - path = "/v1/system/version", + path = "/v1/api-version", tags = ["system/status"], - versions = VERSION_SYSTEM_VERSION.., + versions = VERSION_API_VERSION.., }] - async fn system_version( + async fn api_version( _rqctx: RequestContext, - ) -> Result, HttpError> { - Ok(HttpResponseOk(latest::system::SystemVersion { + ) -> Result, HttpError> { + Ok(HttpResponseOk(latest::system::ApiVersion { version: omicron_common::RELEASE_VERSION, })) } diff --git a/nexus/tests/integration_tests/basic.rs b/nexus/tests/integration_tests/basic.rs index 1c9bbd3807d..25e8693ff60 100644 --- a/nexus/tests/integration_tests/basic.rs +++ b/nexus/tests/integration_tests/basic.rs @@ -559,11 +559,11 @@ async fn test_ping(cptestctx: &ControlPlaneTestContext) { } #[nexus_test] -async fn test_system_version(cptestctx: &ControlPlaneTestContext) { +async fn test_api_version(cptestctx: &ControlPlaneTestContext) { let client = &cptestctx.external_client; - let version = NexusRequest::object_get(client, "/v1/system/version") - .execute_and_parse_unwrap::() + let version = NexusRequest::object_get(client, "/v1/api-version") + .execute_and_parse_unwrap::() .await; assert_eq!(version.version, omicron_common::RELEASE_VERSION); } diff --git a/nexus/tests/output/uncovered-authz-endpoints.txt b/nexus/tests/output/uncovered-authz-endpoints.txt index 923431b80eb..cacc7f597dd 100644 --- a/nexus/tests/output/uncovered-authz-endpoints.txt +++ b/nexus/tests/output/uncovered-authz-endpoints.txt @@ -15,8 +15,8 @@ console_projects (get "/projects/{path}") console_settings_page (get "/settings/{path}") console_system_page (get "/system/{path}") console_silo_utilization (get "/utilization") +api_version (get "/v1/api-version") ping (get "/v1/ping") -system_version (get "/v1/system/version") device_auth_request (post "/device/auth") device_auth_confirm (post "/device/confirm") device_access_token (post "/device/token") diff --git a/nexus/types/versions/src/system_version/mod.rs b/nexus/types/versions/src/api_version/mod.rs similarity index 62% rename from nexus/types/versions/src/system_version/mod.rs rename to nexus/types/versions/src/api_version/mod.rs index 9245213733a..773c2ba2c50 100644 --- a/nexus/types/versions/src/system_version/mod.rs +++ b/nexus/types/versions/src/api_version/mod.rs @@ -2,8 +2,8 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. -//! Version `SYSTEM_VERSION` of the Nexus external API. +//! Version `API_VERSION` of the Nexus external API. //! -//! Adds the `SystemVersion` type for the new `/v1/system/version` endpoint. +//! Adds the `ApiVersion` type for the new `/v1/api-version` endpoint. pub mod system; diff --git a/nexus/types/versions/src/system_version/system.rs b/nexus/types/versions/src/api_version/system.rs similarity index 61% rename from nexus/types/versions/src/system_version/system.rs rename to nexus/types/versions/src/api_version/system.rs index f332b35027a..66a77cee701 100644 --- a/nexus/types/versions/src/system_version/system.rs +++ b/nexus/types/versions/src/api_version/system.rs @@ -2,14 +2,15 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. -//! System types for version `SYSTEM_VERSION`. +//! System types for version `API_VERSION`. use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -/// The version of the Oxide software running on this system. +/// The version of the Oxide API currently being served. #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, JsonSchema)] -pub struct SystemVersion { - /// The current system software version +pub struct ApiVersion { + /// The current API version, corresponding to a numbered Oxide release + /// (e.g., `19.0.0` for release 19). pub version: semver::Version, } diff --git a/nexus/types/versions/src/latest.rs b/nexus/types/versions/src/latest.rs index a79f3c772f7..c92017da3d3 100644 --- a/nexus/types/versions/src/latest.rs +++ b/nexus/types/versions/src/latest.rs @@ -388,7 +388,7 @@ pub mod system { pub use crate::v2025_11_20_00::system::AllowListUpdate; pub use crate::v2025_11_20_00::system::Ping; pub use crate::v2025_11_20_00::system::PingStatus; - pub use crate::v2026_04_05_00::system::SystemVersion; + pub use crate::v2026_04_08_00::system::ApiVersion; } pub mod timeseries { diff --git a/nexus/types/versions/src/lib.rs b/nexus/types/versions/src/lib.rs index 0c12952fef6..cdeefbd3fd7 100644 --- a/nexus/types/versions/src/lib.rs +++ b/nexus/types/versions/src/lib.rs @@ -77,5 +77,5 @@ pub mod v2026_03_14_00; pub mod v2026_03_23_00; #[path = "subnet_pool_utilization_remaining/mod.rs"] pub mod v2026_03_25_00; -#[path = "system_version/mod.rs"] -pub mod v2026_04_05_00; +#[path = "api_version/mod.rs"] +pub mod v2026_04_08_00; diff --git a/openapi/nexus/nexus-2026040800.0.0-37972f.json b/openapi/nexus/nexus-2026040800.0.0-ab2780.json similarity index 99% rename from openapi/nexus/nexus-2026040800.0.0-37972f.json rename to openapi/nexus/nexus-2026040800.0.0-ab2780.json index 4a4f7c05429..3d0cc76698b 100644 --- a/openapi/nexus/nexus-2026040800.0.0-37972f.json +++ b/openapi/nexus/nexus-2026040800.0.0-ab2780.json @@ -2197,6 +2197,34 @@ } } }, + "/v1/api-version": { + "get": { + "tags": [ + "system/status" + ], + "summary": "Fetch API version", + "description": "Returns the version of the Oxide API currently being served, corresponding to a numbered Oxide release.", + "operationId": "api_version", + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiVersion" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, "/v1/auth-settings": { "get": { "tags": [ @@ -13418,34 +13446,6 @@ } } }, - "/v1/system/version": { - "get": { - "tags": [ - "system/status" - ], - "summary": "Fetch system version", - "description": "Returns the current version of the Oxide software running on this system.", - "operationId": "system_version", - "responses": { - "200": { - "description": "successful operation", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SystemVersion" - } - } - } - }, - "4XX": { - "$ref": "#/components/responses/Error" - }, - "5XX": { - "$ref": "#/components/responses/Error" - } - } - } - }, "/v1/timeseries/query": { "post": { "tags": [ @@ -16564,6 +16564,20 @@ } } }, + "ApiVersion": { + "description": "The version of the Oxide API currently being served.", + "type": "object", + "properties": { + "version": { + "description": "The current API version, corresponding to a numbered Oxide release (e.g., `19.0.0` for release 19).", + "type": "string", + "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$" + } + }, + "required": [ + "version" + ] + }, "AuditLogEntry": { "description": "Audit log entry", "type": "object", @@ -29389,20 +29403,6 @@ "vlan_id" ] }, - "SystemVersion": { - "description": "The version of the Oxide software running on this system.", - "type": "object", - "properties": { - "version": { - "description": "The current system software version", - "type": "string", - "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$" - } - }, - "required": [ - "version" - ] - }, "TargetRelease": { "description": "View of a system software target release", "type": "object", diff --git a/openapi/nexus/nexus-latest.json b/openapi/nexus/nexus-latest.json index da67378009c..0970ab0f606 120000 --- a/openapi/nexus/nexus-latest.json +++ b/openapi/nexus/nexus-latest.json @@ -1 +1 @@ -nexus-2026040800.0.0-37972f.json \ No newline at end of file +nexus-2026040800.0.0-ab2780.json \ No newline at end of file From 97edf8099041f1beb6d555bb333d47828b995c59 Mon Sep 17 00:00:00 2001 From: David Crespo Date: Mon, 29 Jun 2026 14:33:31 -0500 Subject: [PATCH 4/4] Add /v1/version endpoint with api_version and system_version (RFD 708) --- Cargo.lock | 1 + common/src/lib.rs | 13 +++++ dev-tools/releng/Cargo.toml | 1 + dev-tools/releng/src/main.rs | 13 +---- nexus/external-api/output/nexus_tags.txt | 1 + nexus/external-api/src/lib.rs | 20 ++++++++ nexus/tests/integration_tests/basic.rs | 11 ++++ .../output/uncovered-authz-endpoints.txt | 1 + nexus/types/versions/src/api_version/mod.rs | 9 ++++ .../types/versions/src/api_version/system.rs | 23 +++++++++ nexus/types/versions/src/latest.rs | 1 + nexus/types/versions/src/lib.rs | 2 + .../nexus-2026061000.0.0-9779e2.json.gitstub | 1 + ....json => nexus-2026070700.0.0-d83eb8.json} | 50 ++++++++++++++++++- openapi/nexus/nexus-latest.json | 2 +- 15 files changed, 135 insertions(+), 14 deletions(-) create mode 100644 nexus/types/versions/src/api_version/mod.rs create mode 100644 nexus/types/versions/src/api_version/system.rs create mode 100644 openapi/nexus/nexus-2026061000.0.0-9779e2.json.gitstub rename openapi/nexus/{nexus-2026061000.0.0-9779e2.json => nexus-2026070700.0.0-d83eb8.json} (99%) diff --git a/Cargo.lock b/Cargo.lock index 126210bd759..14fe975c52d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9503,6 +9503,7 @@ dependencies = [ "fs-err 3.3.0", "futures", "hex", + "omicron-common", "omicron-pins", "omicron-workspace-hack", "omicron-zone-package", diff --git a/common/src/lib.rs b/common/src/lib.rs index 2ce4140b728..8245e280f27 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -101,6 +101,19 @@ pub fn format_time_delta(time_delta: chrono::TimeDelta) -> String { } } +/// The system version of the Oxide software (RFD 557): the `N.P.C` form, +/// without the `+gitHHHH` build metadata that a published release carries. +/// +/// Under current policy, each new release is a major version bump, and +/// generally referred to only by the major version (e.g. 8.0.0 is referred +/// to as "v8", "version 8", or "release 8" to customers). The use of semantic +/// versioning is mostly to hedge for perhaps wanting something more granular in +/// the future. +/// +/// The releng build process appends build metadata to this to produce the full +/// version string; the `/v1/version` API endpoint returns it as-is. +pub const SYSTEM_VERSION: semver::Version = semver::Version::new(21, 0, 0); + pub const OMICRON_DPD_TAG: &str = "omicron"; /// A wrapper struct that does nothing other than elide the inner value from diff --git a/dev-tools/releng/Cargo.toml b/dev-tools/releng/Cargo.toml index 59f4cdafb7c..40af2ba73eb 100644 --- a/dev-tools/releng/Cargo.toml +++ b/dev-tools/releng/Cargo.toml @@ -16,6 +16,7 @@ dev-tools-common.workspace = true fs-err = { workspace = true, features = ["tokio"] } futures.workspace = true hex.workspace = true +omicron-common.workspace = true omicron-pins.workspace = true omicron-workspace-hack.workspace = true omicron-zone-package.workspace = true diff --git a/dev-tools/releng/src/main.rs b/dev-tools/releng/src/main.rs index c374c517d25..789f130c1b3 100644 --- a/dev-tools/releng/src/main.rs +++ b/dev-tools/releng/src/main.rs @@ -29,7 +29,6 @@ use fs_err::tokio as fs; use omicron_zone_package::config::Config; use omicron_zone_package::config::PackageName; use omicron_zone_package::package::PackageSource; -use semver::Version; use slog::Drain; use slog::Logger; use slog::debug; @@ -43,16 +42,6 @@ use tokio::sync::Semaphore; use crate::cmd::Command; use crate::job::Jobs; -/// The base version we're currently building. Build information is appended to -/// this later on. -/// -/// Under current policy, each new release is a major version bump, and -/// generally referred to only by the major version (e.g. 8.0.0 is referred -/// to as "v8", "version 8", or "release 8" to customers). The use of semantic -/// versioning is mostly to hedge for perhaps wanting something more granular in -/// the future. -const BASE_VERSION: Version = Version::new(21, 0, 0); - const RETRY_ATTEMPTS: usize = 3; #[derive(Debug, Clone, Copy)] @@ -261,7 +250,7 @@ async fn main() -> Result<()> { .trim() .to_owned(); - let mut version = BASE_VERSION.clone(); + let mut version = omicron_common::SYSTEM_VERSION.clone(); // Differentiate between CI and local builds. We use `0.word` as the // prerelease field because it comes before `alpha`. version.pre = diff --git a/nexus/external-api/output/nexus_tags.txt b/nexus/external-api/output/nexus_tags.txt index 0f7057078fc..42e251abc2b 100644 --- a/nexus/external-api/output/nexus_tags.txt +++ b/nexus/external-api/output/nexus_tags.txt @@ -332,6 +332,7 @@ user_builtin_view GET /v1/system/users-builtin/{user API operations found with tag "system/status" OPERATION ID METHOD URL PATH ping GET /v1/ping +version GET /v1/version API operations found with tag "system/subnet-pools" OPERATION ID METHOD URL PATH diff --git a/nexus/external-api/src/lib.rs b/nexus/external-api/src/lib.rs index c3641f71d87..c7fb9bec665 100644 --- a/nexus/external-api/src/lib.rs +++ b/nexus/external-api/src/lib.rs @@ -86,6 +86,7 @@ api_versions!([ // | date-based version should be at the top of the list. // v // (next_yyyy_mm_dd_nn, IDENT), + (2026_07_07_00, API_VERSION), (2026_06_10_00, BGP_CONFIGURATION_UPDATE), (2026_06_08_00, INSTANCE_CPU_TYPE_TURIN_V2), (2026_06_05_00, EXTERNAL_JUMBO_FRAMES), @@ -417,6 +418,25 @@ pub trait NexusExternalApi { })) } + /// Fetch version information + /// + /// Returns the version of the Oxide API currently being served and the + /// version of the system software serving it. + #[endpoint { + method = GET, + path = "/v1/version", + tags = ["system/status"], + versions = VERSION_API_VERSION.., + }] + async fn version( + _rqctx: RequestContext, + ) -> Result, HttpError> { + Ok(HttpResponseOk(latest::system::Version { + api_version: latest_version(), + system_version: omicron_common::SYSTEM_VERSION, + })) + } + /// Fetch top-level IAM policy #[endpoint { method = GET, diff --git a/nexus/tests/integration_tests/basic.rs b/nexus/tests/integration_tests/basic.rs index 3cac5724ea3..4b939a8220f 100644 --- a/nexus/tests/integration_tests/basic.rs +++ b/nexus/tests/integration_tests/basic.rs @@ -558,6 +558,17 @@ async fn test_ping(cptestctx: &ControlPlaneTestContext) { assert_eq!(health.status, system::PingStatus::Ok); } +#[nexus_test] +async fn test_version(cptestctx: &ControlPlaneTestContext) { + let client = &cptestctx.external_client; + + let version = NexusRequest::object_get(client, "/v1/version") + .execute_and_parse_unwrap::() + .await; + assert_eq!(version.api_version, nexus_external_api::latest_version()); + assert_eq!(version.system_version, omicron_common::SYSTEM_VERSION); +} + /// Test that the external API returns gzip-compressed responses when the /// client sends Accept-Encoding: gzip. #[nexus_test] diff --git a/nexus/tests/output/uncovered-authz-endpoints.txt b/nexus/tests/output/uncovered-authz-endpoints.txt index c164f37b32f..7e9f4d7bb9c 100644 --- a/nexus/tests/output/uncovered-authz-endpoints.txt +++ b/nexus/tests/output/uncovered-authz-endpoints.txt @@ -16,6 +16,7 @@ console_settings_page (get "/settings/{path}") console_system_page (get "/system/{path}") console_silo_utilization (get "/utilization") ping (get "/v1/ping") +version (get "/v1/version") device_auth_request (post "/device/auth") device_auth_confirm (post "/device/confirm") device_access_token (post "/device/token") diff --git a/nexus/types/versions/src/api_version/mod.rs b/nexus/types/versions/src/api_version/mod.rs new file mode 100644 index 00000000000..2f4f0e0ac12 --- /dev/null +++ b/nexus/types/versions/src/api_version/mod.rs @@ -0,0 +1,9 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! Version `API_VERSION` of the Nexus external API. +//! +//! Adds the `Version` type for the new `/v1/version` endpoint. + +pub mod system; diff --git a/nexus/types/versions/src/api_version/system.rs b/nexus/types/versions/src/api_version/system.rs new file mode 100644 index 00000000000..fddbd01cd16 --- /dev/null +++ b/nexus/types/versions/src/api_version/system.rs @@ -0,0 +1,23 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! System types for version `API_VERSION`. + +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +/// Version information for the API and the system software serving it +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, JsonSchema)] +pub struct Version { + /// The API version, e.g. `2026062900.0.0`, matching the `version` field + /// of the OpenAPI schema. Clients should send it in the `api-version` + /// header to select this version of the API. The official SDKs do this + /// automatically. + pub api_version: semver::Version, + + /// The version of the system software serving this API, e.g., `21.0.0`. + /// Use this version to find the right docs and release notes. During an + /// upgrade, some components of the system may be running a newer version. + pub system_version: semver::Version, +} diff --git a/nexus/types/versions/src/latest.rs b/nexus/types/versions/src/latest.rs index cf4becc0fa5..24ebf23b679 100644 --- a/nexus/types/versions/src/latest.rs +++ b/nexus/types/versions/src/latest.rs @@ -423,6 +423,7 @@ pub mod system { pub use crate::v2025_11_20_00::system::AllowListUpdate; pub use crate::v2025_11_20_00::system::Ping; pub use crate::v2025_11_20_00::system::PingStatus; + pub use crate::v2026_06_29_00::system::Version; } pub mod system_networking { diff --git a/nexus/types/versions/src/lib.rs b/nexus/types/versions/src/lib.rs index 0998a1a4d18..640110e017a 100644 --- a/nexus/types/versions/src/lib.rs +++ b/nexus/types/versions/src/lib.rs @@ -95,3 +95,5 @@ pub mod v2026_06_05_00; pub mod v2026_06_08_00; #[path = "bgp_configuration_update/mod.rs"] pub mod v2026_06_10_00; +#[path = "api_version/mod.rs"] +pub mod v2026_06_29_00; diff --git a/openapi/nexus/nexus-2026061000.0.0-9779e2.json.gitstub b/openapi/nexus/nexus-2026061000.0.0-9779e2.json.gitstub new file mode 100644 index 00000000000..924c037e31b --- /dev/null +++ b/openapi/nexus/nexus-2026061000.0.0-9779e2.json.gitstub @@ -0,0 +1 @@ +3a8796bc025128e4df2fe38ebc4404755823fa1d:openapi/nexus/nexus-2026061000.0.0-9779e2.json diff --git a/openapi/nexus/nexus-2026061000.0.0-9779e2.json b/openapi/nexus/nexus-2026070700.0.0-d83eb8.json similarity index 99% rename from openapi/nexus/nexus-2026061000.0.0-9779e2.json rename to openapi/nexus/nexus-2026070700.0.0-d83eb8.json index a71809018ae..842e617c672 100644 --- a/openapi/nexus/nexus-2026061000.0.0-9779e2.json +++ b/openapi/nexus/nexus-2026070700.0.0-d83eb8.json @@ -7,7 +7,7 @@ "url": "https://oxide.computer", "email": "api@oxide.computer" }, - "version": "2026061000.0.0" + "version": "2026070700.0.0" }, "paths": { "/device/auth": { @@ -14062,6 +14062,34 @@ } } }, + "/v1/version": { + "get": { + "tags": [ + "system/status" + ], + "summary": "Fetch version information", + "description": "Returns the version of the Oxide API currently being served and the version of the system software serving it.", + "operationId": "version", + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Version" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, "/v1/vpc-firewall-rules": { "get": { "tags": [ @@ -30655,6 +30683,26 @@ "values" ] }, + "Version": { + "description": "Version information for the API and the system software serving it", + "type": "object", + "properties": { + "api_version": { + "description": "The API version, e.g. `2026062900.0.0`, matching the `version` field of the OpenAPI schema. Clients should send it in the `api-version` header to select this version of the API. The official SDKs do this automatically.", + "type": "string", + "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$" + }, + "system_version": { + "description": "The version of the system software serving this API, e.g., `21.0.0`. Use this version to find the right docs and release notes. During an upgrade, some components of the system may be running a newer version.", + "type": "string", + "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$" + } + }, + "required": [ + "api_version", + "system_version" + ] + }, "VirtualResourceCounts": { "description": "A collection of resource counts used to describe capacity and utilization", "type": "object", diff --git a/openapi/nexus/nexus-latest.json b/openapi/nexus/nexus-latest.json index d5753ab6c18..5284e010dcf 120000 --- a/openapi/nexus/nexus-latest.json +++ b/openapi/nexus/nexus-latest.json @@ -1 +1 @@ -nexus-2026061000.0.0-9779e2.json \ No newline at end of file +nexus-2026070700.0.0-d83eb8.json \ No newline at end of file