2
0
mirror of https://github.com/xcat2/xcat-dep.git synced 2026-09-12 12:36:23 +00:00
Commit Graph

10 Commits

Author SHA1 Message Date
Daniel Hilst ca051e46c4 fix(xcat-dep): unsafe repo publication, non-fail-hard builds, repo-gate false passes
Addresses the three blocking issues from the PR #63 review.

1. THE TWO ARCH JOBS COULD BOTH REWRITE THE PUBLISHED REPOSITORY.

The amd64 and ppc64el runs execute concurrently on their two hosts against the
same --apt-dir. The run lock does not protect the repository -- it is per-arch by
design, precisely so the two arches CAN build in parallel -- so both were free to
wipe and repopulate the same pool/, dists/, Release, InRelease and tarball at the
same time and interleave into a corrupt but green repository.

Split by role, so architecture jobs produce staging artifacts and one locked
finalization step publishes atomically:

  * A run that builds no longer publishes. It fills staging/<codename>/<arch>/
    and stops, saying so and printing the finalize command. Publishing happens
    only with --publish, or implicitly on a run that builds nothing
    (--skip-build), which IS the finalization step; --skip-createrepo still
    forces it off.
  * publish_repo() takes ONE GLOBAL publish lock (.sbuild-all.publish.lock), not
    the per-arch build lock, so even a cron run racing a manual one on the same
    host serializes instead of interleaving.
  * It publishes atomically. assemble_into() builds and signs the complete tree
    in a side directory (<apt-dir>.publish-<run-id>.<pid>), the gate runs against
    THAT tree, and swap_into_place() then renames it onto --apt-dir. A reader --
    the deploy rsync, an apt client on a served tree -- sees either the previous
    complete repo or the new complete repo, never a half-wiped pool or an index
    that disagrees with its Release. A failure anywhere before the swap leaves
    the published tree untouched and removes the side tree. The side tree is
    seeded from the current published one, so codenames outside --dists survive.
  * The tarball moved inside publish, under the same lock: previously every
    per-arch build tarred the shared apt tree while the other arch rewrote it.

2. build_deb_in_chroot() WAS NOT FAIL-HARD, AND THE ENVIRONMENT WAS NOT CLEAN.

The common build-tooling install ended in `|| true`; a failed Build-Depends
installation only warned; and the Build-Depends came from a sed pipeline over
debian/control that stripped version constraints `(>= 12)`, could not express
alternatives `a | b`, and mangled arch qualifiers. Worse, the per-codename
chroots are long-lived and shared by all seven packages, and nothing asserted
that a schroot session is throwaway -- so on a chroot without a union mount,
package N's build-dependencies stayed installed for package N+1 and a package
whose debian/control forgets a Build-Depends would build green on a sibling's
leftovers.

Making dependency setup fatal is only half the fix; it means nothing if a stale
environment can satisfy an undeclared dependency. Both halves are addressed:

  * The in-chroot program is now generated by the pure, unit-tested
    BuildUtils::chroot_build_script() and runs under `set -euo pipefail`.
    apt-get update and the common tooling go through an apt_retry helper that
    retries a transient mirror hiccup and then FAILS the build. Build-Depends are
    installed with mk-build-deps (devscripts + equivs), which hands
    debian/control's relationships to apt verbatim, and a failure is fatal. The
    mk-build-deps dummy package is excluded from deb collection alongside dbgsym.
  * ensure_disposable_chroot() repairs a chroot.d entry that lacks
    union-type=overlay, and build_deb_in_chroot() re-reads `schroot --config` and
    hard-fails on a chroot that is still not disposable, naming the fix.
    BuildUtils::chroot_is_disposable() is the pure predicate.

3. THE REPOSITORY GATE STILL HAD FALSE PASSES.

  * Standalone --verify-repo skipped signature verification whenever --gpg-home
    was not passed, so the common `--verify-repo <dir>` invocation silently
    checked completeness only. Signatures are now verified BY DEFAULT there;
    --no-verify-signature is the explicit opt-out. (The automatic pre-swap gate
    still requires a signature iff --gpg-sign was used, so an intentionally
    unsigned tree does not false-fail -- and publishing unsigned now warns.)
  * The expected architecture set was inferred from what happened to be present,
    so an entirely missing secondary architecture read as "this run did not build
    it" and passed. It is now always a CLAIM: --expect-arch (repeatable, accepts
    a space/comma list) if given, else the staged arch set when publishing, else
    each codename's own Release "Architectures:" line when verifying standalone
    (BuildUtils::parse_release_architectures). The pure
    BuildUtils::verify_repo_arches then reports an expected arch with no NATIVE
    package as MISSING-ARCH and natives for an unexpected arch as
    UNEXPECTED-ARCH (a stale architecture). Native detection matters because the
    Architecture:all packages ride into every arch's index, so a non-empty
    binary-<arch>/Packages is not evidence that arch was built.
  * Release now advertises exactly the expected arch set and only those
    binary-<arch> indices are written, so an amd64-only build no longer
    advertises a ppc64el it cannot serve.
  * An expected cell with no manifest section used to be skipped with a note --
    a third free pass. It is now a hard NO-MANIFEST error.

TESTS

t/sbuild-all.t gains verify_repo_arches (both directions), the
parse_release_architectures parser, chroot_is_disposable, regression guards on
the generated in-chroot script (no `|| true`, no warn-only build-deps, no sed
extraction, mk-build-deps present, every apt-get behind the fatal helper), and
the build_deb_in_chroot disposability guard driven through a stub schroot.

t/verify-repo.t is new: it drives the real `sbuild-all.pl --verify-repo` against
fixture apt trees and asserts each former false pass now fails -- missing
secondary architecture (with and without --expect-arch), arch:all-only index,
unsigned repo with no --gpg-home, missing manifest section -- and that an honest
single-arch repo still passes.

Validated on the build hosts: a full focal+noble amd64 build of all seven
packages through the new fatal dependency path (mk-build-deps installs the
declared Build-Depends inside the oldest, focal, chroot), and the publish path
end to end -- a staging-only run leaves the repo untouched; publish assembles
aside, gates, and swaps; an unrelated codename survives the swap; stale debs are
dropped; a failed gate leaves the published repo byte-identical and cleans up the
side tree.

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
2026-08-21 20:25:51 -03:00
Daniel Hilst 0266df91f3 docs(build): name conserver-xcat in BUILD.md; test control_binary_arch multi-arch return
Landing-review nits: the Packages note said 'conserver' where the shipped package
is 'conserver-xcat'; and add a test asserting control_binary_arch returns the full
multi-token Architecture value (documents the commit's intent; behaviorally inert).

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
2026-08-13 12:25:56 -03:00
Daniel Hilst d0875ee1ff fix(xcat-dep): validate_manifest must apply the SAME arch:all skip as the builder (ppc build was aborting)
The previous commit added the arch:all boot tools (syslinux-xcat/grub2-xcat/
elilo-xcat/xnba-undi) to the ppc64el manifest and taught build_one_codename to
skip BUILDING them on non-amd64 -- but validate_manifest, which runs per-arch on
every non-dry-run invocation and is NOT gated by --skip-createrepo, still
demanded them. On the ppc64el build stage (--arch ppc64el, no --skip-build) the
builder skipped the four (correct -- their source is x86-only) so they were never
staged, then validate_manifest reported them MISSING and aborted the whole ppc
build before assembly: every BUILD_PPC=true run would have failed.

Fix the drift structurally: extract the skip rule into one pure, tested decider
BuildUtils::skip_arch_all_on() behind a shared sbuild-all.pl helper
pkg_skip_on_arch(), consulted by BOTH build_one_codename and validate_manifest,
so a package the build skips is never demanded by the per-arch validation. The
arch:all debs' presence on ppc is still verified later against the published
index by verify_assembled_repo. Also make control_binary_arch return the full
Architecture value (not just the first token of a multi-arch list) and add a
regression test for the shared skip rule.

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
2026-08-13 11:05:33 -03:00
Daniel Hilst 8f3dfd9e84 fix(xcat-dep): require the arch:all boot tools (syslinux/grub2/elilo/xnba) on ppc64el
The ppc64el manifest sections listed only the natively-built deps
(ipmitool-xcat, conserver-xcat, goconserver) + genesis, omitting the four
noarch boot components syslinux-xcat, grub2-xcat, elilo-xcat and xnba-undi. A
ppc management node needs these for netboot -- the EL manifest and the
historical 2.16 ppc dep repo both ship them (grub2-xcat + syslinux-xcat
especially; yaboot-xcat was dropped in 2.18 as obsolete). They ARE published
to the ppc index today (Architecture:all rides into every binary-<arch>
index), but the completeness gate never asserted their presence for ppc.

List them as required-present on every ppc64el target so the gate verifies the
ppc repo carries them. They remain SINGLE-PRODUCER: their source is x86-only
(syslinux compiles with nasm/gcc-multilib), so build_one_codename now skips an
Architecture:all package on any non-amd64 arch -- detected via a new pure,
unit-tested control_binary_arch() helper -- so listing them for ppc64el drives
verification, not a second (failing) build.

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
2026-08-13 10:43:56 -03:00
Daniel Hilst 7944387b4b fix(xcat-dep): verify gate must detect a NATIVE-arch build, not a non-empty index
The prior commit scoped the apt verify gate to '--arch floor UNION present
arches', but detected presence with a non-empty binary-<arch>/Packages. That
is wrong: every binary-<arch> index carries the Architecture:all debs
(grub2-xcat, genesis), so a BUILD_PPC=false run has a non-empty binary-ppc64el
index built purely from arch:all debs -- and the gate would still demand the
native ppc compiled deps (ipmitool-xcat, conserver-xcat, goconserver) it never
built, the exact false-fail the change was meant to remove.

Detect a genuine per-arch build via a new pure helper index_has_native_arch(),
which is true only when the index has a stanza with Architecture == that arch
(not merely Architecture:all). Unit-tested happy + the arch:all-only sad case.

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
2026-08-13 10:08:33 -03:00
Daniel Hilst 1ac98b6fb6 fix(xcat-dep): repo gate — duplicate version is a hard error; signature strictly matches CLI key
Align the apt gate with the EL gate so both AGREE on success/failure:
- Duplicate: parse_packages_index now DIES loudly on a package with two DISTINCT versions
  (stale .deb not cleaned), mirroring EL's rpm_version -- no more silent keep-highest.
  Removed the dpkg keep-highest oracle; added a happy/sad test.
- Signature: sig_observed_key returns the signer fingerprint or undef (no presence-only
  fallback); the gate hard-fails (SIGKEY) if --gpg-key-id doesn't resolve to a fingerprint,
  so it always confirms the repo was signed by EXACTLY the CLI key. Signature is required
  only when --gpg-sign was used.
- Align the MISSING message with EL (undef pin -> '*'). Document the gate + idiosyncrasies
  in BUILD.md.
prove t/sbuild-all.t: 98/98.

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
2026-08-12 17:11:42 -03:00
Daniel Hilst 6f7497cceb fix(xcat-dep): repo gate must verify the NATIVE per-arch genesis; harden signature check
Review follow-up on the apt-repo gate:
- FALSE-PASS (concern #7610): genesis name-resolution picked xcat-genesis-base-amd64
  alphabetically for BOTH cells (both genesis debs are Architecture:all and appear in every
  arch index), so the ppc64el cell never verified its NATIVE xcat-genesis-base-ppc64el -- a
  dropped ppc genesis passed. Extract a PURE, unit-tested resolve_present_names that resolves
  the arch-suffixed genesis to THIS cell's arch only (never a different arch), with a test that
  reproduces the masked-genesis case.
- signature: reject EXPKEYSIG/REVKEYSIG/EXPSIG (expired/revoked keys emit VALIDSIG too); drop
  the short-GOODSIG-keyid fallback (could never equal the 40-hex expected fpr -> spurious WRONGKEY).
- FALSE-FAIL: the post-assembly auto-run now requires a signature only when --gpg-sign was
  actually used (an unsigned-by-choice repo no longer dies UNSIGNED); standalone --verify-repo
  keeps checking whenever a gpg key/home is configured.
- cosmetic: de-duplicate the MISSING-INDEX message.
prove t/sbuild-all.t: pure gate tests incl. the new genesis-resolution cases.

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
2026-08-12 16:43:19 -03:00
Daniel Hilst 68f17873bb feat(xcat-dep): manifest-driven apt repo completeness + signature gate, auto-run after assemble
Adds a real gate on the ASSEMBLED apt repo, per codename x arch, using
debs-manifest.conf as the single source of truth, layered pure/testable:

- BuildUtils: verify_repo_packages(\%expected,\%present) (MISSING/VERSION),
  verify_repo_signature(\%expected,\%observed) (UNSIGNED/WRONGKEY), and
  parse_packages_index($text) -- all PURE and unit-tested (happy+sad, no dpkg-deb).
- sbuild-all.pl does the IO via one sub verify_assembled_repo: parses each published
  binary-<arch>/Packages (resolving arch-suffixed names like xcat-genesis-base-<arch>,
  reducing to upstream via deb_upstream_version to compare against the manifest pin),
  runs gpg --verify on each dists/<cn>/InRelease and extracts the signer fingerprint,
  then delegates to the two pure deciders and dies listing every [<cn>/<arch>] problem.
- Runs AUTOMATICALLY at the end of assemble_apt (once Packages + signed Release exist);
  suppressible with --no-verify-repo; skipped under --dry-run. Also a standalone,
  lock-free, build-free '--verify-repo=<apt_dir>' mode using the script's --manifest/
  --dists/--gpg-key-id/--gpg-home. Replaces the coarse pool-global hard-coded check.

prove t/sbuild-all.t: 90/90 (was 71). Smoke-tested: complete tree passes; dropped pkg
-> MISSING; wrong version -> VERSION; missing index -> MISSING-INDEX; all die nonzero.

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
2026-08-12 15:30:53 -03:00
Daniel Hilst c808e06da3 fix(xcat-dep): address code review — run lock, loud tree wipes, wire tested genesis copier, dedupe pool, honest Release arches
Review follow-up for the Ubuntu sbuild matrix:

- Add a fail-fast exclusive flock over the whole run (<output-root>/.sbuild-all.lock,
  file-scoped handle) so two overlapping runs can't corrupt the shared staging/apt
  tree -- this is the root of the observed 'remove_tree .../staging/<cn>/<arch>:
  Directory not empty' (an NFS silly-rename from a concurrent run).
- wipe_tree(): remove_tree that captures {error} and dies loud, so an ENOTEMPTY no
  longer carps-and-continues leaving stale debs; used for all staging/pool/dists wipes.
- Wire the tested, hash-based cross_copy_genesis_deb into build_genesis (was a naive
  glob+copy, so the unit-tested stale-dropping copier was dead code); remove the
  genuinely-unused deb_snap_version/rewrite_changelog_top helpers + their subtests
  (compiled deps intentionally ship their tracked changelog version).
- Dedupe assemble_apt on binary Package+Architecture (keep highest via
  dpkg --compare-versions) so a double-produced genesis can't land two versions in
  the pool, independent of the --skip-genesis contract.
- Derive Release Architectures from the arches actually staged (non-empty
  binary-<arch>/Packages), not a hard-coded 'amd64 ppc64el'.
- goconserver: guard 'go mod init' when a go.mod exists (+ TODO to commit go.sum for
  the pinned SHA). Accept-and-ignore the unused per-package --log-dir/--build-number/
  --skip-install flags (documented). Remove orphaned make_deb.sh dispatchers
  (build-debs-all, build.sh, ipmitool/build.sh) + update the READMEs.

perl -c clean; prove t/sbuild-all.t: 71/71.

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
2026-08-12 11:49:54 -03:00
Daniel Hilst b4627bc227 feat(xcat-dep): rework the Ubuntu dep build as testable Perl (sbuild-all.pl)
The Ubuntu/Debian dependency build shipped as three bash scripts
(build-dep-debs.sh, build-apt-repo.sh, mk-dep-chroots.sh) whose review
(PR #63) surfaced correctness problems: partial/stale output could be
published, the rpm->deb genesis conversion dropped the maintained package
semantics (Depends/Breaks/Replaces + maintainer scripts), the arch matrix
was invalid (x86-only syslinux/elilo/xnba treated as ppc64el packages, and
Architecture:all packages with no single producer), several required
failures exited zero, and the build/repo scripts disagreed on their staging
path and codename set (focal missing from the assembler).

Rewrite it as proper, unit-tested Perl mirroring the EL side
(mockbuild-all.pl / MockBuildUtils.pm / <dep>/mockbuild.pl / t/*.t /
packages-manifest.conf), sharing one CLI vocabulary:

- BuildUtils.pm: shared, testable helpers + the canonical CLI spec, plus the
  Debian-specific helpers (out-of-tree changelog stamping, genesis control
  preservation, deb inspection, cross-arch genesis provisioning).
- sbuild-all.pl: the orchestrator, absorbing all three shell scripts. Builds
  + validates into a fresh per-arch staging tree and only (re)assembles the
  published apt repo from validated staging -- so partial/failed output never
  ships and stale debs never accumulate. Auto-initializes the per-codename
  sbuild chroots on first run. Fails the whole run non-zero on any missing
  chroot/package/artifact or version-pin mismatch.
- <dep>/sbuild.pl x7: per-package builders that drive each package's
  MAINTAINED debian/ in the matching chroot (never re-implemented), so the
  converted/built packages keep their control metadata and maintainer scripts.
- debs-manifest.conf: per-[<codename>-<arch>] required set + version pins,
  encoding the per-arch package sets (x86 boot components built once on amd64
  as the single producer; ppc64el builds only the arch-specific compiled deps).
- t/sbuild-all.t: fixture tests for every pure helper.
- goconserver/make_deb.sh: pin the upstream SHA instead of cloning a moving
  branch, so every matrix cell builds the same source (reproducible).

Codename set unified across build, assembly, chroots and docs (focal IS
supported). BUILD.md documents the new flow.

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
2026-08-10 18:55:29 -03:00