ExAtlas.Fly.Tokens.AppServer (ExAtlas v0.5.0)

Copy Markdown View Source

Per-app Fly-token resolver.

One AppServer process exists per Fly app that has ever been asked for a token. The process owns the full resolution chain for its app:

  1. ETS cache re-check (in case a concurrent caller won the race).
  2. ExAtlas.Fly.TokenStorage (durable).
  3. ~/.fly/config.yml.
  4. fly tokens create readonly (CLI).
  5. Manual-override token (last-resort storage lookup).

The shared ETS table is owned by ExAtlas.Fly.Tokens.ETSOwner; this server writes into it but does not own it. That split is what lets per-app crashes stay scoped (DynamicSupervisor :one_for_one) while still preserving the cache for sibling apps.

Runtime options (for test injection)

  • :app_name (required) — the Fly app this server tracks.
  • :registry (required) — the Registry name used for {:via, ...} naming.
  • :task_supTask.Supervisor name used to offload non-blocking persist writes. Optional; when omitted, persist runs inline (synchronous) which is fine for tests without a Task.Supervisor in scope.
  • :table_name — override the ETS table to write into.
  • :cmd_fn — replaces System.cmd/3.
  • :config_file_fn — replaces the ~/.fly/config.yml reader.
  • :storage_mod — replaces the storage module.
  • :ttl_seconds — token TTL (default 24h).
  • :cli_timeout_msfly tokens create timeout (default 15s).
  • :soft_expiry_lead_seconds — how far before expires_at to schedule a background refresh (default 3600). Set to 0 to disable.

Summary

Functions

Resolve a token for this AppServer's app. Returns {result, source}.

Returns a specification to start this module under a supervisor.

Invalidate this AppServer's ETS entry.

Atomic invalidate-then-acquire. Equivalent to invalidate/1 followed by acquire/1, but executed under a single handle_call so no concurrent caller can acquire between the two and see the pre-refresh token.

Persist a manual-override token for this AppServer's app.

Functions

acquire(pid)

@spec acquire(pid()) ::
  {{:ok, String.t()}, atom()} | {{:error, :no_token_available}, :none}

Resolve a token for this AppServer's app. Returns {result, source}.

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

invalidate(pid)

@spec invalidate(pid()) :: :ok

Invalidate this AppServer's ETS entry.

refresh(pid)

@spec refresh(pid()) ::
  {{:ok, String.t()}, atom()} | {{:error, :no_token_available}, :none}

Atomic invalidate-then-acquire. Equivalent to invalidate/1 followed by acquire/1, but executed under a single handle_call so no concurrent caller can acquire between the two and see the pre-refresh token.

Returns the same {result, source} tuple as acquire/1.

set_manual(pid, token)

@spec set_manual(pid(), String.t()) :: :ok | {:error, {:persist_failed, String.t()}}

Persist a manual-override token for this AppServer's app.

Returns {:error, {:persist_failed, reason}} if the storage backend raises — manual tokens are not re-acquirable, so the failure is surfaced rather than silently logged.

start_link(opts)