Packages: retention is enforced by default

5ade69f · gmorell · 2026-11-03 19:53

17 files +31 -29
Message
{commit_body(@commit)}

Files changed

modified config/config.exs
+3 −2
@@ -65,8 +65,9 @@ config :git_gud, Oban,
65 65 # Re-sync repositories that opted into a scheduled upstream
66 66 # fetch. Anonymous — a private upstream is synced by hand.
67 67 {"@hourly", GitGud.Repositories.Workers.UpstreamSync},
68 − # Retention pass over the package registries. Reports rather
69 − # than deletes until `packages.retention_enforced` is on.
68 + # Retention pass over the package registries. Deletes while the
69 + # `packages.retention_enforced` setting is on (the default);
70 + # reports what it would delete while an admin has it off.
70 71 {"@daily", GitGud.Packages.Workers.Prune},
71 72 # Prune `actions/cache` entries by age + per-repo size cap.
72 73 {"@hourly", GitGud.Workflows.Workers.CacheEviction},
modified lib/git_gud/packages/retention.ex
+3 −4
@@ -33,10 +33,9 @@ defmodule GitGud.Packages.Retention do
33 33 ## Reporting vs enforcing
34 34
35 35 `plan/1` computes what would go; `apply/2` is what deletes. They are
36 − separate because the instance-wide default would prune a lot of
37 − history the first time it ran on an instance that predates it, and
38 − "here is what this would do" is the only honest way to ask an admin
39 − to turn it on. `packages.retention_enforced` is off until they do.
36 + separate so an admin can see "here is what this would do" before it
37 + does it: `packages.retention_enforced` is on by default, and turning
38 + it off leaves the nightly pass reporting the plan without acting.
40 39 """
41 40
42 41 import Ecto.Query, warn: false
modified lib/git_gud/packages/workers/prune.ex
+4 −4
@@ -2,10 +2,10 @@ defmodule GitGud.Packages.Workers.Prune do
2 2 @moduledoc """
3 3 The nightly retention pass.
4 4
5 − Does nothing at all until an admin turns `packages.retention_enforced`
6 − on — it still runs, and still logs what it *would* free, which is the
7 − number the admin view shows and the reason the job exists before
8 − anyone has decided to enable it.
5 + Deletes what the retention policy lets go, while
6 + `packages.retention_enforced` is on (the default). With it off it
7 + still runs, and logs what it *would* free — the number the admin view
8 + shows.
9 9
10 10 Scheduled from `config/config.exs`. Daily rather than hourly: a
11 11 registry's disk problem develops over weeks, and a nightly job is
modified lib/git_gud/settings.ex
+5 −6
@@ -14,11 +14,10 @@ defmodule GitGud.Settings do
14 14 ## The keys
15 15
16 16 * `packages.retention_enforced` — whether the pruner deletes
17 − anything at all. **Off by default**, and deliberately: an
18 − instance that upgrades into this slice has years of versions that
19 − the default policy would prune on its first pass. Reporting mode
20 − computes the same plan and shows it, so the decision to start
21 − deleting is one an admin makes after looking at the number.
17 + anything at all. **On by default**: CI publishes on every push,
18 + so a registry left alone is a disk that fills. Turn it off for
19 + reporting mode, which computes the same plan and shows it without
20 + deleting — the admin packages page has the switch and the number.
22 21
23 22 * `packages.retention_mode` / `packages.retention_keep` /
24 23 `packages.retention_bytes` — the instance policy, and the ceiling
@@ -37,7 +36,7 @@ defmodule GitGud.Settings do
37 36 alias GitGud.Settings.InstanceSetting
38 37
39 38 @keys %{
40 − "packages.retention_enforced" => %{type: :boolean, default: false},
39 + "packages.retention_enforced" => %{type: :boolean, default: true},
41 40 "packages.retention_mode" => %{type: :string, default: "keep_last", in: ~w(keep_last quota)},
42 41 "packages.retention_keep" => %{type: :integer, default: 24, min: 1, max: 10_000},
43 42 "packages.retention_bytes" => %{type: :integer, default: nil, min: 0, nullable: true},
modified test/git_gud/packages/pruner_test.exs
+5 −5
@@ -21,6 +21,11 @@ defmodule GitGud.Packages.PrunerTest do
21 21 end
22 22
23 23 describe "prune/2 while enforcement is off" do
24 + setup do
25 + {:ok, _} = Settings.put("packages.retention_enforced", false)
26 + :ok
27 + end
28 +
24 29 test "reports what it would do and touches nothing", %{repo: repo} do
25 30 package = package_with_versions(repo, 30)
26 31
@@ -42,11 +47,6 @@ defmodule GitGud.Packages.PrunerTest do
42 47 end
43 48
44 49 describe "prune/2 with enforcement on" do
45 − setup do
46 − {:ok, _} = Settings.put("packages.retention_enforced", true)
47 − :ok
48 − end
49 −
50 50 test "deletes the oldest versions and their bytes", %{repo: repo} do
51 51 package = package_with_versions(repo, 26)
52 52
modified test/git_gud/packages/retention_test.exs
+4 −4
@@ -200,13 +200,13 @@ defmodule GitGud.Packages.RetentionTest do
200 200 end
201 201
202 202 describe "enforcement" do
203 − test "is off until an admin turns it on" do
204 − refute Retention.enforced?()
203 + test "is on until an admin turns it off" do
204 + assert Retention.enforced?()
205 205
206 206 user = user_fixture()
207 − {:ok, _} = Settings.put("packages.retention_enforced", true, user)
207 + {:ok, _} = Settings.put("packages.retention_enforced", false, user)
208 208
209 − assert Retention.enforced?()
209 + refute Retention.enforced?()
210 210 end
211 211 end
212 212
modified test/git_gud_web/live/admin_packages_test.exs
+7 −4
@@ -1,7 +1,8 @@
1 1 defmodule GitGudWeb.AdminPackagesTest do
2 2 @moduledoc """
3 − The admin view over registry disk, and the levers on it. The levers
4 − are the point: retention is off until someone here turns it on.
3 + The admin view over registry disk, and the levers on it. Retention is
4 + on by default; with it off the page reports what it would free, and
5 + only the button deletes.
5 6 """
6 7 use GitGudWeb.ConnCase, async: false
7 8
@@ -24,6 +25,7 @@ defmodule GitGudWeb.AdminPackagesTest do
24 25 end
25 26
26 27 test "shows what retention would free without deleting anything", %{conn: conn, admin: admin} do
28 + {:ok, _} = Settings.put("packages.retention_enforced", false)
27 29 {:ok, lv, _html} = conn |> log_in_user(admin) |> live(~p"/admin/packages")
28 30
29 31 html = render_async(lv)
@@ -41,7 +43,7 @@ defmodule GitGudWeb.AdminPackagesTest do
41 43 lv
42 44 |> form("form[phx-submit=save]", %{
43 45 "settings" => %{
44 − "packages.retention_enforced" => "true",
46 + "packages.retention_enforced" => "false",
45 47 "packages.retention_mode" => "keep_last",
46 48 "packages.retention_keep" => "10",
47 49 "packages.retention_bytes" => "",
@@ -50,7 +52,7 @@ defmodule GitGudWeb.AdminPackagesTest do
50 52 })
51 53 |> render_submit()
52 54
53 − assert Settings.get("packages.retention_enforced") == true
55 + assert Settings.get("packages.retention_enforced") == false
54 56 assert Settings.get("packages.retention_keep") == 10
55 57 assert Settings.get("packages.max_pins") == 5
56 58 # Blank means "no ceiling", not zero — zero would prune every
@@ -97,6 +99,7 @@ defmodule GitGudWeb.AdminPackagesTest do
97 99 end
98 100
99 101 test "prune now deletes, and only on the button", %{conn: conn, admin: admin} do
102 + {:ok, _} = Settings.put("packages.retention_enforced", false)
100 103 {:ok, lv, _html} = conn |> log_in_user(admin) |> live(~p"/admin/packages")
101 104 render_async(lv)
102 105

Parents: 4241ea4