Match the runner protocol's field numbers to forgejo-runner's

c4d7080 · gmorell · 2026-10-01 18:48

17 files +146 -25
Message
{commit_body(@commit)}

Files changed

modified lib/git_gud_web/controllers/runner_controller.ex
+3 −4
@@ -91,8 +91,8 @@ defmodule GitGudWeb.RunnerController do
91 91 end
92 92 end
93 93
94 − defp merged_labels(%RegisterRequest{labels: ls, agent_labels: als}) do
95 − (ls ++ als)
94 + defp merged_labels(%RegisterRequest{labels: ls, agent_labels: als, custom_labels: cls}) do
95 + (ls ++ als ++ cls)
96 96 |> Enum.reject(&(&1 == "" or is_nil(&1)))
97 97 |> Enum.uniq()
98 98 |> case do
@@ -771,8 +771,7 @@ defmodule GitGudWeb.RunnerController do
771 771 name: r.name,
772 772 version: r.version || "",
773 773 labels: List.wrap(r.labels["labels"] || []),
774 − token: token || "",
775 − address: ""
774 + token: token || ""
776 775 )
777 776 end
778 777
modified lib/git_gud_web/protos/runner/v1/messages.pb.ex
+29 −9
@@ -36,6 +36,21 @@ defmodule Runner.V1.PingResponse do
36 36 field :data, 1, type: :string
37 37 end
38 38
39 +defmodule Runner.V1.RunnerStatus do
40 + @moduledoc false
41 +
42 + use Protobuf,
43 + enum: true,
44 + full_name: "runner.v1.RunnerStatus",
45 + protoc_gen_elixir_version: "0.16.0",
46 + syntax: :proto3
47 +
48 + field :RUNNER_STATUS_UNSPECIFIED, 0
49 + field :RUNNER_STATUS_IDLE, 1
50 + field :RUNNER_STATUS_ACTIVE, 2
51 + field :RUNNER_STATUS_OFFLINE, 3
52 +end
53 +
39 54 defmodule Runner.V1.Runner do
40 55 @moduledoc false
41 56
@@ -46,11 +61,14 @@ defmodule Runner.V1.Runner do
46 61
47 62 field :id, 1, type: :int64
48 63 field :uuid, 2, type: :string
49 − field :name, 3, type: :string
50 − field :token, 4, type: :string
51 − field :address, 5, type: :string
52 − field :labels, 7, repeated: true, type: :string
53 − field :version, 9, type: :string
64 + field :token, 3, type: :string
65 + field :name, 4, type: :string
66 + field :status, 5, type: Runner.V1.RunnerStatus, enum: true
67 + field :agent_labels, 6, repeated: true, type: :string, json_name: "agentLabels", deprecated: true
68 + field :custom_labels, 7, repeated: true, type: :string, json_name: "customLabels", deprecated: true
69 + field :version, 8, type: :string
70 + field :labels, 9, repeated: true, type: :string
71 + field :ephemeral, 10, type: :bool
54 72 end
55 73
56 74 defmodule Runner.V1.RegisterRequest do
@@ -63,9 +81,11 @@ defmodule Runner.V1.RegisterRequest do
63 81
64 82 field :name, 1, type: :string
65 83 field :token, 2, type: :string
66 − field :version, 3, type: :string
67 − field :agent_labels, 5, repeated: true, type: :string, json_name: "agentLabels"
84 + field :agent_labels, 3, repeated: true, type: :string, json_name: "agentLabels", deprecated: true
85 + field :custom_labels, 4, repeated: true, type: :string, json_name: "customLabels", deprecated: true
86 + field :version, 5, type: :string
68 87 field :labels, 6, repeated: true, type: :string
88 + field :ephemeral, 7, type: :bool
69 89 end
70 90
71 91 defmodule Runner.V1.RegisterResponse do
@@ -123,8 +143,8 @@ defmodule Runner.V1.TaskNeed do
123 143 protoc_gen_elixir_version: "0.16.0",
124 144 syntax: :proto3
125 145
126 − field :result, 1, type: Runner.V1.Result, enum: true
127 − field :outputs, 2, repeated: true, type: Runner.V1.TaskNeed.OutputsEntry, map: true
146 + field :outputs, 1, repeated: true, type: Runner.V1.TaskNeed.OutputsEntry, map: true
147 + field :result, 2, type: Runner.V1.Result, enum: true
128 148 end
129 149
130 150 defmodule Runner.V1.Task.SecretsEntry do
modified priv/proto/runner/v1/messages.proto
+26 −12
@@ -26,26 +26,40 @@ message PingResponse {
26 26
27 27 // ── Runner registration / declaration ────────────────────────────────
28 28
29 +// Field numbers are the wire contract with forgejo-runner and MUST match
30 +// code.forgejo.org/forgejo/actions-proto-def (runner/v1/messages.proto).
31 +// They didn't: name/token were swapped and labels/version sat on each
32 +// other's numbers, so a runner read our version as its labels.
29 33 message Runner {
30 34 int64 id = 1;
31 35 string uuid = 2;
32 − string name = 3;
33 − string token = 4;
34 − string address = 5;
35 − // 6 reserved
36 − repeated string labels = 7;
37 − // 8 reserved
38 − string version = 9;
36 + string token = 3;
37 + string name = 4;
38 + RunnerStatus status = 5;
39 + repeated string agent_labels = 6 [deprecated = true];
40 + repeated string custom_labels = 7 [deprecated = true];
41 + string version = 8;
42 + repeated string labels = 9;
43 + bool ephemeral = 10;
44 +}
45 +
46 +enum RunnerStatus {
47 + RUNNER_STATUS_UNSPECIFIED = 0;
48 + RUNNER_STATUS_IDLE = 1;
49 + RUNNER_STATUS_ACTIVE = 2;
50 + RUNNER_STATUS_OFFLINE = 3;
39 51 }
40 52
41 53 message RegisterRequest {
42 54 string name = 1;
43 55 // The one-shot registration token the operator issues.
44 56 string token = 2;
45 − string version = 3;
46 − // Deprecated; runners that still send this set both. Honor both.
47 − repeated string agent_labels = 5;
57 + // Deprecated; runners that still send these set `labels` too. Honor both.
58 + repeated string agent_labels = 3 [deprecated = true];
59 + repeated string custom_labels = 4 [deprecated = true];
60 + string version = 5;
48 61 repeated string labels = 6;
62 + bool ephemeral = 7;
49 63 }
50 64
51 65 message RegisterResponse {
@@ -74,8 +88,8 @@ enum Result {
74 88 // Per-need state — the runner needs the parent-job result to drive
75 89 // `needs.<id>.result` and the parent's outputs map.
76 90 message TaskNeed {
77 − Result result = 1;
78 − map<string, string> outputs = 2;
91 + map<string, string> outputs = 1;
92 + Result result = 2;
79 93 }
80 94
81 95 message Task {
added test/git_gud_web/protos/runner_wire_test.exs
+88 −0
@@ -0,0 +1,88 @@
1 +defmodule GitGudWeb.Protos.RunnerWireTest do
2 + @moduledoc """
3 + The runner messages, checked against bytes laid out the way
4 + forgejo-runner lays them out (code.forgejo.org/forgejo/actions-proto-def,
5 + runner/v1/messages.proto) rather than round-tripped through our own
6 + module — which is how mismatched field numbers went unnoticed: a runner
7 + read our `version` as its labels and our `token` as its name.
8 + """
9 + use ExUnit.Case, async: true
10 +
11 + import Bitwise
12 +
13 + alias Runner.V1.DeclareResponse
14 + alias Runner.V1.RegisterRequest
15 + alias Runner.V1.Runner, as: RunnerMsg
16 + alias Runner.V1.TaskNeed
17 +
18 + # One length-delimited field: key = field number << 3 | wire type 2.
19 + defp str(field, value), do: <<field <<< 3 ||| 2>> <> varint(byte_size(value)) <> value
20 +
21 + defp varint(n) when n < 128, do: <<n>>
22 + defp varint(n), do: <<(n &&& 0x7F) ||| 0x80>> <> varint(n >>> 7)
23 +
24 + test "RegisterRequest decodes what a runner sends" do
25 + # name=1 token=2 agent_labels=3 custom_labels=4 version=5 labels=6
26 + bytes =
27 + str(1, "win-ltsc") <>
28 + str(2, "reg-token") <>
29 + str(3, "old-agent") <>
30 + str(4, "old-custom") <>
31 + str(5, "v13.2.0") <> str(6, "windows:host") <> str(6, "windows-latest:host")
32 +
33 + req = RegisterRequest.decode(bytes)
34 + assert req.name == "win-ltsc"
35 + assert req.token == "reg-token"
36 + assert req.version == "v13.2.0"
37 + assert req.labels == ["windows:host", "windows-latest:host"]
38 + assert req.agent_labels == ["old-agent"]
39 + assert req.custom_labels == ["old-custom"]
40 + end
41 +
42 + test "Runner encodes where a runner looks" do
43 + bytes =
44 + RunnerMsg.encode(%RunnerMsg{
45 + id: 7,
46 + uuid: "u",
47 + token: "secret",
48 + name: "win-ltsc",
49 + version: "v13.2.0",
50 + labels: ["windows:host"]
51 + })
52 +
53 + # token=3 name=4 version=8 labels=9
54 + assert :binary.match(bytes, str(3, "secret")) != :nomatch
55 + assert :binary.match(bytes, str(4, "win-ltsc")) != :nomatch
56 + assert :binary.match(bytes, str(8, "v13.2.0")) != :nomatch
57 + assert :binary.match(bytes, str(9, "windows:host")) != :nomatch
58 + end
59 +
60 + test "a DeclareResponse reads back with the runner's own numbering" do
61 + bytes =
62 + DeclareResponse.encode(%DeclareResponse{
63 + runner: %RunnerMsg{name: "win-ltsc", version: "v13.2.0", labels: ["windows:host"]}
64 + })
65 +
66 + # Field 1 is the embedded Runner; decode it by upstream numbers.
67 + <<0x0A, rest::binary>> = bytes
68 + {len, runner_bytes} = decode_varint(rest)
69 + runner = binary_part(runner_bytes, 0, len)
70 +
71 + assert :binary.match(runner, str(4, "win-ltsc")) != :nomatch
72 + assert :binary.match(runner, str(8, "v13.2.0")) != :nomatch
73 + assert :binary.match(runner, str(9, "windows:host")) != :nomatch
74 + end
75 +
76 + test "TaskNeed: outputs=1, result=2" do
77 + bytes = TaskNeed.encode(%TaskNeed{result: :RESULT_SUCCESS, outputs: %{}})
78 + # result is a varint (wire type 0) on field 2: key 0x10, value 1.
79 + assert bytes == <<0x10, 0x01>>
80 + end
81 +
82 + defp decode_varint(<<0::1, n::7, rest::binary>>), do: {n, rest}
83 +
84 + defp decode_varint(<<1::1, n::7, rest::binary>>) do
85 + {high, rest} = decode_varint(rest)
86 + {n + (high <<< 7), rest}
87 + end
88 +end

Parents: 95e9713