Skip to content

Commit 4e9be22

Browse files
committed
Snap: Caddy has no armhf archive, and a store hiccup is not a bad snap.
Two failures in the v10.78 snap jobs, neither of them a problem with WeKan. ARMHF ASKED FOR A CADDY ARCHITECTURE THAT DOES NOT EXIST. With the libcurl4t64 fix in, the armhf build got past the stage-packages and died further along: :: Downloading Caddy 2.11.4 (linux/armhf) from GitHub releases... :: + curl -fsSL -o /tmp/caddy.tar.gz https://github.com/caddyserver/ caddy/releases/download/v2.11.4/caddy_2.11.4_linux_armhf.tar.gz :: curl: (22) The requested URL returned error: 404 'override-build' in part 'caddy' failed with code 1. Caddy is built by Go and its release assets carry GO's architecture names, not Debian's. There is no linux_armhf archive and there never has been - the 32-bit ARM ones are armv5, armv6 and armv7 - and the case statement had no armhf branch, so it fell through to a default that passed the Debian name straight into the URL. The "fall back to the pinned version" path then retried the SAME wrong name, so the failure read as "Caddy stopped publishing this architecture" when it was this file's mapping all along. armhf maps to armv7, not armv6: Debian armhf's baseline is ARMv7-A with VFPv3-D16 hard-float, and Go's armv7 build is GOARM=7, which is exactly that. This is NOT the armhf/armv7 distinction that matters for Node.js in wekan/node-patches - that one is about NEON, and GOARM=7 does not use NEON. Checked against the actual release: all six mapped URLs answer, and linux_armhf 404s. The default branch now names the problem and stops, instead of guessing a name and letting a 404 blame the wrong project. THREE GOOD SNAPS WERE LOST TO A STORE HICCUP. riscv64, ppc64el and s390x each built on Launchpad, downloaded, and were then refused: Status: error while processing Issues while processing snap: - binary_sha3_384: Error checking upload uniqueness. That is the store failing its OWN duplicate check on a digest it had just computed - a server-side error, not a bad snap - and the message the job printed was about 'is not a valid file', credentials and ACLs, none of which applied. The upload is retried three times with a backoff now, and the give-up message says the snap is fine and nothing here needs changing. The retry stays narrow on purpose: a rejected file, unparseable credentials or a missing ACL will be rejected identically three times, and retrying those only buries the one message that says what to fix. The classifier is tested against all four, not just the one that happened. New checks in tests/snapStagePackages.test.cjs (every snap architecture maps to a Caddy asset that exists, every build-for has a mapping, and - negatively - an unmapped one fails loudly instead of 404ing) and in tests/provenanceTable.test.cjs (the processing failure is retried, the four unretryable messages are not, and the give-up message does not send somebody debugging a build that succeeded). Verified the Caddy guard fails when the armhf branch is removed. Thanks to xet7 !
1 parent 74dfacd commit 4e9be22

4 files changed

Lines changed: 179 additions & 5 deletions

File tree

.github/workflows/release-all.yml

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3670,11 +3670,42 @@ jobs:
36703670
fi
36713671
# base: core24 allows publishing to stable and candidate (core26 does not).
36723672
for f in "${snaps[@]}"; do
3673-
echo "Uploading $f to the Snap Store (stable,candidate,beta,edge)..."
3674-
if ! snapcraft upload --release=stable,candidate,beta,edge "$f"; then
3675-
echo "::error::The Snap Store refused the upload of $f. The snapcraft line above says which: 'is not a valid file' means the build produced nothing usable; 'Credentials could not be parsed' means SNAP_AUTH is not a readable exported login; 401/403 means it lacks the ACL for this snap name. The last two are fixed by re-exporting for all three names: snapcraft export-login --snaps wekan,wekan-ondra,wekan-gantt-gpl --acls package_access,package_push,package_release,package_update snap-auth.txt"
3673+
# RETRIED, because the upload can fail for a reason that has nothing
3674+
# to do with the snap. The store accepts the file, scans it, and can
3675+
# then answer:
3676+
# Status: error while processing
3677+
# Issues while processing snap:
3678+
# - binary_sha3_384: Error checking upload uniqueness.
3679+
# which is the store failing its OWN duplicate check on the digest
3680+
# it just computed - a server-side hiccup, not a bad snap. It hit
3681+
# riscv64, ppc64el and s390x in the same v10.78 run, each after a
3682+
# Launchpad build that had succeeded, so three good snaps were lost
3683+
# to it at once. Re-uploading the identical file is what fixes it.
3684+
up_attempts=3
3685+
uploaded=0
3686+
for u in $(seq 1 "$up_attempts"); do
3687+
echo "Uploading $f to the Snap Store (stable,candidate,beta,edge), attempt $u/$up_attempts..."
3688+
if snapcraft upload --release=stable,candidate,beta,edge "$f" 2>&1 | tee /tmp/snap-upload.log; then
3689+
uploaded=1
3690+
break
3691+
fi
3692+
# Only the store's own processing errors are worth retrying. A
3693+
# rejected file, or credentials the store will not accept, will be
3694+
# rejected exactly the same way three times - and retrying those
3695+
# only buries the message that says what to fix.
3696+
if grep -qiE 'Error checking upload uniqueness|error while processing|502 Bad Gateway|503 Service|504 Gateway|Timeout|Connection (reset|aborted)' /tmp/snap-upload.log; then
3697+
if [ "$u" -lt "$up_attempts" ]; then
3698+
echo "::warning::The Snap Store failed while PROCESSING $f (not a problem with the snap). Retrying in $((u * 30))s."
3699+
sleep $((u * 30))
3700+
continue
3701+
fi
3702+
echo "::error::The Snap Store failed while processing $f on every one of $up_attempts attempts. This is the store's own upload-uniqueness/processing check failing on a snap it accepted - the file itself built and downloaded fine, so re-running this job later usually publishes it unchanged. Nothing in this repository needs to change." >&2
3703+
exit 1
3704+
fi
3705+
echo "::error::The Snap Store refused the upload of $f. The snapcraft line above says which: 'is not a valid file' means the build produced nothing usable; 'Credentials could not be parsed' means SNAP_AUTH is not a readable exported login; 401/403 means it lacks the ACL for this snap name. The last two are fixed by re-exporting for all three names: snapcraft export-login --snaps wekan,wekan-ondra,wekan-gantt-gpl --acls package_access,package_push,package_release,package_update snap-auth.txt" >&2
36763706
exit 1
3677-
fi
3707+
done
3708+
[ "$uploaded" -eq 1 ] || exit 1
36783709
done
36793710
36803711
- name: Attach the ${{ matrix.arch }} snap to the GitHub Release

snapcraft.yaml

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -502,7 +502,32 @@ parts:
502502
s390x) CADDY_ARCH=s390x ;;
503503
ppc64el) CADDY_ARCH=ppc64le ;; # Caddy uses ppc64le, not Debian's ppc64el
504504
riscv64) CADDY_ARCH=riscv64 ;;
505-
*) CADDY_ARCH="${CRAFT_ARCH_BUILD_FOR}" ;;
505+
# Caddy is built by Go and its release assets carry GO's names,
506+
# not Debian's. There is no linux_armhf archive and there never
507+
# has been; the 32-bit ARM ones are armv5, armv6 and armv7. With
508+
# no branch here armhf fell through to the default below, asked
509+
# for caddy_<v>_linux_armhf.tar.gz, and got
510+
# curl: (22) The requested URL returned error: 404
511+
# which failed the whole armhf snap in v10.78.
512+
#
513+
# armv7, not armv6: Debian armhf's baseline is ARMv7-A with
514+
# VFPv3-D16 hard-float, and Go's armv7 build is GOARM=7, which
515+
# is exactly that. Note this is NOT the armhf/armv7 distinction
516+
# that matters for Node.js in wekan/node-patches - that one is
517+
# about NEON, and Go's GOARM=7 does not use NEON.
518+
armhf) CADDY_ARCH=armv7 ;;
519+
*)
520+
# An unmapped architecture used to become a URL that 404s,
521+
# and the "fall back to the pinned version" path below then
522+
# retried the same wrong name and failed too - so the error
523+
# blamed the Caddy RELEASE for something this case statement
524+
# got wrong. Say which it is.
525+
echo "caddy: no Caddy architecture is mapped for '${CRAFT_ARCH_BUILD_FOR}'." >&2
526+
echo " Caddy publishes linux_amd64, linux_arm64, linux_armv5, linux_armv6," >&2
527+
echo " linux_armv7, linux_ppc64le, linux_riscv64 and linux_s390x - Go's names," >&2
528+
echo " not Debian's. Add a branch above rather than guessing here." >&2
529+
exit 1
530+
;;
506531
esac
507532
echo "Downloading Caddy ${CADDY_VERSION} (linux/${CADDY_ARCH}) from GitHub releases..."
508533
# The newest release does not have to publish every architecture -

tests/provenanceTable.test.cjs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,4 +374,56 @@ test('a release-note lookup can never fail the build it annotates', () => {
374374
});
375375
});
376376

377+
// ───────────────────── the Snap Store can fail for reasons that are not ours
378+
379+
test('a store PROCESSING failure is retried; a rejected snap is not', () => {
380+
// The store accepts the file, scans it, and can then answer:
381+
// Status: error while processing
382+
// Issues while processing snap:
383+
// - binary_sha3_384: Error checking upload uniqueness.
384+
// which is the store failing its OWN duplicate check on a digest it just
385+
// computed. It hit riscv64, ppc64el and s390x in one v10.78 run, each after a
386+
// Launchpad build that had SUCCEEDED, so three good snaps were lost at once
387+
// and the printed advice was about credentials and ACLs - none of which
388+
// applied.
389+
//
390+
// Retrying must stay narrow. A rejected file, bad credentials or a missing
391+
// ACL will be rejected identically three times, and retrying those only
392+
// buries the one message that says what to fix.
393+
const src = read(WORKFLOW);
394+
const step = src.slice(src.indexOf('Uploading $f to the Snap Store'));
395+
const upTo = step.slice(0, step.indexOf('- name:'));
396+
397+
assert.ok(/Error checking upload uniqueness/.test(upTo),
398+
'the store-side processing failure is not recognised');
399+
assert.ok(/up_attempts/.test(upTo) && /sleep/.test(upTo),
400+
'there is no retry with a backoff');
401+
402+
// The classifier itself, applied to the messages that must NOT be retried.
403+
const m = upTo.match(/grep -qiE '([^']+)'/);
404+
assert.ok(m, 'the retryable-error pattern is gone');
405+
const re = new RegExp(m[1], 'i');
406+
assert.ok(re.test('- binary_sha3_384: Error checking upload uniqueness.'),
407+
'the real v10.78 failure must be retried');
408+
assert.ok(re.test('Status: error while processing'));
409+
[
410+
'wekan_10.78_s390x.snap is not a valid file',
411+
'Credentials could not be parsed',
412+
'Error 403: forbidden - no permission for snap name',
413+
'Error 401: unauthorized',
414+
].forEach(line => assert.ok(!re.test(line),
415+
`"${line}" must NOT be retried - three identical rejections hide the fix`));
416+
});
417+
418+
test('and it says the snap is fine when the store is not', () => {
419+
// The failure that remains after three attempts is still not a problem with
420+
// the snap, and the message has to say so - otherwise the next person goes
421+
// looking through a build that succeeded.
422+
const src = read(WORKFLOW);
423+
const step = src.slice(src.indexOf('Uploading $f to the Snap Store'));
424+
const upTo = step.slice(0, step.indexOf('- name:'));
425+
assert.ok(/Nothing in this repository needs to change/.test(upTo),
426+
'the give-up message must not send somebody debugging a good build');
427+
});
428+
377429
console.log(`\n${passed} tests passed`);

tests/snapStagePackages.test.cjs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,18 @@ const T64_RENAMED = {
7575
'libboost-filesystem1.83.0': null, // NOT renamed - real on armhf
7676
};
7777

78+
// The Caddy architecture `case` block, and not the WEKAN_ARCH one above it:
79+
// snapcraft.yaml has more than one `case "${CRAFT_ARCH_BUILD_FOR}" in`, and
80+
// taking the first match tested the wrong block while looking like it worked.
81+
function caddyCase() {
82+
const at = yaml.indexOf('CADDY_ARCH=');
83+
assert.ok(at > 0, 'the Caddy architecture mapping is gone');
84+
const start = yaml.lastIndexOf('case "${CRAFT_ARCH_BUILD_FOR}" in', at);
85+
assert.ok(start > 0, 'the Caddy mapping is no longer a case statement');
86+
const end = yaml.indexOf('esac', start);
87+
return yaml.slice(start, end);
88+
}
89+
7890
test('snapcraft.yaml declares stage-packages at all', () => {
7991
const pkgs = stagePackages();
8092
assert.ok(pkgs.length >= 10,
@@ -132,4 +144,58 @@ test('the reason is written down where the next person will read it', () => {
132144
'and the error it produces, so a search for it lands here');
133145
});
134146

147+
// ─────────────────────────────────── Caddy is built by Go, and named like it
148+
149+
test('THE BUG: every snap architecture maps to a Caddy asset that exists', () => {
150+
// Caddy's release assets carry GO's architecture names, not Debian's. There
151+
// is no linux_armhf archive and there never has been - the 32-bit ARM ones
152+
// are armv5, armv6 and armv7 - so armhf fell through to the default branch,
153+
// asked for caddy_<v>_linux_armhf.tar.gz and got
154+
// curl: (22) The requested URL returned error: 404
155+
// which failed the whole armhf snap. The "fall back to the pinned version"
156+
// path then retried the SAME wrong name, so the error blamed the Caddy
157+
// release for something the case statement got wrong.
158+
const caseBlock = caddyCase();
159+
160+
const EXPECTED = {
161+
amd64: 'amd64',
162+
arm64: 'arm64',
163+
s390x: 's390x',
164+
ppc64el: 'ppc64le', // Caddy uses Go's name, not Debian's
165+
riscv64: 'riscv64',
166+
armhf: 'armv7', // Debian armhf's baseline IS Go's GOARM=7
167+
};
168+
Object.entries(EXPECTED).forEach(([deb, go]) => {
169+
const re = new RegExp(`${deb.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\)\\s*CADDY_ARCH=${go}\\b`);
170+
assert.ok(re.test(caseBlock),
171+
`${deb} must map to Caddy's ${go}; Caddy publishes no linux_${deb} archive`);
172+
});
173+
});
174+
175+
test('every architecture snapcraft builds has a Caddy mapping', () => {
176+
// The list that decides which snaps exist is build-for in this same file, so
177+
// a new architecture added there without a Caddy branch is caught here rather
178+
// than by a 404 halfway through a release.
179+
const buildFor = [...new Set(
180+
[...yaml.matchAll(/^\s*build-for:\s*(\S+)\s*$/gm)].map(m => m[1]))];
181+
assert.ok(buildFor.length >= 5, `expected the build-for list, found ${buildFor.length}`);
182+
const caseBlock = caddyCase();
183+
buildFor.forEach(a => {
184+
assert.ok(new RegExp(`(^|\\s|\\|)${a}\\)`, 'm').test(caseBlock),
185+
`snapcraft builds ${a} but no Caddy architecture is mapped for it`);
186+
});
187+
});
188+
189+
test('NEGATIVE: an unmapped architecture fails loudly instead of 404ing', () => {
190+
// The old default silently produced a URL that cannot exist. A guess that
191+
// looks like a Caddy release name is worse than no guess, because the failure
192+
// then reads as "Caddy stopped publishing this architecture".
193+
const caseBlock = caddyCase();
194+
assert.ok(!/\*\)\s*CADDY_ARCH="\$\{CRAFT_ARCH_BUILD_FOR\}"/.test(caseBlock),
195+
'the default branch still passes the Debian name straight through');
196+
assert.ok(/no Caddy architecture is mapped for/.test(caseBlock),
197+
'the default branch must say what went wrong');
198+
assert.ok(/exit 1/.test(caseBlock), 'and must stop rather than 404 later');
199+
});
200+
135201
console.log(`\n${passed} tests passed`);

0 commit comments

Comments
 (0)