Skip to content

Fix the S3 publish summary, and add a changelog and ADR - #3

Closed
t4k wants to merge 3 commits into
mainfrom
ci/fix-summary-exit
Closed

Fix the S3 publish summary, and add a changelog and ADR#3
t4k wants to merge 3 commits into
mainfrom
ci/fix-summary-exit

Conversation

@t4k

@t4k t4k commented Sep 2, 2026

Copy link
Copy Markdown
Member

Three things, all arising from the first dry run against the released @v1.

1. The summary step failed after a successful publish

✓ Build the bundles
✗ Run caltechlibrary/workflows/.github/actions/publish-to-s3@v1
    ==> dist -> s3://***/cl-webcomponents/
    ==> css  -> s3://***/cl-webcomponents/css/
    ==> would invalidate /cl-webcomponents/* on ***
    publish-to-s3: dry run done
    ##[error]Process completed with exit code 1
[ "$COUNT" -gt 40 ] && echo "- …and $((COUNT - 40)) more"

Returns 1 when the count is below the threshold, and it was the last command in the block. Composite action steps run with bash -eo pipefail, so the step failed. Replaced with an if.

It triggers on uploads of 40 files or fewer — every realistic upload — so v1 is currently broken for any consumer.

CI missed it because publish-to-s3-guards exercises bin/publish-to-s3.sh, while this lived in the action's summary step, reachable only by running the action end to end. That needs AWS credentials this repository deliberately does not hold, so a consuming repository's dry run is the test — working as intended, just later than ideal.

2. CHANGELOG.md

Following Keep a Changelog 1.1.0, with v1.0.0 and v1.1.0 reconstructed.

Consumers reference @v1, which moves. Until now there was no way to learn what moved short of reading commits — the one real cost of a moving major tag. CONTRIBUTING.md now asks for an entry with any change that reaches a consumer.

3. ADR-0007 — constraints on publishing to the media bucket

Four choices in publish-to-s3 that each look like an oversight and each cause a visible outage if "corrected":

Choice What breaks if changed
cp, never sync --delete removes the fonts/ directory, which exists in no repository — every site using footer-global loses its typeface
keep --acl public-read the bucket has no policy; uploads succeed but are invisible to anonymous readers
explicit content types the CLI's guess omits the charset and has changed between versions, so what the CDN serves changes for files nobody edited
invalidate /$PREFIX/*, not /* discards every other project's cached objects and bills per path

All four were defended only by comments in the script. A comment does not survive someone rewriting the file it lives in, and the failure modes are not guessable from the code.

The ADR also records the order for the bucket migration that would relax two of them — policy first, verify, then BucketOwnerEnforced — since doing it in any other order takes the distribution down.

After merging

v1 needs to move again, since the released one still has the bug. Then I'll re-run the consumer dry run before opening the CL-web-components caller.

🤖 Generated with Claude Code

t4k and others added 2 commits September 1, 2026 17:16
Uploads built assets to an S3 bucket and invalidates the matching CloudFront
paths, authenticating with OIDC so no long-lived AWS keys exist anywhere.
Replaces the per-repository publish_to_s3.bash and invalidate_cdn.bash
scripts, which needed an uncommitted media.env to exist on someone's machine.

The invalidation stays in this action rather than becoming a separate one.
It is cleanup after an upload, not a step anyone would run on its own.

Two guards, both covering ways the old scripts could do damage quietly:

- It refuses to publish an empty or missing source directory. A build that
  "succeeds" into nothing would otherwise leave the CDN serving stale objects
  with no signal that anything went wrong.
- The invalidation is scoped to the project's own prefix. The previous script
  invalidated /*, which charged for and discarded every other project's cached
  objects in the same distribution.

Content types are set explicitly per extension rather than left to the AWS
CLI's guess, which omits the charset and has changed between CLI versions.
publish_to_s3.bash set text/javascript and text/css with charset=utf-8, and
the live objects carry those values; replacing it should not quietly change
them.

It uses `cp`, not `sync --delete`. The CL-web-components prefix holds a fonts/
directory that exists in no repository -- three woff files that page.tmpl and
the footer component load. A delete-enabled sync would remove them and nothing
would put them back.

--acl public-read is kept and the reason recorded: the media bucket is in
legacy ObjectWriter mode with no bucket policy, so public read comes entirely
from per-object ACLs. Removing the need for it means adding a bucket policy
first, verifying the CDN still serves, then setting BucketOwnerEnforced -- in
that order, or every project on the distribution goes down.

The calling job must declare `permissions: id-token: write`; a composite
action cannot. The role's trust policy must name the calling repository's
subject claim, which is repo-specific. Both are documented, because both cost
time to diagnose from the error message alone.

CI tests the argument handling and the guards but holds no AWS credentials.
This repository should not have any.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@t4k t4k changed the title Fix the S3 publish summary failing on small uploads Fix the S3 publish summary, and add a changelog and ADR Sep 2, 2026
Five things, all surfacing from the first dry run of publish-to-s3 against a
real consumer.

Fixed: the summary step failed after a successful publish whenever 40 or fewer
files were uploaded -- every realistic upload. It ended on a test that returns
non-zero when false, and composite action steps run with `bash -eo pipefail`,
so the step failed after the work had already succeeded. CI did not catch it
because the guards job exercises the script and this was in the action; the
consumer's dry run is what found it.

`prefix` is now optional. Empty means the bucket root, which is the shape a
project gets when it has a bucket to itself. Paths no longer collapse into a
double slash, stray leading and trailing slashes are normalised, and the
resulting whole-distribution invalidation is reported rather than silent.

The `acl` default is now `none`, following AWS's recommendation to disable
ACLs and grant access by bucket policy, which is also what new buckets get.
That is a breaking change: a bucket in legacy ObjectWriter mode with no policy
must now ask for public-read. It is safe to make now only because nothing
consumes this action yet.

The risk in that flip is a silent failure -- an object uploaded without an ACL
the bucket needs uploads fine and is unreadable -- so setting public-base-url
now also fetches one published file afterwards and fails on a non-200. Both
mistakes are loud in either direction: a missing ACL fails the fetch, and
setting one on a bucket with BucketOwnerEnforced already failed with
AccessControlListNotSupported.

CHANGELOG.md, following Keep a Changelog 1.1.0. Consumers reference a moving
major tag, so when @v1 advances they have had no way to learn what moved short
of reading commits. CONTRIBUTING.md asks for an entry with any change that
reaches a consumer, and documents the release procedure: entries accumulate
under [Unreleased], releasing renames that heading and tags the commit that
does so, one commit and one tag per release rather than per change.

ADR-0007 records why publishing touches only what it published -- copy rather
than sync with delete, no ACL by default, explicit content types, invalidation
scoped to the prefix. Each looks like an oversight to a reader assuming a
dedicated bucket, and each is destructive if "corrected". It is scoped to the
action rather than to any deployment: operational facts about a particular
bucket belong in the repository that publishes to it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant