neiam
/gitgud
Git Gud
public · Issues · Pulls · Labels · Forks · Compare · Actions success · Badges · Packages
⭐
1
Log in to mark this repository.
Match the runner protocol's field numbers to forgejo-runner's
c4d7080 · gmorell · 2026-10-01 18:48
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