Skip to content

statics: link auto-provisioned tigris bucket to its app#4827

Open
jphenow wants to merge 3 commits intomasterfrom
jphenow/statics-link-addon-to-app
Open

statics: link auto-provisioned tigris bucket to its app#4827
jphenow wants to merge 3 commits intomasterfrom
jphenow/statics-link-addon-to-app

Conversation

@jphenow
Copy link
Copy Markdown
Member

@jphenow jphenow commented Apr 15, 2026

Summary

  • statics.ensureBucketCreated now passes AppName when provisioning the tigris bucket so web's create_add_on mutation populates add_ons.app_id (internal/command/deploy/statics/addon.go). New buckets are linked by FK, not just by the staticsMetaKeyAppId metadata pointer.
  • statics.FindBucket is now tiered: it tries GetAppWithAddons (app-scoped) first and falls back to ListOrganizationAddOns (the org-scoped query from fix(statics): scope FindBucket to the app's org #4826) only if no match is found. Legacy buckets created before the FK link still resolve via the fallback, no backfill needed.
  • Both paths verify the staticsMetaKeyAppId metadata pointer. A user-provisioned tigris add-on that happens to be attached to the same app via fly ext create tigris will not be mistaken for a statics bucket and will not be touched by fly apps destroy.
  • Bucket moves from a generated-type alias to a small local struct so both GraphQL paths can feed into it.

Base branch: this PR is stacked on top of #4826 (needs the ListOrganizationAddOns query) — please merge that one first.

Why stacked / why in one PR

The user explicitly asked for the backwards-compatible app-scoped lookup in this PR: "we can assume we're not backfilling so both should work, new stuff should work better." Keeping the FK write and the FK read in the same change means we never ship a flyctl version that writes the link but doesn't read it (or vice versa), and the legacy fallback is always present.

Behavior change worth calling out

extensions.ProvisionExtension errors with "already exists for app" when AppName is set and the app already has any tigris add-on (internal/command/extensions/core/core.go:74-87). Previously, with AppName empty, statics could silently create a second tigris bucket alongside a user's manually-provisioned one. Now it will refuse. That seems like the better behavior — there's no sane way for statics to share a bucket with a user-owned tigris — but reviewers should confirm.

Test plan

  • go build ./... (passes locally)
  • go vet ./internal/command/deploy/statics/... ./internal/command/apps/... ./gql/... (passes locally)
  • Manual: deploy a fresh app with statics, confirm the tigris add-on shows up under fly ext list --app <name> (proves app_id is set).
  • Manual: redeploy the same app, confirm no new bucket is created (proves FindBucket's app-scoped path hits).
  • Manual: fly apps destroy <app> — confirm the statics bucket is still cleaned up.
  • Manual: against an account with a legacy statics bucket (created before this PR), confirm fly apps destroy still finds and removes it via the org-scoped fallback.
  • Manual: against an account with many tigris add-ons, confirm fly apps destroy returns quickly on apps that do not have a statics bucket (the app-scoped query short-circuits before the fallback ever runs).

jphenow added 2 commits April 15, 2026 12:09
FindBucket was calling the top-level addOns(type: tigris) query, which
returns every tigris add-on the caller can see across every org and then
filters client-side by org + metadata. For accounts with a lot of tigris
add-ons this stalls `fly apps destroy` (and `fly apps move`).

Use Organization.addOns(type:) via a new ListOrganizationAddOns query so
we only transfer the relevant org's add-ons. Metadata-based app matching
stays the same, since existing statics buckets are not linked by app_id.
Two changes, one motivation — stop dragging every tigris add-on through
statics bucket lookups:

  * ensureBucketCreated now passes AppName to ProvisionExtension so web's
    create_add_on mutation populates add_ons.app_id. New buckets are
    linked by FK rather than only by the staticsMetaKeyAppId metadata
    pointer.

  * FindBucket now tries GetAppWithAddons (app-scoped) first and falls
    back to the org-scoped ListOrganizationAddOns query only if no match
    is found. Both paths verify the staticsMetaKeyAppId pointer so a
    user-provisioned tigris add-on on the same app is not mistaken for a
    statics bucket. Legacy buckets (created before the first change)
    still resolve via the fallback without any backfill.

The Bucket return type moves from a generated type alias to a small
local struct so both code paths can feed into it cleanly.
@jphenow jphenow force-pushed the jphenow/statics-link-addon-to-app branch from 5045b48 to 572a73d Compare April 15, 2026 19:43
@jphenow jphenow changed the base branch from master to jphenow/statics-findbucket-org-scope April 15, 2026 19:44
golangci-lint's nlreturn linter flagged the bare `return &Bucket{...}`
after the guard. Add the blank line it wants.
@jphenow jphenow changed the base branch from jphenow/statics-findbucket-org-scope to master April 15, 2026 21:02
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