neiam
/gitgud
Git Gud
public · Issues · Pulls · Labels · Forks · Compare · Actions success · Badges · Packages
⭐
1
Log in to mark this repository.
Registry tokens: name and namespace the generated pull secret
76419e8 · gmorell · 2026-10-01 20:35
Message
{commit_body(@commit)}
Files changed
modified
lib/git_gud_web/live/registry_token_live/index.ex
+55
−7
@@ -24,6 +24,8 @@ defmodule GitGudWeb.RegistryTokenLive.Index do
| 24 | 24 | |> GitGudWeb.RepoLive.Header.assign_chrome(repo) |
| 25 | 25 | |> assign(:registry_host, Application.get_env(:git_gud, :registry_host)) |
| 26 | 26 | |> assign(:new_token, nil) |
| 27 | + |> assign(:secret_name, "") | |
| 28 | + |> assign(:secret_namespace, "") | |
| 27 | 29 | |> assign(:all_scopes, Packages.RegistryToken.scopes()) |
| 28 | 30 | |> load() |
| 29 | 31 | |> assign(:page_title, "Registry tokens — #{owner}/#{name}")} |
@@ -63,6 +65,15 @@ defmodule GitGudWeb.RegistryTokenLive.Index do
| 63 | 65 | end |
| 64 | 66 | end |
| 65 | 67 | |
| 68 | + # The pull-secret preview's name and namespace, as typed. Blank means the | |
| 69 | + # ghost default: `<repo>-pull`, and no namespace. | |
| 70 | + def handle_event("secret_meta", params, socket) do | |
| 71 | + {:noreply, | |
| 72 | + socket | |
| 73 | + |> assign(:secret_name, String.trim(params["secret_name"] || "")) | |
| 74 | + |> assign(:secret_namespace, String.trim(params["secret_namespace"] || ""))} | |
| 75 | + end | |
| 76 | + | |
| 66 | 77 | def handle_event("delete_token", %{"id" => id}, socket) do |
| 67 | 78 | if socket.assigns.can_admin? do |
| 68 | 79 | :ok = Packages.delete_registry_token(socket.assigns.repo, String.to_integer(id)) |
@@ -82,10 +93,18 @@ defmodule GitGudWeb.RegistryTokenLive.Index do
| 82 | 93 | |
| 83 | 94 | defp image_ref(assigns), do: "#{assigns.registry_host}/#{assigns.handle}/#{assigns.repo.name}" |
| 84 | 95 | |
| 96 | + defp default_secret_name(repo), do: "#{repo.name}-pull" | |
| 97 | + | |
| 98 | + defp secret_name(%{secret_name: "", repo: repo}), do: default_secret_name(repo) | |
| 99 | + defp secret_name(%{secret_name: name}), do: name | |
| 100 | + | |
| 85 | 101 | # Ready-to-apply kubernetes.io/dockerconfigjson secret for pulling this |
| 86 | − # repo's images with the freshly-minted token. | |
| 102 | + # repo's images with the freshly-minted token, under the name and in the | |
| 103 | + # namespace typed above it — the ones the Deployment's imagePullSecrets | |
| 104 | + # will name. Without a namespace it lands wherever kubectl defaults to, | |
| 105 | + # which is rarely where the Deployment is, so the YAML says so. | |
| 87 | 106 | defp pull_secret_yaml(assigns) do |
| 88 | − %{registry_host: host, repo: repo, new_token: %{name: user, secret: pass}} = assigns | |
| 107 | + %{registry_host: host, new_token: %{name: user, secret: pass}} = assigns | |
| 89 | 108 | auth = Base.encode64("#{user}:#{pass}") |
| 90 | 109 | |
| 91 | 110 | dockercfg = |
@@ -93,12 +112,18 @@ defmodule GitGudWeb.RegistryTokenLive.Index do
| 93 | 112 | "auths" => %{host => %{"username" => user, "password" => pass, "auth" => auth}} |
| 94 | 113 | }) |
| 95 | 114 | |
| 115 | + namespace = | |
| 116 | + case assigns.secret_namespace do | |
| 117 | + "" -> " # no namespace: apply with `kubectl -n <the Deployment's namespace>`\n" | |
| 118 | + ns -> " namespace: #{ns}\n" | |
| 119 | + end | |
| 120 | + | |
| 96 | 121 | """ |
| 97 | 122 | apiVersion: v1 |
| 98 | 123 | kind: Secret |
| 99 | 124 | metadata: |
| 100 | − name: #{repo.name}-pull | |
| 101 | − type: kubernetes.io/dockerconfigjson | |
| 125 | + name: #{secret_name(assigns)} | |
| 126 | + #{namespace}type: kubernetes.io/dockerconfigjson | |
| 102 | 127 | data: |
| 103 | 128 | .dockerconfigjson: #{Base.encode64(dockercfg)} |
| 104 | 129 | """ |
@@ -146,13 +171,36 @@ defmodule GitGudWeb.RegistryTokenLive.Index do
| 146 | 171 | podman login {@registry_host} -u {@new_token.name} -p {@new_token.secret} |
| 147 | 172 | </span> |
| 148 | 173 | </p> |
| 149 | − <details :if={@registry_host} class="text-xs"> | |
| 174 | + <details :if={@registry_host} class="text-xs" open> | |
| 150 | 175 | <summary class="cursor-pointer opacity-80">Kubernetes imagePullSecret</summary> |
| 176 | + <form phx-change="secret_meta" class="flex flex-wrap gap-2 mt-2"> | |
| 177 | + <label class="form-control"> | |
| 178 | + <span class="opacity-70">Secret name</span> | |
| 179 | + <input | |
| 180 | + name="secret_name" | |
| 181 | + value={@secret_name} | |
| 182 | + placeholder={default_secret_name(@repo)} | |
| 183 | + phx-debounce="200" | |
| 184 | + class="input input-xs input-bordered font-mono" | |
| 185 | + /> | |
| 186 | + </label> | |
| 187 | + <label class="form-control"> | |
| 188 | + <span class="opacity-70">Namespace</span> | |
| 189 | + <input | |
| 190 | + name="secret_namespace" | |
| 191 | + value={@secret_namespace} | |
| 192 | + placeholder="the Deployment's namespace" | |
| 193 | + phx-debounce="200" | |
| 194 | + class="input input-xs input-bordered font-mono" | |
| 195 | + /> | |
| 196 | + </label> | |
| 197 | + </form> | |
| 151 | 198 | <pre class="bg-black text-green-300 p-2 rounded overflow-auto select-all mt-1">{pull_secret_yaml(assigns)}</pre> |
| 152 | 199 | <p class="opacity-70 mt-1"> |
| 153 | 200 | Apply it, then set <code>imagePullSecrets</code> |
| 154 | − to <code>{@repo.name}-pull</code> | |
| 155 | − for image <code>{image_ref(assigns)}:thetag</code>. | |
| 201 | + to <code>{secret_name(assigns)}</code> | |
| 202 | + for image <code>{image_ref(assigns)}:thetag</code> | |
| 203 | + — in the same namespace: a pod only sees pull secrets in its own. | |
| 156 | 204 | </p> |
| 157 | 205 | </details> |
| 158 | 206 | </div> |
added
test/git_gud_web/live/registry_token_live_test.exs
+71
−0
@@ -0,0 +1,71 @@
| 1 | +defmodule GitGudWeb.RegistryTokenLiveTest do | |
| 2 | + @moduledoc """ | |
| 3 | + The registry-token page's pull-secret preview: it has to come out under | |
| 4 | + the name and namespace the Deployment will look in, or the cluster pulls | |
| 5 | + anonymously and gets 401 with the token never used. | |
| 6 | + """ | |
| 7 | + use GitGudWeb.ConnCase, async: false | |
| 8 | + | |
| 9 | + import GitGud.ForgeFixtures | |
| 10 | + import Phoenix.LiveViewTest | |
| 11 | + | |
| 12 | + alias GitGud.Repositories.Storage | |
| 13 | + | |
| 14 | + @moduletag :git_required | |
| 15 | + | |
| 16 | + setup do | |
| 17 | + if System.find_executable("git") == nil do | |
| 18 | + {:skip, "git not on PATH"} | |
| 19 | + else | |
| 20 | + previous = Application.get_env(:git_gud, :registry_host) | |
| 21 | + Application.put_env(:git_gud, :registry_host, "ggr.example.com") | |
| 22 | + | |
| 23 | + on_exit(fn -> | |
| 24 | + if previous, | |
| 25 | + do: Application.put_env(:git_gud, :registry_host, previous), | |
| 26 | + else: Application.delete_env(:git_gud, :registry_host) | |
| 27 | + end) | |
| 28 | + | |
| 29 | + {owner, repo} = repository_fixture() | |
| 30 | + repo = GitGud.Repo.preload(repo, [:owner, :organization]) | |
| 31 | + path = "/r/#{Storage.repo_handle(repo)}/#{repo.name}/settings/registry-tokens" | |
| 32 | + {:ok, owner: owner, repo: repo, path: path} | |
| 33 | + end | |
| 34 | + end | |
| 35 | + | |
| 36 | + defp create_token(view) do | |
| 37 | + view | |
| 38 | + |> form("form[phx-submit=create_token]", %{"name" => "argo-pull", "scopes" => ["pull"]}) | |
| 39 | + |> render_submit() | |
| 40 | + end | |
| 41 | + | |
| 42 | + test "defaults to <repo>-pull with no namespace, and says to pass -n", | |
| 43 | + %{conn: conn, owner: owner, repo: repo, path: path} do | |
| 44 | + {:ok, view, _html} = conn |> log_in_user(owner) |> live(path) | |
| 45 | + html = create_token(view) | |
| 46 | + | |
| 47 | + assert html =~ "name: #{repo.name}-pull" | |
| 48 | + refute html =~ ~r/\n\s*namespace: / | |
| 49 | + assert html =~ "apply with `kubectl -n" | |
| 50 | + assert html =~ ~s(placeholder="#{repo.name}-pull") | |
| 51 | + end | |
| 52 | + | |
| 53 | + test "uses the name and namespace typed in", | |
| 54 | + %{conn: conn, owner: owner, path: path} do | |
| 55 | + {:ok, view, _html} = conn |> log_in_user(owner) |> live(path) | |
| 56 | + create_token(view) | |
| 57 | + | |
| 58 | + html = | |
| 59 | + view | |
| 60 | + |> form("form[phx-change=secret_meta]", %{ | |
| 61 | + "secret_name" => "ggr-pull", | |
| 62 | + "secret_namespace" => "ws-tower" | |
| 63 | + }) | |
| 64 | + |> render_change() | |
| 65 | + | |
| 66 | + assert html =~ "name: ggr-pull" | |
| 67 | + assert html =~ ~r/\n\s*namespace: ws-tower\n/ | |
| 68 | + refute html =~ "apply with `kubectl -n" | |
| 69 | + assert html =~ "to <code>ggr-pull</code>" | |
| 70 | + end | |
| 71 | +end |
Parents: 713c997