Models: - cloud_projects: arcadia-cloud's mirror of DO Projects, indexed by (provider, provider_id); tenant_id + purpose classify each project. - cloud_resources: single unified resource table; kind-specific bits in attrs JSONB; first_seen_at / last_seen_at / stale_strike_count drive three-strike deletion. - cloud_resource_events: append-only audit (discovered, updated, deleted, drift_detected, tagged, restored). ArcadiaCloud.Cloud context owns the single upsert chokepoint that: - inserts new with `discovered` event - updates existing only when meaningful fields change - restores tombstoned rows seen again - bumps last_seen_at and resets strike count mark_stale/3 implements the three-strike rule. ArcadiaCloud.DigitalOcean.Client is a Req wrapper with auto-pagination. Per-purpose token resolution via .Tokens (phase 1: env DO_API_TOKEN; phase 2: vault). Per project_arcadia_cloud memory the long-term shape is one PAT per queue purpose for rate-limit isolation. ArcadiaCloud.Sync.Bootstrap ensures the skyai-internal DO Project exists on first sync, idempotent thereafter. ArcadiaCloud.Sync.DropletsWorker runs full droplet sync on the cloud_sync_full Oban queue. InventoryController wired to real data: platform_admin sees all, tenants see only their scope. Live smoke test against real DO: 5 droplets synced; skyai-internal project auto-created; events written; endpoint returns scoped results. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
41 lines
1.2 KiB
Elixir
41 lines
1.2 KiB
Elixir
defmodule ArcadiaCloud.Cloud.CloudResource do
|
|
use Ecto.Schema
|
|
import Ecto.Changeset
|
|
|
|
@primary_key {:id, :binary_id, autogenerate: true}
|
|
@foreign_key_type :binary_id
|
|
|
|
schema "cloud_resources" do
|
|
field :provider, :string
|
|
field :provider_id, :string
|
|
field :kind, :string
|
|
field :name, :string
|
|
field :region, :string
|
|
field :status, :string
|
|
field :size_slug, :string
|
|
field :tenant_id, :binary_id
|
|
field :deployment_id, :binary_id
|
|
field :tags, {:array, :string}, default: []
|
|
field :attrs, :map, default: %{}
|
|
field :first_seen_at, :utc_datetime
|
|
field :last_seen_at, :utc_datetime
|
|
field :stale_strike_count, :integer, default: 0
|
|
field :deleted_at, :utc_datetime
|
|
|
|
belongs_to :cloud_project, ArcadiaCloud.Cloud.CloudProject
|
|
|
|
timestamps(type: :utc_datetime)
|
|
end
|
|
|
|
@required ~w(provider provider_id kind name status first_seen_at last_seen_at)a
|
|
@optional ~w(region size_slug cloud_project_id tenant_id deployment_id tags attrs
|
|
stale_strike_count deleted_at)a
|
|
|
|
def changeset(resource, attrs) do
|
|
resource
|
|
|> cast(attrs, @required ++ @optional)
|
|
|> validate_required(@required)
|
|
|> unique_constraint([:provider, :provider_id])
|
|
end
|
|
end
|