From b6fc3385de2add7313722df039bda45d4d9d3493 Mon Sep 17 00:00:00 2001 From: "oxide-reflector-bot[bot]" <130185838+oxide-reflector-bot[bot]@users.noreply.github.com> Date: Mon, 2 Mar 2026 16:45:44 +0000 Subject: [PATCH 1/4] Rebuilt with latest dependency updates --- cli/src/generated_cli.rs | 90 +++++++++++++++++++ oxide.json | 33 ++++++- sdk-httpmock/src/generated_httpmock.rs | 75 ++++++++++++++++ sdk/src/generated_sdk.rs | 120 ++++++++++++++++++++++++- 4 files changed, 315 insertions(+), 3 deletions(-) diff --git a/cli/src/generated_cli.rs b/cli/src/generated_cli.rs index 409b8a44..a9b9a0a9 100644 --- a/cli/src/generated_cli.rs +++ b/cli/src/generated_cli.rs @@ -346,6 +346,7 @@ impl Cli { } CliCommand::SystemTimeseriesQuery => Self::cli_system_timeseries_query(), CliCommand::SystemTimeseriesSchemaList => Self::cli_system_timeseries_schema_list(), + CliCommand::SystemUpdateRecoveryFinish => Self::cli_system_update_recovery_finish(), CliCommand::SystemUpdateRepositoryList => Self::cli_system_update_repository_list(), CliCommand::SystemUpdateRepositoryUpload => Self::cli_system_update_repository_upload(), CliCommand::SystemUpdateRepositoryView => Self::cli_system_update_repository_view(), @@ -7973,6 +7974,48 @@ impl Cli { .about("List timeseries schemas") } + pub fn cli_system_update_recovery_finish() -> ::clap::Command { + ::clap::Command::new("") + .arg( + ::clap::Arg::new("system-version") + .long("system-version") + .value_parser(::clap::value_parser!( + types::SetTargetReleaseParamsSystemVersion + )) + .required_unless_present("json-body") + .help("Version of the system software to make the target release."), + ) + .arg( + ::clap::Arg::new("json-body") + .long("json-body") + .value_name("JSON-FILE") + .required(false) + .value_parser(::clap::value_parser!(std::path::PathBuf)) + .help("Path to a file that contains the full json body."), + ) + .arg( + ::clap::Arg::new("json-body-template") + .long("json-body-template") + .action(::clap::ArgAction::SetTrue) + .help("XXX"), + ) + .about("Instructs the system that a system recovery operation (\"mupdate\") was") + .long_about( + "completed using the software in the specified release\n\nThe system recovery \ + operation is used to bypass the control plane to deploy known-working software \ + when the control plane itself is not functioning or otherwise unable to update \ + itself. When the control plane detects this, it stops making any changes to \ + deployed software to avoid reverting the recovery itself. This operation puts \ + the control plane back in charge of determining what software should be \ + deployed, instructing it that the specified software (which is also what's \ + currently running) is what's supposed to be deployed.\n\nIf the provided version \ + does not match what's currently running, the control plane will continue to \ + avoid changing deployed software until this operation is invoked with the \ + correct version.\n\nThis endpoint should only be called at the direction of \ + Oxide support.", + ) + } + pub fn cli_system_update_repository_list() -> ::clap::Command { ::clap::Command::new("") .arg( @@ -10078,6 +10121,9 @@ impl Cli { CliCommand::SystemTimeseriesSchemaList => { self.execute_system_timeseries_schema_list(matches).await } + CliCommand::SystemUpdateRecoveryFinish => { + self.execute_system_update_recovery_finish(matches).await + } CliCommand::SystemUpdateRepositoryList => { self.execute_system_update_repository_list(matches).await } @@ -19112,6 +19158,40 @@ impl Cli { } } + pub async fn execute_system_update_recovery_finish( + &self, + matches: &::clap::ArgMatches, + ) -> anyhow::Result<()> { + let mut request = self.client.system_update_recovery_finish(); + if let Some(value) = + matches.get_one::("system-version") + { + request = request.body_map(|body| body.system_version(value.clone())) + } + + if let Some(value) = matches.get_one::("json-body") { + let body_txt = std::fs::read_to_string(value) + .with_context(|| format!("failed to read {}", value.display()))?; + let body_value = serde_json::from_str::(&body_txt) + .with_context(|| format!("failed to parse {}", value.display()))?; + request = request.body(body_value); + } + + self.config + .execute_system_update_recovery_finish(matches, &mut request)?; + let result = request.send().await; + match result { + Ok(r) => { + self.config.success_no_item(&r); + Ok(()) + } + Err(r) => { + self.config.error(&r); + Err(anyhow::Error::new(r)) + } + } + } + pub async fn execute_system_update_repository_list( &self, matches: &::clap::ArgMatches, @@ -22992,6 +23072,14 @@ pub trait CliConfig { Ok(()) } + fn execute_system_update_recovery_finish( + &self, + matches: &::clap::ArgMatches, + request: &mut builder::SystemUpdateRecoveryFinish, + ) -> anyhow::Result<()> { + Ok(()) + } + fn execute_system_update_repository_list( &self, matches: &::clap::ArgMatches, @@ -23655,6 +23743,7 @@ pub enum CliCommand { SystemSubnetPoolUtilizationView, SystemTimeseriesQuery, SystemTimeseriesSchemaList, + SystemUpdateRecoveryFinish, SystemUpdateRepositoryList, SystemUpdateRepositoryUpload, SystemUpdateRepositoryView, @@ -23970,6 +24059,7 @@ impl CliCommand { CliCommand::SystemSubnetPoolUtilizationView, CliCommand::SystemTimeseriesQuery, CliCommand::SystemTimeseriesSchemaList, + CliCommand::SystemUpdateRecoveryFinish, CliCommand::SystemUpdateRepositoryList, CliCommand::SystemUpdateRepositoryUpload, CliCommand::SystemUpdateRepositoryView, diff --git a/oxide.json b/oxide.json index c533aa35..51c244db 100644 --- a/oxide.json +++ b/oxide.json @@ -7,7 +7,7 @@ "url": "https://oxide.computer", "email": "api@oxide.computer" }, - "version": "2026021900.0.0" + "version": "2026022500.0.0" }, "paths": { "/device/auth": { @@ -12708,6 +12708,37 @@ } } }, + "/v1/system/update/recovery-finish": { + "put": { + "tags": [ + "system/update" + ], + "summary": "Instructs the system that a system recovery operation (\"mupdate\") was", + "description": "completed using the software in the specified release\n\nThe system recovery operation is used to bypass the control plane to deploy known-working software when the control plane itself is not functioning or otherwise unable to update itself. When the control plane detects this, it stops making any changes to deployed software to avoid reverting the recovery itself. This operation puts the control plane back in charge of determining what software should be deployed, instructing it that the specified software (which is also what's currently running) is what's supposed to be deployed.\n\nIf the provided version does not match what's currently running, the control plane will continue to avoid changing deployed software until this operation is invoked with the correct version.\n\nThis endpoint should only be called at the direction of Oxide support.", + "operationId": "system_update_recovery_finish", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SetTargetReleaseParams" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, "/v1/system/update/repositories": { "get": { "tags": [ diff --git a/sdk-httpmock/src/generated_httpmock.rs b/sdk-httpmock/src/generated_httpmock.rs index 2f4b65f9..5c8d06b5 100644 --- a/sdk-httpmock/src/generated_httpmock.rs +++ b/sdk-httpmock/src/generated_httpmock.rs @@ -19130,6 +19130,60 @@ pub mod operations { } } + pub struct SystemUpdateRecoveryFinishWhen(::httpmock::When); + impl SystemUpdateRecoveryFinishWhen { + pub fn new(inner: ::httpmock::When) -> Self { + Self( + inner.method(::httpmock::Method::PUT).path_matches( + regex::Regex::new("^/v1/system/update/recovery-finish$").unwrap(), + ), + ) + } + + pub fn into_inner(self) -> ::httpmock::When { + self.0 + } + + pub fn body(self, value: &types::SetTargetReleaseParams) -> Self { + Self(self.0.json_body_obj(value)) + } + } + + pub struct SystemUpdateRecoveryFinishThen(::httpmock::Then); + impl SystemUpdateRecoveryFinishThen { + pub fn new(inner: ::httpmock::Then) -> Self { + Self(inner) + } + + pub fn into_inner(self) -> ::httpmock::Then { + self.0 + } + + pub fn no_content(self) -> Self { + Self(self.0.status(204u16)) + } + + pub fn client_error(self, status: u16, value: &types::Error) -> Self { + assert_eq!(status / 100u16, 4u16); + Self( + self.0 + .status(status) + .header("content-type", "application/json") + .json_body_obj(value), + ) + } + + pub fn server_error(self, status: u16, value: &types::Error) -> Self { + assert_eq!(status / 100u16, 5u16); + Self( + self.0 + .status(status) + .header("content-type", "application/json") + .json_body_obj(value), + ) + } + } + pub struct SystemUpdateRepositoryListWhen(::httpmock::When); impl SystemUpdateRepositoryListWhen { pub fn new(inner: ::httpmock::When) -> Self { @@ -24022,6 +24076,12 @@ pub trait MockServerExt { operations::SystemTimeseriesSchemaListWhen, operations::SystemTimeseriesSchemaListThen, ); + fn system_update_recovery_finish(&self, config_fn: F) -> ::httpmock::Mock<'_> + where + F: FnOnce( + operations::SystemUpdateRecoveryFinishWhen, + operations::SystemUpdateRecoveryFinishThen, + ); fn system_update_repository_list(&self, config_fn: F) -> ::httpmock::Mock<'_> where F: FnOnce( @@ -27559,6 +27619,21 @@ impl MockServerExt for ::httpmock::MockServer { }) } + fn system_update_recovery_finish(&self, config_fn: F) -> ::httpmock::Mock<'_> + where + F: FnOnce( + operations::SystemUpdateRecoveryFinishWhen, + operations::SystemUpdateRecoveryFinishThen, + ), + { + self.mock(|when, then| { + config_fn( + operations::SystemUpdateRecoveryFinishWhen::new(when), + operations::SystemUpdateRecoveryFinishThen::new(then), + ) + }) + } + fn system_update_repository_list(&self, config_fn: F) -> ::httpmock::Mock<'_> where F: FnOnce( diff --git a/sdk/src/generated_sdk.rs b/sdk/src/generated_sdk.rs index 2b041980..5b3ee0e4 100644 --- a/sdk/src/generated_sdk.rs +++ b/sdk/src/generated_sdk.rs @@ -65506,7 +65506,7 @@ pub mod types { /// /// API for interacting with the Oxide control plane /// -/// Version: 2026021900.0.0 +/// Version: 2026022500.0.0 pub struct Client { pub(crate) baseurl: String, pub(crate) client: reqwest::Client, @@ -65547,7 +65547,7 @@ impl Client { impl ClientInfo<()> for Client { fn api_version() -> &'static str { - "2026021900.0.0" + "2026022500.0.0" } fn baseurl(&self) -> &str { @@ -71313,6 +71313,34 @@ impl ClientSystemSubnetPoolsExt for Client { /// Upload and manage system updates pub trait ClientSystemUpdateExt { + /// Instructs the system that a system recovery operation ("mupdate") was + /// + /// completed using the software in the specified release + /// + /// The system recovery operation is used to bypass the control plane to + /// deploy known-working software when the control plane itself is not + /// functioning or otherwise unable to update itself. When the control + /// plane detects this, it stops making any changes to deployed software to + /// avoid reverting the recovery itself. This operation puts the control + /// plane back in charge of determining what software should be deployed, + /// instructing it that the specified software (which is also what's + /// currently running) is what's supposed to be deployed. + /// + /// If the provided version does not match what's currently running, the + /// control plane will continue to avoid changing deployed software until + /// this operation is invoked with the correct version. + /// + /// This endpoint should only be called at the direction of Oxide support. + /// + /// Sends a `PUT` request to `/v1/system/update/recovery-finish` + /// + /// ```ignore + /// let response = client.system_update_recovery_finish() + /// .body(body) + /// .send() + /// .await; + /// ``` + fn system_update_recovery_finish(&self) -> builder::SystemUpdateRecoveryFinish<'_>; /// List all TUF repositories /// /// Returns a paginated list of all TUF repositories ordered by system @@ -71463,6 +71491,10 @@ pub trait ClientSystemUpdateExt { } impl ClientSystemUpdateExt for Client { + fn system_update_recovery_finish(&self) -> builder::SystemUpdateRecoveryFinish<'_> { + builder::SystemUpdateRecoveryFinish::new(self) + } + fn system_update_repository_list(&self) -> builder::SystemUpdateRepositoryList<'_> { builder::SystemUpdateRepositoryList::new(self) } @@ -101137,6 +101169,90 @@ pub mod builder { } } + /// Builder for [`ClientSystemUpdateExt::system_update_recovery_finish`] + /// + /// [`ClientSystemUpdateExt::system_update_recovery_finish`]: super::ClientSystemUpdateExt::system_update_recovery_finish + #[derive(Debug, Clone)] + pub struct SystemUpdateRecoveryFinish<'a> { + client: &'a super::Client, + body: Result, + } + + impl<'a> SystemUpdateRecoveryFinish<'a> { + pub fn new(client: &'a super::Client) -> Self { + Self { + client: client, + body: Ok(::std::default::Default::default()), + } + } + + pub fn body(mut self, value: V) -> Self + where + V: std::convert::TryInto, + >::Error: std::fmt::Display, + { + self.body = value.try_into().map(From::from).map_err(|s| { + format!( + "conversion to `SetTargetReleaseParams` for body failed: {}", + s + ) + }); + self + } + + pub fn body_map(mut self, f: F) -> Self + where + F: std::ops::FnOnce( + types::builder::SetTargetReleaseParams, + ) -> types::builder::SetTargetReleaseParams, + { + self.body = self.body.map(f); + self + } + + /// Sends a `PUT` request to `/v1/system/update/recovery-finish` + pub async fn send(self) -> Result, Error> { + let Self { client, body } = self; + let body = body + .and_then(|v| types::SetTargetReleaseParams::try_from(v).map_err(|e| e.to_string())) + .map_err(Error::InvalidRequest)?; + let url = format!("{}/v1/system/update/recovery-finish", client.baseurl,); + let mut header_map = ::reqwest::header::HeaderMap::with_capacity(1usize); + header_map.append( + ::reqwest::header::HeaderName::from_static("api-version"), + ::reqwest::header::HeaderValue::from_static(super::Client::api_version()), + ); + #[allow(unused_mut)] + let mut request = client + .client + .put(url) + .header( + ::reqwest::header::ACCEPT, + ::reqwest::header::HeaderValue::from_static("application/json"), + ) + .json(&body) + .headers(header_map) + .build()?; + let info = OperationInfo { + operation_id: "system_update_recovery_finish", + }; + client.pre(&mut request, &info).await?; + let result = client.exec(request, &info).await; + client.post(&result, &info).await?; + let response = result?; + match response.status().as_u16() { + 204u16 => Ok(ResponseValue::empty(response)), + 400u16..=499u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + 500u16..=599u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + _ => Err(Error::UnexpectedResponse(response)), + } + } + } + /// Builder for [`ClientSystemUpdateExt::system_update_repository_list`] /// /// [`ClientSystemUpdateExt::system_update_repository_list`]: super::ClientSystemUpdateExt::system_update_repository_list From 2a95a9256acbdd628a7c1834bc4811ec263ab21e Mon Sep 17 00:00:00 2001 From: "oxide-reflector-bot[bot]" <130185838+oxide-reflector-bot[bot]@users.noreply.github.com> Date: Mon, 2 Mar 2026 18:25:56 +0000 Subject: [PATCH 2/4] Rebuilt with latest dependency updates --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 86a4bee9..3ae7c738 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2931,7 +2931,7 @@ dependencies = [ [[package]] name = "progenitor" version = "0.13.0" -source = "git+https://github.com/oxidecomputer/progenitor#54ee668b3b458c882a35787dc2a475d854d0a485" +source = "git+https://github.com/oxidecomputer/progenitor#f2cdb8aff9829d371d77ef125c1a36326e6e0ebe" dependencies = [ "progenitor-impl", ] @@ -2954,7 +2954,7 @@ dependencies = [ [[package]] name = "progenitor-impl" version = "0.13.0" -source = "git+https://github.com/oxidecomputer/progenitor#54ee668b3b458c882a35787dc2a475d854d0a485" +source = "git+https://github.com/oxidecomputer/progenitor#f2cdb8aff9829d371d77ef125c1a36326e6e0ebe" dependencies = [ "heck", "http 1.4.0", From 7fb091e2dc822cea623a4b87836e8af143e78730 Mon Sep 17 00:00:00 2001 From: "oxide-reflector-bot[bot]" <130185838+oxide-reflector-bot[bot]@users.noreply.github.com> Date: Mon, 2 Mar 2026 19:00:40 +0000 Subject: [PATCH 3/4] Rebuilt with latest dependency updates --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3ae7c738..140c1ddd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2931,7 +2931,7 @@ dependencies = [ [[package]] name = "progenitor" version = "0.13.0" -source = "git+https://github.com/oxidecomputer/progenitor#f2cdb8aff9829d371d77ef125c1a36326e6e0ebe" +source = "git+https://github.com/oxidecomputer/progenitor#c3776c767d2e76fc45d8c6fa77684d7ff823e1eb" dependencies = [ "progenitor-impl", ] @@ -2954,7 +2954,7 @@ dependencies = [ [[package]] name = "progenitor-impl" version = "0.13.0" -source = "git+https://github.com/oxidecomputer/progenitor#f2cdb8aff9829d371d77ef125c1a36326e6e0ebe" +source = "git+https://github.com/oxidecomputer/progenitor#c3776c767d2e76fc45d8c6fa77684d7ff823e1eb" dependencies = [ "heck", "http 1.4.0", From b7974bbf74f5b609f9092816c3940433de40620f Mon Sep 17 00:00:00 2001 From: "Adam H. Leventhal" Date: Mon, 2 Mar 2026 14:46:19 -0800 Subject: [PATCH 4/4] add recovery-finish --- cli/docs/cli.json | 24 ++++++++++++++++++++++++ cli/src/cli_builder.rs | 1 + 2 files changed, 25 insertions(+) diff --git a/cli/docs/cli.json b/cli/docs/cli.json index 27269d31..8c3939e9 100644 --- a/cli/docs/cli.json +++ b/cli/docs/cli.json @@ -8757,6 +8757,30 @@ } ], "subcommands": [ + { + "name": "recovery-finish", + "about": "Instructs the system that a system recovery operation (\"mupdate\") was", + "long_about": "completed using the software in the specified release\n\nThe system recovery operation is used to bypass the control plane to deploy known-working software when the control plane itself is not functioning or otherwise unable to update itself. When the control plane detects this, it stops making any changes to deployed software to avoid reverting the recovery itself. This operation puts the control plane back in charge of determining what software should be deployed, instructing it that the specified software (which is also what's currently running) is what's supposed to be deployed.\n\nIf the provided version does not match what's currently running, the control plane will continue to avoid changing deployed software until this operation is invoked with the correct version.\n\nThis endpoint should only be called at the direction of Oxide support.", + "args": [ + { + "long": "json-body", + "help": "Path to a file that contains the full json body." + }, + { + "long": "json-body-template", + "help": "XXX" + }, + { + "long": "profile", + "help": "Configuration profile to use for commands", + "global": true + }, + { + "long": "system-version", + "help": "Version of the system software to make the target release." + } + ] + }, { "name": "repo", "args": [ diff --git a/cli/src/cli_builder.rs b/cli/src/cli_builder.rs index 091c99e7..86c7b959 100644 --- a/cli/src/cli_builder.rs +++ b/cli/src/cli_builder.rs @@ -698,6 +698,7 @@ fn xxx<'a>(command: CliCommand) -> Option<&'a str> { CliCommand::SystemUpdateStatus => Some("system update status"), CliCommand::SystemUpdateRepositoryList => Some("system update repo list"), CliCommand::SystemUpdateRepositoryView => Some("system update repo view"), + CliCommand::SystemUpdateRecoveryFinish => Some("system update recovery-finish"), CliCommand::SystemUpdateRepositoryUpload => None, // Manually implemented CliCommand::SwitchList => Some("system hardware switch list"),