Documentation
¶
Overview ¶
Package bundle owns the per-tenant static-content bundle lifecycle: HMAC-verified ingest webhook → authenticated GH release tarball fetch → un-tar into versioned directory → atomic swap of `current` symlink.
Per gocodealone-multisite SPEC.md:
V8: ingest webhook → HMAC sig ! valid; mismatched req → 401. V9: bundle version monotonic per tenant; replay attempt → 409. V24: content-repo tarball fetch ! authenticated; ⊥ public-anon GET.
Index ¶
Constants ¶
const SignatureHeader = "X-Multisite-Sig"
SignatureHeader is the canonical HMAC-bearing header.
Variables ¶
var ErrInvalidSignature = errors.New("ingest: invalid HMAC signature")
ErrInvalidSignature is returned when the HMAC header does not match.
var ErrSignatureHeaderMissing = errors.New("ingest: signature header missing")
ErrSignatureHeaderMissing is returned when no signature header was supplied.
var ErrVersionRegression = errors.New("ingest: version regression")
ErrVersionRegression is returned by OnPayload when the incoming bundle version is not monotonic (V9).
Functions ¶
func ComputeSignature ¶
ComputeSignature is the inverse of VerifyHMAC — used by the content- repo release.yml to sign outgoing requests. Returned header value is "sha256=<hex>".
Types ¶
type Fetcher ¶
type Fetcher struct {
// BundleRoot is the directory under which tenants are stored.
BundleRoot string
// Token is the GH PAT or App token used as `Authorization: Bearer`.
// Required (V24) — never make an anonymous request.
Token string
// HTTPClient lets tests inject a recording client. Default is
// http.DefaultClient with a 30s timeout if nil.
HTTPClient *http.Client
// Now is a clock injection point for tests.
Now func() time.Time
// MaxTarballBytes caps the downloaded size (defence against
// resource exhaustion). Default 200 MiB.
MaxTarballBytes int64
}
Fetcher fetches a tarball from a GH release URL using a Bearer token, streams it through an integrity hash, un-tars into a versioned dir, and atomically swaps the `current` symlink.
Layout on disk:
<root>/<tenant_slug>/<version>/... <root>/<tenant_slug>/current → <version>
`current` swap uses rename(2) semantics — readers see either the old target or the new target, never a partial state (V9 monotonic activation).
func (*Fetcher) Activate ¶
func (f *Fetcher) Activate(ctx context.Context, tenantSlug, version, tarballURL string) (string, error)
Activate fetches the tarball, hashes it, extracts it under <BundleRoot>/<tenantSlug>/<version>/, then atomically swaps the `current` symlink. Returns the SHA-256 hex of the tarball for integrity recording.
type IngestHandler ¶
type IngestHandler struct {
Secret string
OnPayload func(IngestPayload) error // host-supplied; usually queues a fetch
}
IngestHandler is an http.Handler that verifies the HMAC + parses the payload, then hands off to fn for the actual fetch + activate work.
The handler itself does NO fetching — that's the bundle fetcher's job (see Fetcher in fetcher.go) — so this handler stays trivially testable.
func (*IngestHandler) ServeHTTP ¶
func (h *IngestHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
type IngestPayload ¶
type IngestPayload struct {
Tag string `json:"tag"` // e.g. "v0.3.0"
Repo string `json:"repo"` // "GoCodeAlone/gocodealone-website"
TarballURL string `json:"tarball_url"` // GH release asset URL
}
IngestPayload is the body of POST /api/v1/ingest/release. Sent by the content-repo's release.yml GH Action after a tag is published; the host fetches the actual tarball.
func (*IngestPayload) Validate ¶
func (p *IngestPayload) Validate() error
Validate returns nil iff the payload satisfies basic shape rules.