mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-04-04 14:29:45 +00:00
feat: add registry os promote command for cross-registry OS version promotion
Batch promotes an entire OS version (metadata + all iso/squashfs/img assets across all platforms) from one registry to another, mirroring the existing package promote command.
This commit is contained in:
2
container-runtime/package-lock.json
generated
2
container-runtime/package-lock.json
generated
@@ -37,7 +37,7 @@
|
|||||||
},
|
},
|
||||||
"../sdk/dist": {
|
"../sdk/dist": {
|
||||||
"name": "@start9labs/start-sdk",
|
"name": "@start9labs/start-sdk",
|
||||||
"version": "0.4.0-beta.66",
|
"version": "1.0.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@iarna/toml": "^3.0.0",
|
"@iarna/toml": "^3.0.0",
|
||||||
|
|||||||
@@ -1826,6 +1826,21 @@ registry.os.version.signer-not-authorized:
|
|||||||
fr_FR: "Le signataire %{signer} n'est pas autorisé à signer pour v%{version}"
|
fr_FR: "Le signataire %{signer} n'est pas autorisé à signer pour v%{version}"
|
||||||
pl_PL: "Sygnatariusz %{signer} nie jest autoryzowany do podpisywania v%{version}"
|
pl_PL: "Sygnatariusz %{signer} nie jest autoryzowany do podpisywania v%{version}"
|
||||||
|
|
||||||
|
# registry/os/promote.rs
|
||||||
|
registry.os.promote.need-from-or-to:
|
||||||
|
en_US: "At least one of --from or --to must be specified"
|
||||||
|
de_DE: "Mindestens --from oder --to muss angegeben werden"
|
||||||
|
es_ES: "Se debe especificar al menos --from o --to"
|
||||||
|
fr_FR: "Au moins --from ou --to doit être spécifié"
|
||||||
|
pl_PL: "Należy podać przynajmniej --from lub --to"
|
||||||
|
|
||||||
|
registry.os.promote.version-not-found:
|
||||||
|
en_US: "OS version %{version} not found on source registry"
|
||||||
|
de_DE: "OS-Version %{version} nicht in der Quell-Registry gefunden"
|
||||||
|
es_ES: "Versión del SO %{version} no encontrada en el registro de origen"
|
||||||
|
fr_FR: "Version OS %{version} introuvable dans le registre source"
|
||||||
|
pl_PL: "Wersja OS %{version} nie znaleziona w rejestrze źródłowym"
|
||||||
|
|
||||||
# registry/package/mod.rs
|
# registry/package/mod.rs
|
||||||
registry.package.remove-not-exist:
|
registry.package.remove-not-exist:
|
||||||
en_US: "%{id}@%{version}%{sighash} does not exist, so not removed"
|
en_US: "%{id}@%{version}%{sighash} does not exist, so not removed"
|
||||||
@@ -5311,6 +5326,13 @@ about.persist-new-notification:
|
|||||||
fr_FR: "Persister une nouvelle notification"
|
fr_FR: "Persister une nouvelle notification"
|
||||||
pl_PL: "Utrwal nowe powiadomienie"
|
pl_PL: "Utrwal nowe powiadomienie"
|
||||||
|
|
||||||
|
about.promote-os-registry:
|
||||||
|
en_US: "Promote an OS version from one registry to another"
|
||||||
|
de_DE: "Eine OS-Version von einer Registry in eine andere heraufstufen"
|
||||||
|
es_ES: "Promover una versión del SO de un registro a otro"
|
||||||
|
fr_FR: "Promouvoir une version OS d'un registre à un autre"
|
||||||
|
pl_PL: "Promuj wersję OS z jednego rejestru do drugiego"
|
||||||
|
|
||||||
about.promote-package-registry:
|
about.promote-package-registry:
|
||||||
en_US: "Promote a package from one registry to another"
|
en_US: "Promote a package from one registry to another"
|
||||||
de_DE: "Ein Paket von einer Registry in eine andere heraufstufen"
|
de_DE: "Ein Paket von einer Registry in eine andere heraufstufen"
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ pub const SIG_CONTEXT: &str = "startos";
|
|||||||
|
|
||||||
pub mod asset;
|
pub mod asset;
|
||||||
pub mod index;
|
pub mod index;
|
||||||
|
pub mod promote;
|
||||||
pub mod version;
|
pub mod version;
|
||||||
|
|
||||||
pub fn os_api<C: Context>() -> ParentHandler<C> {
|
pub fn os_api<C: Context>() -> ParentHandler<C> {
|
||||||
@@ -28,4 +29,10 @@ pub fn os_api<C: Context>() -> ParentHandler<C> {
|
|||||||
"version",
|
"version",
|
||||||
version::version_api::<C>().with_about("about.commands-add-remove-list-versions"),
|
version::version_api::<C>().with_about("about.commands-add-remove-list-versions"),
|
||||||
)
|
)
|
||||||
|
.subcommand(
|
||||||
|
"promote",
|
||||||
|
from_fn_async(promote::cli_os_promote)
|
||||||
|
.no_display()
|
||||||
|
.with_about("about.promote-os-registry"),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
114
core/src/registry/os/promote.rs
Normal file
114
core/src/registry/os/promote.rs
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
use clap::Parser;
|
||||||
|
use exver::Version;
|
||||||
|
use imbl_value::InternedString;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
use crate::context::CliContext;
|
||||||
|
use crate::prelude::*;
|
||||||
|
use crate::registry::os::SIG_CONTEXT;
|
||||||
|
use crate::registry::os::index::OsIndex;
|
||||||
|
use crate::registry::package::promote::{call_registry, resolve_registry_url};
|
||||||
|
use crate::sign::commitment::blake3::Blake3Commitment;
|
||||||
|
use crate::sign::ed25519::Ed25519;
|
||||||
|
use crate::sign::{AnySignature, SignatureScheme};
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize, Parser)]
|
||||||
|
#[group(skip)]
|
||||||
|
#[command(rename_all = "kebab-case")]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct CliOsPromoteParams {
|
||||||
|
#[arg(long, help = "help.arg.from-registry-url")]
|
||||||
|
pub from: Option<Url>,
|
||||||
|
#[arg(long, help = "help.arg.to-registry-url")]
|
||||||
|
pub to: Option<Url>,
|
||||||
|
#[arg(help = "help.arg.os-version")]
|
||||||
|
pub version: Version,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn cli_os_promote(
|
||||||
|
ctx: CliContext,
|
||||||
|
CliOsPromoteParams { from, to, version }: CliOsPromoteParams,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
if from.is_none() && to.is_none() {
|
||||||
|
return Err(Error::new(
|
||||||
|
eyre!("{}", t!("registry.os.promote.need-from-or-to")),
|
||||||
|
ErrorKind::InvalidRequest,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let from_url = resolve_registry_url(from.as_ref(), &ctx)?;
|
||||||
|
let to_url = resolve_registry_url(to.as_ref(), &ctx)?;
|
||||||
|
|
||||||
|
// Fetch OS index from source registry
|
||||||
|
let res: Value = call_registry(&ctx, from_url, "os.index", imbl_value::json!({})).await?;
|
||||||
|
let os_index: OsIndex = from_value(res)?;
|
||||||
|
|
||||||
|
// Find the target version
|
||||||
|
let version_info = os_index
|
||||||
|
.versions
|
||||||
|
.0
|
||||||
|
.get(&version)
|
||||||
|
.ok_or_else(|| {
|
||||||
|
Error::new(
|
||||||
|
eyre!(
|
||||||
|
"{}",
|
||||||
|
t!(
|
||||||
|
"registry.os.promote.version-not-found",
|
||||||
|
version = &version
|
||||||
|
)
|
||||||
|
),
|
||||||
|
ErrorKind::NotFound,
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// Add the version to the target registry
|
||||||
|
call_registry(
|
||||||
|
&ctx,
|
||||||
|
to_url.clone(),
|
||||||
|
"os.version.add",
|
||||||
|
imbl_value::json!({
|
||||||
|
"version": &version,
|
||||||
|
"headline": &version_info.headline,
|
||||||
|
"releaseNotes": &version_info.release_notes,
|
||||||
|
"sourceVersion": &version_info.source_version,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Promote all assets for each type and platform
|
||||||
|
promote_assets(&ctx, &to_url, &version, &version_info.iso, "os.asset.add.iso").await?;
|
||||||
|
promote_assets(&ctx, &to_url, &version, &version_info.squashfs, "os.asset.add.squashfs").await?;
|
||||||
|
promote_assets(&ctx, &to_url, &version, &version_info.img, "os.asset.add.img").await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn promote_assets(
|
||||||
|
ctx: &CliContext,
|
||||||
|
to_url: &Url,
|
||||||
|
version: &Version,
|
||||||
|
assets: &std::collections::BTreeMap<InternedString, crate::registry::asset::RegistryAsset<Blake3Commitment>>,
|
||||||
|
method: &str,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
for (platform, asset) in assets {
|
||||||
|
let commitment = &asset.commitment;
|
||||||
|
let signature =
|
||||||
|
AnySignature::Ed25519(Ed25519.sign_commitment(ctx.developer_key()?, commitment, SIG_CONTEXT)?);
|
||||||
|
|
||||||
|
call_registry(
|
||||||
|
ctx,
|
||||||
|
to_url.clone(),
|
||||||
|
method,
|
||||||
|
imbl_value::json!({
|
||||||
|
"version": version,
|
||||||
|
"platform": platform,
|
||||||
|
"url": &asset.urls[0],
|
||||||
|
"signature": signature,
|
||||||
|
"commitment": commitment,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -28,7 +28,7 @@ pub struct CliPromoteParams {
|
|||||||
pub version: VersionString,
|
pub version: VersionString,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn registry_rpc_url(url: &Url) -> Result<Url, Error> {
|
pub fn registry_rpc_url(url: &Url) -> Result<Url, Error> {
|
||||||
let mut url = url.clone();
|
let mut url = url.clone();
|
||||||
url.path_segments_mut()
|
url.path_segments_mut()
|
||||||
.map_err(|_| eyre!("Url cannot be base"))
|
.map_err(|_| eyre!("Url cannot be base"))
|
||||||
@@ -38,7 +38,7 @@ fn registry_rpc_url(url: &Url) -> Result<Url, Error> {
|
|||||||
Ok(url)
|
Ok(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve_registry_url(explicit: Option<&Url>, ctx: &CliContext) -> Result<Url, Error> {
|
pub fn resolve_registry_url(explicit: Option<&Url>, ctx: &CliContext) -> Result<Url, Error> {
|
||||||
if let Some(url) = explicit {
|
if let Some(url) = explicit {
|
||||||
registry_rpc_url(url)
|
registry_rpc_url(url)
|
||||||
} else if let Some(url) = &ctx.registry_url {
|
} else if let Some(url) = &ctx.registry_url {
|
||||||
@@ -51,7 +51,7 @@ fn resolve_registry_url(explicit: Option<&Url>, ctx: &CliContext) -> Result<Url,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn call_registry(
|
pub async fn call_registry(
|
||||||
ctx: &CliContext,
|
ctx: &CliContext,
|
||||||
url: Url,
|
url: Url,
|
||||||
method: &str,
|
method: &str,
|
||||||
|
|||||||
Reference in New Issue
Block a user