Files
arcadia-cloud/lib/arcadia_cloud/sync/load_balancers_worker.ex
Giuliano Silvestro 8bdf500214 Round out DO sync workers: volumes, snapshots, floating IPs, firewalls, LBs
Five new Oban workers, all on cloud_sync_full following the established
droplet/domains pattern (list → normalize → upsert → mark_stale):

- VolumesWorker         — block storage
- SnapshotsWorker       — both droplet and volume snapshots (kind="snapshot"
                          with attrs.resource_type to differentiate)
- FloatingIpsWorker     — provider_id is the IP; status assigned/unassigned
- FirewallsWorker       — inbound/outbound rules + droplet_ids in attrs
- LoadBalancersWorker   — name + region + algorithm + forwarding rules

DigitalOcean.Client gains list_snapshots / list_firewalls / list_load_balancers.

ProjectsWorker URN normalization extended: "floatingip" → floating_ip,
"loadbalancer" → load_balancer, "dbaas" → managed_db. URNs DO emits don't
have underscores for these.

Cron updated: new workers run every 15min on cloud_sync_full; snapshots
moved to hourly (at :33) since they change slowly and listing them is
the most-paginated call we make.

InvoiceIngestWorker.derive_kind/2 reordered to check specific phrases
before generic products — "Droplet Snapshots"/"Droplet Backups" no longer
get bucketed as kind=droplet ahead of the actual snapshot check. Also
adds kind="droplet_backup" for DO's automated backup billing (separate
from the snapshot kind because backups aren't exposed via /v2/snapshots).

Live verified: 12 snapshots discovered + 1 firewall (account has no
volumes / floating IPs / LBs at the moment, so those workers ran clean).
April 2026 invoice match rate jumped from 18.2% → 51.5%. Of the
unmatched: 10 historic droplets that no longer exist on DO, 2 backups
(separate API surface), 1 Spaces bucket (S3 API, deferred), 1 GST
(correctly no kind). Effectively ~95% of currently-extant resources match.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 22:41:12 +10:00

49 lines
1.2 KiB
Elixir

defmodule ArcadiaCloud.Sync.LoadBalancersWorker do
@moduledoc "Full sync of DO load balancers."
use Oban.Worker, queue: :cloud_sync_full, max_attempts: 3
alias ArcadiaCloud.Cloud
alias ArcadiaCloud.DigitalOcean.Client
@kind "load_balancer"
@provider "digitalocean"
@impl Oban.Worker
def perform(_job) do
now = DateTime.utc_now() |> DateTime.truncate(:second)
with {:ok, lbs} <- Client.list_load_balancers() do
Enum.each(lbs, fn lb ->
Cloud.upsert_resource(normalize(lb, now))
end)
Cloud.mark_stale(@kind, now)
:ok
end
end
defp normalize(lb, now) do
%{
provider: @provider,
provider_id: lb["id"],
kind: @kind,
name: lb["name"],
region: get_in(lb, ["region", "slug"]),
status: lb["status"] || "active",
size_slug: lb["size"] || lb["size_unit"],
tags: lb["tag"] && [lb["tag"]] || [],
attrs: %{
ip: lb["ip"],
algorithm: lb["algorithm"],
forwarding_rules: lb["forwarding_rules"],
health_check: lb["health_check"],
droplet_ids: lb["droplet_ids"],
vpc_uuid: lb["vpc_uuid"],
do_created_at: lb["created_at"]
},
first_seen_at: now,
last_seen_at: now
}
end
end