mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-20 16:07:11 +08:00
feat(ql3): add reviewed upgrade staging
This commit is contained in:
@@ -240,7 +240,7 @@ function validateMilestoneRecord(record, product, sourceRevision, variant) {
|
||||
: 'cluster_integration_candidate_not_public_release';
|
||||
const expectedSchema =
|
||||
product === 'local'
|
||||
? 'qinglong/alpha-local-milestone@v3'
|
||||
? 'qinglong/alpha-local-milestone@v4'
|
||||
: 'qinglong/alpha-cluster-milestone@v1';
|
||||
if (
|
||||
!exactKeys(record, ['artifactName', 'schema', 'maturity', 'manifest']) ||
|
||||
|
||||
@@ -12,7 +12,7 @@ const {
|
||||
const { readReleaseIdentity } = require('./lib/ql3-release-identity.cjs');
|
||||
|
||||
const DEFAULT_ROOT = path.resolve(__dirname, '..');
|
||||
const SCHEMA = 'qinglong/alpha-local-milestone@v3';
|
||||
const SCHEMA = 'qinglong/alpha-local-milestone@v4';
|
||||
const ARCHITECTURES = Object.freeze(['amd64', 'arm64']);
|
||||
const FILES = Object.freeze({
|
||||
readme: 'README.md',
|
||||
@@ -220,6 +220,7 @@ function bundleRecord(options, architecture) {
|
||||
operatorImageId: report.operatorImageId,
|
||||
verificationSha256: report.verificationSha256,
|
||||
upgradeReadinessSha256: report.upgradeReadinessSha256,
|
||||
upgradeRehearsalSha256: report.upgradeRehearsalSha256,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -234,6 +235,7 @@ function validateArtifactRecord(record, architecture, manifest) {
|
||||
'operatorImageId',
|
||||
'verificationSha256',
|
||||
'upgradeReadinessSha256',
|
||||
'upgradeRehearsalSha256',
|
||||
]) ||
|
||||
record.artifactName !==
|
||||
artifactName(manifest.sourceRevision, architecture, manifest.variant) ||
|
||||
@@ -248,6 +250,7 @@ function validateArtifactRecord(record, architecture, manifest) {
|
||||
!SHA256_PATTERN.test(record.operatorImageId || '') ||
|
||||
!SHA256_PATTERN.test(record.verificationSha256 || '') ||
|
||||
!SHA256_PATTERN.test(record.upgradeReadinessSha256 || '') ||
|
||||
!SHA256_PATTERN.test(record.upgradeRehearsalSha256 || '') ||
|
||||
record.applicationImageId === record.operatorImageId
|
||||
) {
|
||||
fail(`${architecture} milestone artifact record is incompatible`);
|
||||
@@ -316,7 +319,7 @@ function auditLocalAlphaMilestone(options) {
|
||||
'artifacts',
|
||||
'readme',
|
||||
]) ||
|
||||
manifest.schemaVersion !== 3 ||
|
||||
manifest.schemaVersion !== 4 ||
|
||||
manifest.schema !== SCHEMA ||
|
||||
manifest.maturity !== 'alpha_candidate_not_public_release' ||
|
||||
manifest.product !== 'local' ||
|
||||
@@ -361,6 +364,8 @@ function auditLocalAlphaMilestone(options) {
|
||||
new Set(records.map((record) => record.verificationSha256)).size !==
|
||||
ARCHITECTURES.length ||
|
||||
new Set(records.map((record) => record.upgradeReadinessSha256)).size !==
|
||||
ARCHITECTURES.length ||
|
||||
new Set(records.map((record) => record.upgradeRehearsalSha256)).size !==
|
||||
ARCHITECTURES.length
|
||||
) {
|
||||
fail('milestone architecture subjects are not distinct');
|
||||
@@ -388,7 +393,7 @@ function auditLocalAlphaMilestone(options) {
|
||||
}
|
||||
return Object.freeze({
|
||||
schemaVersion: 1,
|
||||
schema: 'qinglong/alpha-local-milestone-audit@v3',
|
||||
schema: 'qinglong/alpha-local-milestone-audit@v4',
|
||||
sourceRevision: manifest.sourceRevision,
|
||||
version: manifest.version,
|
||||
variant: manifest.variant,
|
||||
@@ -427,7 +432,9 @@ function finalizeLocalAlphaMilestone(options) {
|
||||
artifacts.amd64.archiveSha256 === artifacts.arm64.archiveSha256 ||
|
||||
artifacts.amd64.verificationSha256 === artifacts.arm64.verificationSha256 ||
|
||||
artifacts.amd64.upgradeReadinessSha256 ===
|
||||
artifacts.arm64.upgradeReadinessSha256
|
||||
artifacts.arm64.upgradeReadinessSha256 ||
|
||||
artifacts.amd64.upgradeRehearsalSha256 ===
|
||||
artifacts.arm64.upgradeRehearsalSha256
|
||||
) {
|
||||
fail('milestone architecture subjects must be distinct');
|
||||
}
|
||||
@@ -440,7 +447,7 @@ function finalizeLocalAlphaMilestone(options) {
|
||||
path.join(normalized.outputRoot, FILES.readme),
|
||||
);
|
||||
const manifest = {
|
||||
schemaVersion: 3,
|
||||
schemaVersion: 4,
|
||||
schema: SCHEMA,
|
||||
maturity: 'alpha_candidate_not_public_release',
|
||||
product: 'local',
|
||||
|
||||
@@ -10,8 +10,8 @@ const { auditClusterImageSbom } = require('./ql3-cluster-image-sbom.cjs');
|
||||
const { readReleaseIdentity } = require('./lib/ql3-release-identity.cjs');
|
||||
|
||||
const DEFAULT_ROOT = path.resolve(__dirname, '..');
|
||||
const SCHEMA = 'qinglong/alpha-local-trial-kit@v6';
|
||||
const VERIFICATION_SCHEMA = 'qinglong/alpha-local-trial-kit-verification@v4';
|
||||
const SCHEMA = 'qinglong/alpha-local-trial-kit@v7';
|
||||
const VERIFICATION_SCHEMA = 'qinglong/alpha-local-trial-kit-verification@v5';
|
||||
const QUICKSTART_TEMPLATE = path.join(
|
||||
DEFAULT_ROOT,
|
||||
'scripts/templates/ql3-local-alpha-quickstart.sh',
|
||||
@@ -20,6 +20,10 @@ const UPGRADE_READINESS_TEMPLATE = path.join(
|
||||
DEFAULT_ROOT,
|
||||
'scripts/templates/ql3-local-alpha-upgrade-readiness.sh',
|
||||
);
|
||||
const UPGRADE_REHEARSAL_TEMPLATE = path.join(
|
||||
DEFAULT_ROOT,
|
||||
'scripts/templates/ql3-local-alpha-upgrade-rehearsal.sh',
|
||||
);
|
||||
const ARCHITECTURES = Object.freeze(['amd64', 'arm64']);
|
||||
const VARIANTS = Object.freeze(['headless', 'console']);
|
||||
const ARCHIVE_MIN_BYTES = 1024;
|
||||
@@ -27,6 +31,7 @@ const MAX_JSON_BYTES = 4 * 1024 * 1024;
|
||||
const MAX_README_BYTES = 512 * 1024;
|
||||
const MAX_QUICKSTART_BYTES = 256 * 1024;
|
||||
const MAX_UPGRADE_READINESS_BYTES = 256 * 1024;
|
||||
const MAX_UPGRADE_REHEARSAL_BYTES = 256 * 1024;
|
||||
const SHA256_PATTERN = /^sha256:[0-9a-f]{64}$/u;
|
||||
const REVISION_PATTERN = /^[0-9a-f]{40}$/u;
|
||||
const FILES = Object.freeze({
|
||||
@@ -35,6 +40,7 @@ const FILES = Object.freeze({
|
||||
verificationEvidence: 'verification-evidence.json',
|
||||
quickstart: 'quickstart.sh',
|
||||
upgradeReadiness: 'upgrade-readiness.sh',
|
||||
upgradeRehearsal: 'upgrade-rehearsal.sh',
|
||||
readme: 'README.md',
|
||||
manifest: 'manifest.json',
|
||||
checksums: 'SHA256SUMS',
|
||||
@@ -51,6 +57,7 @@ const VERIFICATION = Object.freeze({
|
||||
standaloneFreshLifecycle: 'passed',
|
||||
localApiCancellation: 'passed',
|
||||
legacyUpgradeReadiness: 'passed',
|
||||
legacyUpgradeStage: 'passed',
|
||||
});
|
||||
|
||||
function verificationGates(variant) {
|
||||
@@ -480,6 +487,32 @@ function renderUpgradeReadiness(identity) {
|
||||
return rendered;
|
||||
}
|
||||
|
||||
function renderUpgradeRehearsal(identity) {
|
||||
const template = fs.readFileSync(
|
||||
assertCanonicalFile(
|
||||
UPGRADE_REHEARSAL_TEMPLATE,
|
||||
MAX_UPGRADE_REHEARSAL_BYTES,
|
||||
'upgrade rehearsal template',
|
||||
),
|
||||
'utf8',
|
||||
);
|
||||
const replacements = Object.freeze({
|
||||
'@@OPERATOR_IMAGE@@': identity.images.operator.reference,
|
||||
'@@OPERATOR_ID@@': identity.images.operator.id,
|
||||
'@@ARCHITECTURE@@': identity.architecture,
|
||||
'@@SOURCE_REVISION@@': identity.sourceRevision,
|
||||
'@@ARCHIVE@@': identity.archive.file,
|
||||
});
|
||||
let rendered = template;
|
||||
for (const [token, value] of Object.entries(replacements)) {
|
||||
rendered = rendered.replaceAll(token, value);
|
||||
}
|
||||
if (/@@[A-Z_]+@@/u.test(rendered)) {
|
||||
fail('upgrade rehearsal template contains an unresolved token');
|
||||
}
|
||||
return rendered;
|
||||
}
|
||||
|
||||
function fileRecord(bundleRoot, name) {
|
||||
const filePath = path.join(bundleRoot, name);
|
||||
const stat = fs.lstatSync(filePath);
|
||||
@@ -660,8 +693,13 @@ function createLocalAlphaTrialKit(options, adapters = {}) {
|
||||
renderUpgradeReadiness(manifestIdentity),
|
||||
0o700,
|
||||
);
|
||||
writeExclusive(
|
||||
path.join(normalized.outputRoot, FILES.upgradeRehearsal),
|
||||
renderUpgradeRehearsal(manifestIdentity),
|
||||
0o700,
|
||||
);
|
||||
const manifest = {
|
||||
schemaVersion: 7,
|
||||
schemaVersion: 8,
|
||||
schema: SCHEMA,
|
||||
maturity: 'alpha_candidate_not_public_release',
|
||||
product: 'local',
|
||||
@@ -680,6 +718,10 @@ function createLocalAlphaTrialKit(options, adapters = {}) {
|
||||
normalized.outputRoot,
|
||||
FILES.upgradeReadiness,
|
||||
),
|
||||
upgradeRehearsal: fileRecord(
|
||||
normalized.outputRoot,
|
||||
FILES.upgradeRehearsal,
|
||||
),
|
||||
readme: fileRecord(normalized.outputRoot, FILES.readme),
|
||||
verification: fileRecord(
|
||||
normalized.outputRoot,
|
||||
@@ -697,6 +739,7 @@ function createLocalAlphaTrialKit(options, adapters = {}) {
|
||||
FILES.verificationEvidence,
|
||||
FILES.quickstart,
|
||||
FILES.upgradeReadiness,
|
||||
FILES.upgradeRehearsal,
|
||||
FILES.readme,
|
||||
FILES.manifest,
|
||||
];
|
||||
@@ -767,10 +810,11 @@ function auditLocalAlphaTrialKit(options) {
|
||||
'sboms',
|
||||
'quickstart',
|
||||
'upgradeReadiness',
|
||||
'upgradeRehearsal',
|
||||
'readme',
|
||||
'verification',
|
||||
]) ||
|
||||
manifest.schemaVersion !== 7 ||
|
||||
manifest.schemaVersion !== 8 ||
|
||||
manifest.schema !== SCHEMA ||
|
||||
manifest.maturity !== 'alpha_candidate_not_public_release' ||
|
||||
manifest.product !== 'local' ||
|
||||
@@ -834,6 +878,23 @@ function auditLocalAlphaTrialKit(options) {
|
||||
if (actualUpgradeReadiness !== expectedUpgradeReadiness) {
|
||||
fail('upgrade readiness differs from the canonical inspection journey');
|
||||
}
|
||||
validateFileRecord(
|
||||
manifest.upgradeRehearsal,
|
||||
FILES.upgradeRehearsal,
|
||||
bundleRoot,
|
||||
);
|
||||
const expectedUpgradeRehearsal = renderUpgradeRehearsal(manifest);
|
||||
const actualUpgradeRehearsal = fs.readFileSync(
|
||||
assertCanonicalFile(
|
||||
path.join(bundleRoot, FILES.upgradeRehearsal),
|
||||
MAX_UPGRADE_REHEARSAL_BYTES,
|
||||
'upgrade rehearsal',
|
||||
),
|
||||
'utf8',
|
||||
);
|
||||
if (actualUpgradeRehearsal !== expectedUpgradeRehearsal) {
|
||||
fail('upgrade rehearsal differs from the canonical staging journey');
|
||||
}
|
||||
validateFileRecord(manifest.readme, FILES.readme, bundleRoot);
|
||||
validateOfflineSbom(
|
||||
readBoundedJson(
|
||||
@@ -871,6 +932,7 @@ function auditLocalAlphaTrialKit(options) {
|
||||
FILES.verificationEvidence,
|
||||
FILES.quickstart,
|
||||
FILES.upgradeReadiness,
|
||||
FILES.upgradeRehearsal,
|
||||
expectedArchive,
|
||||
].sort();
|
||||
const actualFiles = fs
|
||||
@@ -892,6 +954,7 @@ function auditLocalAlphaTrialKit(options) {
|
||||
FILES.verificationEvidence,
|
||||
FILES.quickstart,
|
||||
FILES.upgradeReadiness,
|
||||
FILES.upgradeRehearsal,
|
||||
FILES.readme,
|
||||
FILES.manifest,
|
||||
];
|
||||
@@ -905,7 +968,7 @@ function auditLocalAlphaTrialKit(options) {
|
||||
}
|
||||
return Object.freeze({
|
||||
schemaVersion: 1,
|
||||
schema: 'qinglong/alpha-local-trial-kit-audit@v3',
|
||||
schema: 'qinglong/alpha-local-trial-kit-audit@v4',
|
||||
sourceRevision: manifest.sourceRevision,
|
||||
version: manifest.version,
|
||||
architecture: manifest.architecture,
|
||||
@@ -915,6 +978,7 @@ function auditLocalAlphaTrialKit(options) {
|
||||
operatorImageId: manifest.images.operator.id,
|
||||
quickstartSha256: manifest.quickstart.sha256,
|
||||
upgradeReadinessSha256: manifest.upgradeReadiness.sha256,
|
||||
upgradeRehearsalSha256: manifest.upgradeRehearsal.sha256,
|
||||
verificationSha256: manifest.verification.sha256,
|
||||
workflowRunId: verificationEvidence.workflow.runId,
|
||||
workflowRunAttempt: verificationEvidence.workflow.runAttempt,
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -eu
|
||||
|
||||
OPERATOR_IMAGE='@@OPERATOR_IMAGE@@'
|
||||
OPERATOR_ID='@@OPERATOR_ID@@'
|
||||
ARCHITECTURE='@@ARCHITECTURE@@'
|
||||
SOURCE_REVISION='@@SOURCE_REVISION@@'
|
||||
ARCHIVE='@@ARCHIVE@@'
|
||||
|
||||
fail() {
|
||||
printf '%s\n' "QingLong Local Alpha upgrade rehearsal failed: $*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
usage() {
|
||||
printf '%s\n' \
|
||||
'usage: sh upgrade-rehearsal.sh edge|standalone /absolute/legacy-data-root /absolute/new/rehearsal-root <reviewed-sqlite-plan-digest> <reviewed-data-directory-plan-digest>' >&2
|
||||
exit 2
|
||||
}
|
||||
|
||||
safe_absolute_path() {
|
||||
case "$1" in
|
||||
/|*[!A-Za-z0-9_./-]*|*'/../'*|*'/./'*|*'/..'|*'/.'|*'//'*|*/)
|
||||
return 1
|
||||
;;
|
||||
/*) return 0 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
valid_digest() {
|
||||
[ "${#1}" -eq 64 ] || return 1
|
||||
case "$1" in
|
||||
*[!0-9a-f]*) return 1 ;;
|
||||
*) return 0 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
extract_digest() {
|
||||
result_file=$1
|
||||
field=$2
|
||||
digest=$(sed -n "s/^.*\"$field\":\"\([0-9a-f][0-9a-f]*\)\".*$/\1/p" "$result_file")
|
||||
valid_digest "$digest" || fail "$field is missing or invalid in $result_file"
|
||||
printf '%s' "$digest"
|
||||
}
|
||||
|
||||
[ "$#" -eq 5 ] || usage
|
||||
profile=$1
|
||||
legacy_root=$2
|
||||
rehearsal_root=$3
|
||||
sqlite_plan_digest=$4
|
||||
directory_plan_digest=$5
|
||||
|
||||
case "$profile" in
|
||||
edge|standalone) ;;
|
||||
*) usage ;;
|
||||
esac
|
||||
safe_absolute_path "$legacy_root" || fail 'legacy data root is not a safe canonical absolute path'
|
||||
safe_absolute_path "$rehearsal_root" || fail 'rehearsal root is not a safe canonical absolute path'
|
||||
valid_digest "$sqlite_plan_digest" || fail 'reviewed SQLite plan digest is invalid'
|
||||
valid_digest "$directory_plan_digest" || fail 'reviewed data-directory plan digest is invalid'
|
||||
[ "$legacy_root" != "$rehearsal_root" ] || fail 'legacy and rehearsal roots must be distinct'
|
||||
case "$rehearsal_root/" in
|
||||
"$legacy_root"/*) fail 'rehearsal root must not be inside the legacy data root' ;;
|
||||
esac
|
||||
case "$legacy_root/" in
|
||||
"$rehearsal_root"/*) fail 'legacy data root must not be inside the rehearsal root' ;;
|
||||
esac
|
||||
[ -d "$legacy_root" ] || fail 'legacy data root does not exist'
|
||||
[ -f "$legacy_root/db/database.sqlite" ] || fail 'legacy db/database.sqlite does not exist'
|
||||
legacy_real=$(CDPATH= cd -- "$legacy_root" && pwd -P)
|
||||
[ "$legacy_real" = "$legacy_root" ] || fail 'legacy data root is not canonical'
|
||||
[ ! -e "$rehearsal_root" ] || fail 'rehearsal root must not already exist'
|
||||
rehearsal_parent=${rehearsal_root%/*}
|
||||
[ -n "$rehearsal_parent" ] || rehearsal_parent=/
|
||||
[ -d "$rehearsal_parent" ] || fail 'rehearsal root parent does not exist'
|
||||
rehearsal_parent_real=$(CDPATH= cd -- "$rehearsal_parent" && pwd -P)
|
||||
[ "$rehearsal_parent_real/${rehearsal_root##*/}" = "$rehearsal_root" ] || fail 'rehearsal root parent is not canonical'
|
||||
|
||||
command -v docker >/dev/null 2>&1 || fail 'docker is required'
|
||||
command -v sha256sum >/dev/null 2>&1 || fail 'sha256sum is required'
|
||||
command -v grep >/dev/null 2>&1 || fail 'grep is required'
|
||||
command -v sed >/dev/null 2>&1 || fail 'sed is required'
|
||||
|
||||
script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd -P)
|
||||
(CDPATH= cd -- "$script_dir" && sha256sum --check SHA256SUMS)
|
||||
docker info >/dev/null 2>&1 || fail 'docker daemon is unavailable'
|
||||
docker load --input "$script_dir/$ARCHIVE" >/dev/null
|
||||
|
||||
operator_identity=$(docker image inspect --format '{{.Id}}|{{.Architecture}}|{{.Config.User}}|{{index .Config.Labels "org.opencontainers.image.revision"}}|{{index .Config.Labels "io.qinglong.lifecycle"}}|{{index .Config.Labels "io.qinglong.network"}}' "$OPERATOR_IMAGE")
|
||||
[ "$operator_identity" = "$OPERATOR_ID|$ARCHITECTURE|65532:65532|$SOURCE_REVISION|short-lived|none-by-default" ] || fail 'operator image identity is incompatible'
|
||||
|
||||
old_umask=$(umask)
|
||||
umask 077
|
||||
mkdir -m 0700 "$rehearsal_root"
|
||||
for directory in commands results sqlite data-directory; do
|
||||
mkdir -m 0700 "$rehearsal_root/$directory"
|
||||
done
|
||||
|
||||
cat >"$rehearsal_root/commands/sqlite-stage.json" <<EOF
|
||||
{"schemaVersion":1,"operation":"local-sqlite.adoption.stage","options":{"deploymentRoot":"/var/lib/qinglong3","profile":"$profile","sourcePath":"/var/lib/qinglong2/db/database.sqlite","targetPath":"/var/lib/qinglong3/sqlite/qinglong3.sqlite","recoveryPath":"/var/lib/qinglong3/sqlite/database.pre-ql3.sqlite","manifestPath":"/var/lib/qinglong3/sqlite/qinglong3-adoption.json","expectedPlanDigest":"$sqlite_plan_digest"}}
|
||||
EOF
|
||||
cat >"$rehearsal_root/commands/sqlite-verify.json" <<EOF
|
||||
{"schemaVersion":1,"operation":"local-sqlite.adoption.verify","options":{"deploymentRoot":"/var/lib/qinglong3","profile":"$profile","targetPath":"/var/lib/qinglong3/sqlite/qinglong3.sqlite","recoveryPath":"/var/lib/qinglong3/sqlite/database.pre-ql3.sqlite","manifestPath":"/var/lib/qinglong3/sqlite/qinglong3-adoption.json"}}
|
||||
EOF
|
||||
chmod 0600 "$rehearsal_root/commands/sqlite-stage.json" "$rehearsal_root/commands/sqlite-verify.json"
|
||||
|
||||
uid=$(id -u)
|
||||
gid=$(id -g)
|
||||
run_adoption() {
|
||||
command_file=$1
|
||||
result_file=$2
|
||||
docker run --rm --read-only --user "$uid:$gid" --network none \
|
||||
--cap-drop ALL --security-opt no-new-privileges \
|
||||
--memory 128m --memory-swap 128m --cpus 0.5 --pids-limit 32 \
|
||||
--tmpfs /tmp:rw,nosuid,nodev,noexec,size=8m \
|
||||
--mount "type=bind,src=$legacy_root,dst=/var/lib/qinglong2,readonly" \
|
||||
--mount "type=bind,src=$rehearsal_root,dst=/var/lib/qinglong3" \
|
||||
"$OPERATOR_IMAGE" adoption run \
|
||||
--command-file "/var/lib/qinglong3/commands/$command_file" \
|
||||
>"$rehearsal_root/results/$result_file"
|
||||
}
|
||||
|
||||
run_adoption sqlite-stage.json sqlite-stage.result.json
|
||||
grep -q '"status":"staged"' "$rehearsal_root/results/sqlite-stage.result.json" || fail 'SQLite stage did not report staged'
|
||||
run_adoption sqlite-verify.json sqlite-verify.result.json
|
||||
grep -q '"status":"verified"' "$rehearsal_root/results/sqlite-verify.result.json" || fail 'SQLite verify did not report verified'
|
||||
sqlite_manifest_digest=$(extract_digest "$rehearsal_root/results/sqlite-verify.result.json" manifestDigest)
|
||||
|
||||
cat >"$rehearsal_root/commands/sqlite-activation.json" <<EOF
|
||||
{"schemaVersion":1,"operation":"local-sqlite.activation.prepare","options":{"deploymentRoot":"/var/lib/qinglong3","profile":"$profile","sourcePath":"/var/lib/qinglong2/db/database.sqlite","targetPath":"/var/lib/qinglong3/sqlite/qinglong3.sqlite","recoveryPath":"/var/lib/qinglong3/sqlite/database.pre-ql3.sqlite","manifestPath":"/var/lib/qinglong3/sqlite/qinglong3-adoption.json","activationPath":"/var/lib/qinglong3/sqlite/qinglong3-activation.json","expectedManifestDigest":"$sqlite_manifest_digest"}}
|
||||
EOF
|
||||
chmod 0600 "$rehearsal_root/commands/sqlite-activation.json"
|
||||
run_adoption sqlite-activation.json sqlite-activation.result.json
|
||||
grep -q '"status":"prepared"' "$rehearsal_root/results/sqlite-activation.result.json" || fail 'SQLite activation did not report prepared'
|
||||
activation_digest=$(extract_digest "$rehearsal_root/results/sqlite-activation.result.json" activationDigest)
|
||||
|
||||
cat >"$rehearsal_root/commands/data-directory-stage.json" <<EOF
|
||||
{"schemaVersion":1,"operation":"local-data-directory.adoption.stage","options":{"deploymentRoot":"/var/lib/qinglong3","dataRoot":"/var/lib/qinglong2","stagingRoot":"/var/lib/qinglong3/data-directory/staged","profile":"$profile","expectedPlanDigest":"$directory_plan_digest","sqlite":{"sourcePath":"/var/lib/qinglong2/db/database.sqlite","targetPath":"/var/lib/qinglong3/sqlite/qinglong3.sqlite","recoveryPath":"/var/lib/qinglong3/sqlite/database.pre-ql3.sqlite","manifestPath":"/var/lib/qinglong3/sqlite/qinglong3-adoption.json","activationPath":"/var/lib/qinglong3/sqlite/qinglong3-activation.json","expectedActivationDigest":"$activation_digest"}}}
|
||||
EOF
|
||||
chmod 0600 "$rehearsal_root/commands/data-directory-stage.json"
|
||||
run_adoption data-directory-stage.json data-directory-stage.result.json
|
||||
grep -q '"status":"staged"' "$rehearsal_root/results/data-directory-stage.result.json" || fail 'data-directory stage did not report staged'
|
||||
directory_manifest_digest=$(extract_digest "$rehearsal_root/results/data-directory-stage.result.json" manifestDigest)
|
||||
|
||||
cat >"$rehearsal_root/commands/data-directory-verify.json" <<EOF
|
||||
{"schemaVersion":1,"operation":"local-data-directory.adoption.verify","options":{"deploymentRoot":"/var/lib/qinglong3","dataRoot":"/var/lib/qinglong2","stagingRoot":"/var/lib/qinglong3/data-directory/staged","profile":"$profile","expectedManifestDigest":"$directory_manifest_digest","sqlite":{"sourcePath":"/var/lib/qinglong2/db/database.sqlite","targetPath":"/var/lib/qinglong3/sqlite/qinglong3.sqlite","recoveryPath":"/var/lib/qinglong3/sqlite/database.pre-ql3.sqlite","manifestPath":"/var/lib/qinglong3/sqlite/qinglong3-adoption.json","activationPath":"/var/lib/qinglong3/sqlite/qinglong3-activation.json","expectedActivationDigest":"$activation_digest"}}}
|
||||
EOF
|
||||
chmod 0600 "$rehearsal_root/commands/data-directory-verify.json"
|
||||
run_adoption data-directory-verify.json data-directory-verify.result.json
|
||||
grep -q '"status":"verified"' "$rehearsal_root/results/data-directory-verify.result.json" || fail 'data-directory verify did not report verified'
|
||||
|
||||
cat >"$rehearsal_root/stage-summary.json" <<EOF
|
||||
{"schemaVersion":1,"schema":"qinglong/local-alpha-upgrade-stage-summary@v1","status":"verified","profile":"$profile","sourceRevision":"$SOURCE_REVISION","architecture":"$ARCHITECTURE","reviewedPlans":{"sqlite":"$sqlite_plan_digest","dataDirectory":"$directory_plan_digest"},"sqlite":{"manifestDigest":"$sqlite_manifest_digest","activationDigest":"$activation_digest"},"dataDirectory":{"manifestDigest":"$directory_manifest_digest"},"legacySource":"read_only","cutover":"not_authorized"}
|
||||
EOF
|
||||
chmod 0600 "$rehearsal_root/stage-summary.json"
|
||||
umask "$old_umask"
|
||||
|
||||
printf '%s\n' \
|
||||
"QingLong 2.x side-by-side upgrade stage completed ($profile, $ARCHITECTURE)." \
|
||||
"Stage summary: $rehearsal_root/stage-summary.json" \
|
||||
"SQLite activation evidence: $rehearsal_root/sqlite/qinglong3-activation.json" \
|
||||
"Data-directory manifest: $rehearsal_root/data-directory/staged/manifest.json" \
|
||||
'The legacy root remained read-only. No transform/apply, target start, cutover or Legacy rollback was authorized.' \
|
||||
'Preserve the complete rehearsal root. Do not edit or reuse it as a production data root.'
|
||||
Reference in New Issue
Block a user