mirror of
https://github.com/Corfirean/coa-server-manager.git
synced 2026-11-12 20:11:02 +00:00
Add coordinated release packaging and disposable update acceptance
This commit is contained in:
1 parent
0d6d8a0db8
commit
171d9a2bf8
2 files changed
+92
No files matched your search
@@ -0,0 +1,38 @@
|
||||
use std::{fs, path::PathBuf};
|
||||
use coa_core::{manifest::{Kind, Manifest}, package::{self, BuildOptions}, pkgsource::{self, Source}, release, signing, Result};
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let args: Vec<String> = std::env::args().skip(1).collect();
|
||||
let source = Source::Dir(args[1].clone().into());
|
||||
let (previous, _) = pkgsource::fetch_manifest(&source, signing::EMBEDDED_PUBLIC_KEY)?;
|
||||
let tree = PathBuf::from(&args[2]);
|
||||
if args[0] == "extract" {
|
||||
assert!(!tree.exists(), "Use a new release tree");
|
||||
package::extract(&PathBuf::from(&args[1]), &previous, &tree, &|_, _| {})?;
|
||||
println!("Verified and extracted {}: {} historical migrations", previous.version, previous.migrations.len());
|
||||
return Ok(());
|
||||
}
|
||||
assert_eq!(args[0], "pack");
|
||||
let out = PathBuf::from(&args[3]);
|
||||
assert!(!out.exists(), "Release addresses must be immutable");
|
||||
for migration in &previous.migrations {
|
||||
let entry = previous.files.iter().find(|file| file.path.starts_with("_migrations/") && file.sha256 == migration.sha256).expect("Historical SQL entry");
|
||||
assert_eq!(coa_core::fsx::sha256_file(&tree.join(&entry.path))?, entry.sha256, "Historical SQL changed: {}", migration.id);
|
||||
}
|
||||
let mut manifest = package::build(&tree, &out, &BuildOptions {
|
||||
kind: Kind::Update, version: args[4].clone(), core_commit: Some(args[5].clone()),
|
||||
bots_commit: previous.bots.and_then(|revision| revision.commit),
|
||||
built_at: chrono::Utc::now().to_rfc3339(), part_size: package::DEFAULT_PART_SIZE,
|
||||
migrations: previous.migrations.clone(),
|
||||
}, &|line| println!("{line}"))?;
|
||||
manifest.min_manager_version = "0.6.5".into();
|
||||
manifest.validate()?;
|
||||
coa_core::fsx::atomic_write(&out.join("manifest.json"), &serde_json::to_vec_pretty(&manifest)?)?;
|
||||
let key_path = PathBuf::from(std::env::var("USERPROFILE").unwrap()).join(".coa-manager/signing/manifest-signing.key");
|
||||
release::sign_manifest(&out, &fs::read_to_string(key_path)?)?;
|
||||
coa_core::release_schema::verify_package(&out, signing::EMBEDDED_PUBLIC_KEY)?;
|
||||
let reread: Manifest = serde_json::from_slice(&fs::read(out.join("manifest.json"))?)?;
|
||||
assert_eq!(serde_json::to_value(reread.migrations)?, serde_json::to_value(previous.migrations)?);
|
||||
println!("PASS: signed {}; all historical migration bytes, checksums, aliases and order preserved", manifest.version);
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
use std::{collections::BTreeMap, path::PathBuf};
|
||||
use coa_core::{backup, driver, registry::MetaDir, update::{self, RepackEnv}, Result};
|
||||
|
||||
fn players(db: &coa_core::db::Db) -> Result<String> {
|
||||
db.query("SELECT CONCAT(id,':',username,':',HEX(salt),':',HEX(verifier)) FROM acore_auth.account WHERE id=900001; SELECT CONCAT(guid,':',account,':',name,':',level,':',money) FROM acore_characters.characters WHERE guid=900001; SELECT CONCAT(guid,':',itemEntry,':',owner_guid,':',enchantments) FROM acore_characters.item_instance WHERE guid=900001; SELECT CONCAT(guid,':',bag,':',slot,':',item) FROM acore_characters.character_inventory WHERE guid=900001;")
|
||||
}
|
||||
|
||||
fn exercise(root: &std::path::Path, package: &str) -> Result<()> {
|
||||
let meta_dir = coa_core::registry::metadata_dir_for(root)?;
|
||||
let (_, meta) = MetaDir::open(&meta_dir)?;
|
||||
let expected = backup::with_database(root, |db| [coa_core::realms::Mode::Coa, coa_core::realms::Mode::Wildcard].into_iter().map(|mode| players(&db.clone().for_realm(mode))).collect::<Result<Vec<_>>>())?;
|
||||
assert!(driver::run(root, driver::Verb::StopAll)?.ok);
|
||||
let source = coa_core::pkgsource::Source::Dir(package.into());
|
||||
let preview = update::preview(root, &meta, &source, coa_core::signing::EMBEDDED_PUBLIC_KEY, &BTreeMap::new())?;
|
||||
assert_eq!(preview.pending_migrations, 0, "Historical SQL must not replay");
|
||||
assert!(!preview.items.iter().any(|item| item.action == update::Action::Conflict), "Unresolved candidate files: {:?}", preview.items);
|
||||
let environment = RepackEnv { root, meta_dir: &meta_dir };
|
||||
let outcome = update::apply(&update::Params {
|
||||
root, meta_dir: &meta_dir, source, trusted_key: coa_core::signing::EMBEDDED_PUBLIC_KEY,
|
||||
resolutions: BTreeMap::new(), cancel: Default::default(), env: &environment, fail_after_ops: None,
|
||||
}, &|stage, progress| println!("{progress}% {stage}"))?;
|
||||
assert_eq!(outcome.txn.state, update::State::Committed, "{:?}", outcome.txn);
|
||||
backup::with_database(root, |db| {
|
||||
for (index, mode) in [coa_core::realms::Mode::Coa, coa_core::realms::Mode::Wildcard].into_iter().enumerate() {
|
||||
let realm = db.clone().for_realm(mode);
|
||||
assert_eq!(players(&realm)?, expected[index], "Player data changed in {mode:?}");
|
||||
let problems = coa_core::schema_check::check(&realm, root)?;
|
||||
assert!(problems.is_empty(), "{problems:?}");
|
||||
}
|
||||
Ok(())
|
||||
})?;
|
||||
let release = coa_core::squid::release(root).expect("SQUID release metadata");
|
||||
println!("SQUID metadata: {release:?}");
|
||||
assert_eq!(coa_core::squid::fields(root)?.len(), 40);
|
||||
let (_, installed) = MetaDir::open(&meta_dir)?;
|
||||
let repeated = update::preview(root, &installed, &coa_core::pkgsource::Source::Dir(package.into()), coa_core::signing::EMBEDDED_PUBLIC_KEY, &BTreeMap::new())?;
|
||||
assert_eq!(repeated.pending_migrations, 0);
|
||||
assert!(repeated.items.iter().all(|item| item.action == update::Action::Skip), "{:?}", repeated.items);
|
||||
println!("PASS: coordinated update committed, both realms started, player fields preserved, historical SQL skipped, repeated preview clean");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let args: Vec<String> = std::env::args().skip(1).collect();
|
||||
let root = PathBuf::from(&args[0]);
|
||||
assert!(root.file_name().unwrap().to_string_lossy().starts_with("coa-schema-fixture-chaos-064-transition-"));
|
||||
assert_eq!(std::fs::read(root.join(".transition-fixture"))?, b"disposable published-064 transition fixture");
|
||||
assert_eq!(std::fs::read(root.join(".release-schema-fixture"))?, b"disposable release-schema fixture");
|
||||
assert!(!root.join("Data").symlink_metadata()?.file_type().is_symlink());
|
||||
assert!(driver::run(&root, driver::Verb::StopAll)?.ok);
|
||||
let outcome = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| exercise(&root, &args[1])));
|
||||
assert!(driver::run(&root, driver::Verb::StopAll)?.ok, "Fixture shutdown failed");
|
||||
outcome.map_err(|_| coa_core::Error::Invalid("Disposable coordinated acceptance failed; stopped services and retained recovery copies".into()))?
|
||||
}
|
||||
Reference in new issue
Block a user