Skip to content

storage/gcp, storage/aws: fix double BucketPrefix on idempotent-write recovery - #1208

Merged
mhutchinson merged 2 commits into
transparency-dev:mainfrom
eperrine-ant:storage-bucketprefix-recovery
Oct 6, 2026
Merged

mhutchinson merged 2 commits into
transparency-dev:mainfrom
eperrine-ant:storage-bucketprefix-recovery

Conversation

@eperrine-ant

Copy link
Copy Markdown
Contributor

What

Fixes the idempotent-write recovery path in the GCP and AWS drivers when BucketPrefix is set.

gcsStorage.setObject and s3Storage.setObjectIfNoneMatch overwrite objName with the prefixed name before the conditional write. When that write fails its precondition because the object already exists, they call getObject with the already-prefixed name to compare contents. getObject applies bucketPrefix itself, so the read goes to <prefix>/<prefix>/<name>, fails with not-found, and the caller gets an error instead of success.

The fix keeps objName unprefixed, uses a separate prefixedName for the write and for log, error and trace output, and passes the unprefixed name to getObject. One commit per driver. Nothing changes when BucketPrefix is empty.

Why

Tiles and entry bundles are written with a "does not exist" precondition, and a write that finds a bit-for-bit identical object already there is meant to succeed, so that retried writes are idempotent. With a non-empty BucketPrefix that branch can never succeed: every such write returns failed to fetch existing content ... object doesn't exist (GCS) or NoSuchKey (S3), even though the stored data matches.

Testing

  • TestSetObjectBucketPrefixIdempotentRecovery (GCP) drives gcsStorage with a non-empty prefix against an httptest fake of the GCS JSON API that answers the conditional upload with 412 and serves identical bytes only at <prefix>/<name>. It checks setObject returns nil and the recovery read was for the single-prefixed object.
  • TestSetObjectIfNoneMatchBucketPrefixIdempotentRecovery (AWS) does the same for s3Storage against an httptest fake S3 endpoint, and also checks the conditional PUT key.

Each test fails without the corresponding driver change, with the recovery read going to <prefix>/<prefix>/<name>.

go test ./storage/gcp/... ./storage/aws/..., go vet, gofmt, golangci-lint clean.

@eperrine-ant
eperrine-ant requested a review from a team as a code owner October 1, 2026 15:31
setObject prefixed objName with bucketPrefix before the write, then on
a precondition failure called getObject with that already-prefixed
name. getObject applies bucketPrefix again, so the recovery read looked
for <prefix>/<prefix>/<name> and always failed whenever BucketPrefix is
non-empty, defeating the idempotent-write branch the precondition check
exists to reach.

Keep the caller's name unprefixed and use a separate prefixedName for
the write, so the recovery getObject call gets the name it expects.
Adds a regression test that drives gcsStorage against an httptest fake
GCS endpoint with a non-empty BucketPrefix and asserts the recovery
read targets the single-prefixed object and setObject returns nil.
…nt recovery

Same defect as the GCP fix in the previous commit: setObjectIfNoneMatch
prefixed objName with bucketPrefix before the conditional write, then on
a precondition failure called getObject with that already-prefixed name.
getObject applies bucketPrefix again, so the recovery read looked for
<prefix>/<prefix>/<name> and always failed whenever BucketPrefix is
non-empty, defeating the idempotent-write branch.

Keep the caller's name unprefixed and use a separate prefixedName for
the write, so the recovery getObject call gets the name it expects.
Adds a regression test that drives s3Storage against an httptest fake
S3 endpoint with a non-empty BucketPrefix and asserts both the write
and the recovery read target the single-prefixed key.
@mhutchinson
mhutchinson force-pushed the storage-bucketprefix-recovery branch from c908a6f to 7264afe Compare October 6, 2026 15:41
@mhutchinson
mhutchinson merged commit 3504bec into transparency-dev:main Oct 6, 2026
20 checks passed
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.

2 participants