Packages webhook: parse zot v2.1 events CloudEvent payload

1276b61 · gmorell · 2026-06-25 21:11

1 files +41 -20
Message
{commit_body(@commit)}

Files changed

modified lib/git_gud_web/controllers/packages_webhook_controller.ex
+41 −20
@@ -35,8 +35,12 @@ defmodule GitGudWeb.PackagesWebhookController do
35 35 alias GitGud.Packages
36 36 alias GitGud.Repositories
37 37
38 + require Logger
39 +
38 40 def notify(conn, params) do
39 41 with :ok <- authenticate(conn) do
42 + Logger.info("zot webhook: #{inspect(params, limit: 40, printable_limit: 1500)}")
43 +
40 44 case classify(params) do
41 45 {:push, payload} ->
42 46 handle_push(conn, payload)
@@ -55,32 +59,44 @@ defmodule GitGudWeb.PackagesWebhookController do
55 59
56 60 # ── dispatch ────────────────────────────────────────────────────────
57 61
58 defp classify(%{"event" => event} = p) when is_binary(event) do
59 case String.upcase(event) do
60 "PUSH" -> {:push, p}
61 "DELETE" -> {:delete, p}
62 _ -> :ignored
63 end
64 end
62 + # Old zot `notifications` payloads carried `event`/`type` = PUSH/DELETE
63 + # at the top level; the v2.1 `events` extension sends a CloudEvent with
64 + # `type` like `zotregistry.image.updated` and the image fields nested
65 + # under `data`. Handle both: derive the action from whichever action
66 + # string is present, and operate on the unwrapped data map.
67 + defp classify(%{"event" => event} = p) when is_binary(event), do: classify_action(event, p)
68 + defp classify(%{"type" => type} = p) when is_binary(type), do: classify_action(type, p)
69 + defp classify(_), do: :ignored
70 +
71 + defp classify_action(action, p) do
72 + data = unwrap_data(p)
73 + a = String.downcase(action)
65 74
66 defp classify(%{"type" => type} = p) when is_binary(type) do
67 # Some zot versions use `type` instead of `event`.
68 classify(Map.put(p, "event", type))
75 + cond do
76 + a == "push" or String.contains?(a, "updated") or String.contains?(a, "created") ->
77 + {:push, data}
78 +
79 + a == "delete" or String.contains?(a, "deleted") ->
80 + {:delete, data}
81 +
82 + true ->
83 + :ignored
84 + end
69 85 end
70 86
71 defp classify(_), do: :ignored
87 + defp unwrap_data(%{"data" => %{} = data}), do: data
88 + defp unwrap_data(p), do: p
72 89
73 90 defp handle_push(conn, p) do
74 with {:ok, repo, image} <- resolve_repo_and_image(p["repository"]),
75 reference when is_binary(reference) <- p["reference"] || p["tag"],
76 digest when is_binary(digest) <- p["digest"] do
91 + with {:ok, repo, image} <- resolve_repo_and_image(repository(p)),
92 + reference when is_binary(reference) <- reference(p) do
77 93 attrs = %{
78 94 version: reference,
79 digest: digest,
95 + digest: digest(p) || "",
80 96 size: parse_size(p),
81 media_type: p["mediaType"],
82 manifest_doc: p["manifest"] || %{},
83 pushed_at: parse_timestamp(p["timestamp"])
97 + media_type: p["mediaType"] || p["MediaType"],
98 + manifest_doc: p["manifest"] || p["Manifest"] || %{},
99 + pushed_at: parse_timestamp(p["timestamp"] || p["time"] || p["Timestamp"])
84 100 }
85 101
86 102 _ = Packages.record_push(repo, "container", image, attrs)
@@ -91,8 +107,8 @@ defmodule GitGudWeb.PackagesWebhookController do
91 107 end
92 108
93 109 defp handle_delete(conn, p) do
94 with {:ok, repo, image} <- resolve_repo_and_image(p["repository"]),
95 reference when is_binary(reference) <- p["reference"] || p["tag"] do
110 + with {:ok, repo, image} <- resolve_repo_and_image(repository(p)),
111 + reference when is_binary(reference) <- reference(p) do
96 112 _ = Packages.delete_version(repo, "container", image, reference)
97 113 send_resp(conn, 204, "")
98 114 else
@@ -100,6 +116,11 @@ defmodule GitGudWeb.PackagesWebhookController do
100 116 end
101 117 end
102 118
119 + # zot's field naming varies across the notifications/events payloads.
120 + defp repository(p), do: p["repository"] || p["name"] || p["Name"] || p["Repository"]
121 + defp reference(p), do: p["reference"] || p["tag"] || p["Reference"] || p["Tag"]
122 + defp digest(p), do: p["digest"] || p["Digest"] || p["manifestDigest"]
123 +
103 124 # ── helpers ────────────────────────────────────────────────────────
104 125
105 126 defp resolve_repo_and_image(nil), do: :error

Parents: a4513e0