Skip to content

Commit ed1ea75

Browse files
committed
gc cron job to clean up dangling policy_bindings entries
1 parent 28bb18e commit ed1ea75

4 files changed

Lines changed: 71 additions & 0 deletions

File tree

config/prod.exs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ config :console, Console.Cron.Scheduler,
6868
{"15 * * * *", {Console.Deployments.Cron, :prune_vuln_reports, []}},
6969
{"*/15 * * * *", {Console.Deployments.Cron, :pr_governance, []}},
7070
{"15 3 * * *", {Console.Deployments.Cron, :prune_dangling_templates, []}},
71+
{"20 3 * * *", {Console.Deployments.Cron, :prune_dangling_policy_bindings, []}},
7172
{"30 3 * * *", {Console.Deployments.Cron, :prune_insight_components, []}},
7273
{"0 4 * * *", {Console.Deployments.Cron, :prune_helm_repositories, []}},
7374
{"0 5 * * *", {Console.Deployments.Cron, :prune_agent_run_repositories, []}},

lib/console/deployments/cron.ex

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ defmodule Console.Deployments.Cron do
1818
AppNotification,
1919
Alert,
2020
ClusterAuditLog,
21+
PolicyBinding,
2122
PolicyConstraint,
2223
VulnerabilityReport,
2324
ServiceTemplate,
@@ -298,6 +299,20 @@ defmodule Console.Deployments.Cron do
298299
|> Stream.run()
299300
end
300301

302+
def prune_dangling_policy_bindings() do
303+
PolicyBinding.dangling()
304+
|> PolicyBinding.ordered(asc: :id)
305+
|> Repo.stream(method: :keyset)
306+
|> Stream.chunk_every(100)
307+
|> Stream.each(fn bindings ->
308+
ids = Enum.map(bindings, & &1.id)
309+
Logger.info "pruning #{length(ids)} dangling policy bindings"
310+
PolicyBinding.for_ids(ids)
311+
|> Repo.delete_all(timeout: 10_000)
312+
end)
313+
|> Stream.run()
314+
end
315+
301316
def add_ignore_crds(search) do
302317
Service.search(search)
303318
|> Repo.stream(method: :keyset)

lib/console/schema/policy_binding.ex

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,34 @@ defmodule Console.Schema.PolicyBinding do
1010
timestamps()
1111
end
1212

13+
def dangling(query \\ __MODULE__) do
14+
from(p in query,
15+
where:
16+
fragment("NOT EXISTS(SELECT 1 FROM services WHERE write_policy_id = ? OR read_policy_id = ?)", p.policy_id, p.policy_id) and
17+
fragment("NOT EXISTS(SELECT 1 FROM clusters WHERE write_policy_id = ? OR read_policy_id = ?)", p.policy_id, p.policy_id) and
18+
fragment("NOT EXISTS(SELECT 1 FROM projects WHERE write_policy_id = ? OR read_policy_id = ?)", p.policy_id, p.policy_id) and
19+
fragment("NOT EXISTS(SELECT 1 FROM pipelines WHERE write_policy_id = ? OR read_policy_id = ?)", p.policy_id, p.policy_id) and
20+
fragment("NOT EXISTS(SELECT 1 FROM stacks WHERE write_policy_id = ? OR read_policy_id = ?)", p.policy_id, p.policy_id) and
21+
fragment("NOT EXISTS(SELECT 1 FROM catalogs WHERE write_policy_id = ? OR read_policy_id = ? OR create_policy_id = ?)", p.policy_id, p.policy_id, p.policy_id) and
22+
fragment("NOT EXISTS(SELECT 1 FROM deployment_settings WHERE write_policy_id = ? OR read_policy_id = ? OR create_policy_id = ? OR git_policy_id = ?)", p.policy_id, p.policy_id, p.policy_id, p.policy_id) and
23+
fragment("NOT EXISTS(SELECT 1 FROM pr_automations WHERE write_policy_id = ? OR create_policy_id = ?)", p.policy_id, p.policy_id) and
24+
fragment("NOT EXISTS(SELECT 1 FROM flows WHERE write_policy_id = ? OR read_policy_id = ?)", p.policy_id, p.policy_id) and
25+
fragment("NOT EXISTS(SELECT 1 FROM cluster_providers WHERE write_policy_id = ? OR read_policy_id = ?)", p.policy_id, p.policy_id) and
26+
fragment("NOT EXISTS(SELECT 1 FROM mcp_servers WHERE write_policy_id = ? OR read_policy_id = ?)", p.policy_id, p.policy_id) and
27+
fragment("NOT EXISTS(SELECT 1 FROM oidc_providers WHERE bindings_id = ? OR write_policy_id = ?)", p.policy_id, p.policy_id) and
28+
fragment("NOT EXISTS(SELECT 1 FROM personas WHERE bindings_id = ?)", p.policy_id) and
29+
fragment("NOT EXISTS(SELECT 1 FROM watchman_users WHERE assume_policy_id = ?)", p.policy_id) and
30+
fragment("NOT EXISTS(SELECT 1 FROM pull_requests WHERE notifications_policy_id = ?)", p.policy_id) and
31+
fragment("NOT EXISTS(SELECT 1 FROM cloud_connections WHERE read_policy_id = ?)", p.policy_id) and
32+
fragment("NOT EXISTS(SELECT 1 FROM compliance_report_generators WHERE read_policy_id = ?)", p.policy_id) and
33+
fragment("NOT EXISTS(SELECT 1 FROM agent_runtimes WHERE create_policy_id = ?)", p.policy_id)
34+
)
35+
end
36+
37+
def ordered(query \\ __MODULE__, order \\ [asc: :id]) do
38+
from(p in query, order_by: ^order)
39+
end
40+
1341
@valid ~w(user_id group_id policy_id)a
1442

1543
def changeset(model, attrs \\ %{}) do

test/console/deployments/cron_test.exs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,4 +349,31 @@ defmodule Console.Deployments.CronTest do
349349
assert refetch(keep)
350350
end
351351
end
352+
353+
describe "#prune_dangling_policy_bindings/0" do
354+
test "it will prune dangling policy bindings" do
355+
user = insert(:user)
356+
357+
# Create a project with write_bindings - these should be kept
358+
project = insert(:project, write_bindings: [%{user_id: user.id}])
359+
%{write_bindings: [kept_binding]} = Console.Repo.preload(project, [:write_bindings])
360+
361+
# Create orphaned policy bindings with random policy_ids that don't exist anywhere
362+
orphaned = for _ <- 1..3 do
363+
%Console.Schema.PolicyBinding{}
364+
|> Console.Schema.PolicyBinding.changeset(%{
365+
policy_id: Ecto.UUID.generate(),
366+
user_id: user.id
367+
})
368+
|> Console.Repo.insert!()
369+
end
370+
:ok = Cron.prune_dangling_policy_bindings()
371+
372+
# Referenced binding should still exist
373+
assert refetch(kept_binding)
374+
375+
# Orphaned bindings should be deleted
376+
for binding <- orphaned, do: refute refetch(binding)
377+
end
378+
end
352379
end

0 commit comments

Comments
 (0)