Documentation
¶
Overview ¶
Package awss3 reads what S3 said in the body of a refusal.
IT IS NOT AN S3 CLIENT. billet has two of those — internal/store/ebss3 for the site cache and internal/archivestore for the off-box deployment archive — and this is the one thing they have to agree about. It lives beside internal/awssig for the reason internal/awsquota and internal/awssts do: a store and a transport are siblings that may not import each other, and both need this answer.
WHAT IT EXISTS FOR is that a 404 from S3 is TWO DIFFERENT FACTS. `NoSuchKey` says the object is not there, which is an ordinary answer that a cold cache and a bucket nobody has written to yet both produce. `NoSuchBucket` says billet is addressing a bucket that does not exist, which is a misconfiguration no amount of retrying fixes. Both readers used to treat every 404 as the first, so a cache pointed at a bucket S3 had never heard of was indistinguishable from a cold one and nothing reported a fault.
SO ONLY `NoSuchKey` MEANS ABSENCE, and a 404 carrying anything else — another code, no code, a body billet could not read — stays an error. That is the direction the house rule points: could-not-tell never collapses into no.
MEASURED AGAINST REAL S3 IN us-east-1 ON 2026-09-04, because a rule about somebody else's API written from its documentation agrees with whatever billet already believes. WHAT WAS OBSERVED, exactly, is two unauthenticated GETs, both answering HTTP 404 with a body ending at </Error> and no trailing byte:
one key that was not there, in a public bucket that was: <Error><Code>NoSuchKey</Code><Message>The specified key does not exist.</Message><Key>…</Key><RequestId>…</RequestId><HostId>…</HostId></Error> one key in a randomly named bucket that could not exist: <Error><Code>NoSuchBucket</Code><Message>The specified bucket does not exist</Message><BucketName>…</BucketName><RequestId>…</RequestId>…</Error>
TWO INFERENCES, LABELLED AS THAT. That the first answered 404 rather than 403 is consistent with that bucket granting anonymous s3:ListBucket, which is the condition internal/store/ebss3's CheckAccess already names — no policy was read. And that the second answered without a credential suggests the name is resolved before the request is authorized, which is a reading of one response rather than a measurement of S3's ordering. Neither inference is what the code rests on: it rests on the two codes.
reals3_test.go is the same pair SIGNED, against a bucket an operator points it at, which is the run that would contradict any of this.
Index ¶
Constants ¶
const ( // CodeNoSuchKey is the ONLY 404 that means an object is absent. CodeNoSuchKey = "NoSuchKey" // CodeNoSuchBucket is a bucket that does not exist at the address billet // built out of the configured bucket name and region. CodeNoSuchBucket = "NoSuchBucket" )
The two codes billet acts on by name. Every other code travels as itself.
Variables ¶
This section is empty.
Functions ¶
func RegionHint ¶
RegionHint reports the bucket's real region when S3 named one.
S3 answers a wrong-region request with a redirect carrying this header, and an operator staring at a bare 301 has no other way to see that their region is wrong. It is empty when the header is absent or is not something billet will repeat.
func StatusOf ¶
StatusOf reports the status of the S3 refusal in err's chain, or zero.
ZERO IS "NOT AN S3 REFUSAL", which is what a transport failure, a credential that would not resolve and a nil error all are. A caller branching on a particular status therefore falls through on all three rather than treating them as that status.
Types ¶
type Refusal ¶
type Refusal struct {
// Status is the HTTP status S3 answered with.
Status int
// Code is the <Code> element of S3's error document, empty when S3 named
// none or billet could not read one. Empty is could-not-tell.
Code string
}
Refusal is one non-2xx answer from S3, as an error.
AN ERROR RATHER THAN A VALUE so a caller can wrap it with %w and anything above can ask what S3 actually said without matching on prose. `billet check`'s cache probe classifies a 403 as inconclusive, and it used to do that by looking for the substring "HTTP 403" in a rendered message — which makes a reworded diagnostic silently change the verdict.
func ParseRefusal ¶
ParseRefusal is ReadRefusal for a caller that already holds the body — the listing paths read theirs before they look at the status.
func ReadRefusal ¶
ReadRefusal consumes a bounded prefix of a non-2xx response and reports what S3 said. The caller still closes the body.
func (*Refusal) Absent ¶
Absent reports the one 404 that is a fact rather than a failure.
THE STATUS IS CHECKED AS WELL AS THE CODE. A code alone would let a body that arrived with some other status decide that an object is missing, and the two halves of the answer come from different places in the response.